mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-24 16:03:43 +08:00
Automatic merge from release/2.0.0 -> release/2.1.0
* commit '2dd13257361edec640f0bdb9d4616a37df7cd211': cloudcommon: add validator helpers
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Empty struct{}
|
||||
type Choices map[string]Empty
|
||||
|
||||
func NewChoices(choices ...string) Choices {
|
||||
cs := Choices{}
|
||||
for _, choice := range choices {
|
||||
cs[choice] = Empty{}
|
||||
}
|
||||
return cs
|
||||
}
|
||||
|
||||
func (cs Choices) Has(choice string) bool {
|
||||
_, ok := cs[choice]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (cs Choices) String() string {
|
||||
choices := make([]string, len(cs))
|
||||
i := 0
|
||||
for choice, _ := range cs {
|
||||
choices[i] = choice
|
||||
i++
|
||||
}
|
||||
return strings.Join(choices, "|")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package validators // import "yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
@@ -0,0 +1,106 @@
|
||||
package validators
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrType uintptr
|
||||
|
||||
const (
|
||||
ERR_SUCCESS ErrType = iota
|
||||
ERR_GENERAL // uncategorized error
|
||||
ERR_MISSING_KEY
|
||||
ERR_INVALID_TYPE
|
||||
ERR_INVALID_CHOICE
|
||||
ERR_NOT_IN_RANGE
|
||||
ERR_INVALID_VALUE
|
||||
ERR_MODEL_MANAGER
|
||||
ERR_MODEL_NOT_FOUND
|
||||
)
|
||||
|
||||
var errTypeToString = map[ErrType]string{
|
||||
ERR_SUCCESS: "No error",
|
||||
ERR_GENERAL: "General error",
|
||||
ERR_MISSING_KEY: "Missing_key error",
|
||||
ERR_INVALID_TYPE: "Invalid type error",
|
||||
ERR_INVALID_CHOICE: "Invalid choice error",
|
||||
ERR_NOT_IN_RANGE: "Not in range error",
|
||||
ERR_INVALID_VALUE: "Invalid value error",
|
||||
ERR_MODEL_MANAGER: "Model manager error",
|
||||
ERR_MODEL_NOT_FOUND: "Model not found error",
|
||||
}
|
||||
|
||||
func (errType ErrType) String() string {
|
||||
s, ok := errTypeToString[errType]
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
return "unknown error"
|
||||
}
|
||||
|
||||
type ValidateError struct {
|
||||
ErrType ErrType
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (ve *ValidateError) Error() string {
|
||||
return ve.Msg
|
||||
}
|
||||
|
||||
// TODO let each validator provide the error
|
||||
func newMissingKeyError(key string) error {
|
||||
msg := fmt.Sprintf("missing %q", key)
|
||||
return newError(ERR_MISSING_KEY, msg)
|
||||
}
|
||||
|
||||
func newGeneralError(key string, err error) error {
|
||||
msg := fmt.Sprintf("general error for %q: %s", key, err)
|
||||
return newError(ERR_GENERAL, msg)
|
||||
}
|
||||
|
||||
func newInvalidTypeError(key string, typ string, err error) error {
|
||||
msg := fmt.Sprintf("expecting %s type for %q: %s", typ, key, err)
|
||||
return newError(ERR_INVALID_TYPE, msg)
|
||||
}
|
||||
|
||||
func newInvalidChoiceError(key string, choices Choices, choice string) error {
|
||||
msg := fmt.Sprintf("invalid %q, want %s, got %s", key, choices, choice)
|
||||
return newError(ERR_INVALID_CHOICE, msg)
|
||||
}
|
||||
|
||||
func newNotInRangeError(key string, value, lower, upper int64) error {
|
||||
msg := fmt.Sprintf("invalid %q: %d, want [%d,%d]", key, value, lower, upper)
|
||||
return newError(ERR_NOT_IN_RANGE, msg)
|
||||
}
|
||||
|
||||
func newInvalidValueError(key string, value string) error {
|
||||
msg := fmt.Sprintf("invalid %q: %s", key, value)
|
||||
return newError(ERR_INVALID_VALUE, msg)
|
||||
}
|
||||
|
||||
func newModelManagerError(modelKeyword string) error {
|
||||
msg := fmt.Sprintf("internal error: getting model manager for %q failed",
|
||||
modelKeyword)
|
||||
return newError(ERR_MODEL_MANAGER, msg)
|
||||
}
|
||||
|
||||
func newModelNotFoundError(modelKeyword, idOrName string, err error) error {
|
||||
msg := fmt.Sprintf("cannot find %q with id/name %q: %s",
|
||||
modelKeyword, idOrName, err)
|
||||
return newError(ERR_MODEL_NOT_FOUND, msg)
|
||||
}
|
||||
|
||||
func newError(typ ErrType, msg string) error {
|
||||
return &ValidateError{
|
||||
ErrType: typ,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
func IsModelNotFoundError(err error) bool {
|
||||
ve, ok := err.(*ValidateError)
|
||||
if ok && ve.ErrType == ERR_MODEL_NOT_FOUND {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
package validators
|
||||
|
||||
// TODO
|
||||
//
|
||||
// email
|
||||
// uuid
|
||||
// uri
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/pkg/gotypes"
|
||||
"yunion.io/x/pkg/util/regutils"
|
||||
"yunion.io/x/sqlchemy"
|
||||
)
|
||||
|
||||
type ValidatorFunc func(*jsonutils.JSONDict) error
|
||||
|
||||
type IValidatorBase interface {
|
||||
Validate(data *jsonutils.JSONDict) error
|
||||
}
|
||||
|
||||
type IValidator interface {
|
||||
IValidatorBase
|
||||
getValue() interface{}
|
||||
setDefault(data *jsonutils.JSONDict) bool
|
||||
}
|
||||
|
||||
type Validator struct {
|
||||
parent IValidator
|
||||
Key string
|
||||
optional bool
|
||||
defaultVal interface{}
|
||||
value jsonutils.JSONObject
|
||||
}
|
||||
|
||||
func (v *Validator) Optional(optional bool) IValidator {
|
||||
v.optional = optional
|
||||
return v.parent
|
||||
}
|
||||
|
||||
func (v *Validator) Default(defaultVal interface{}) IValidator {
|
||||
v.defaultVal = defaultVal
|
||||
return v.parent
|
||||
}
|
||||
|
||||
func (v *Validator) getValue() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Validator) setDefault(data *jsonutils.JSONDict) bool {
|
||||
switch v.defaultVal.(type) {
|
||||
case string:
|
||||
s := v.defaultVal.(string)
|
||||
v.value = jsonutils.NewString(s)
|
||||
data.Set(v.Key, v.value)
|
||||
return true
|
||||
case int, int32, int64, uint, uint32, uint64:
|
||||
value := reflect.ValueOf(v.defaultVal)
|
||||
value64 := value.Convert(gotypes.Int64Type)
|
||||
defaultVal64 := value64.Interface().(int64)
|
||||
v.value = jsonutils.NewInt(defaultVal64)
|
||||
data.Set(v.Key, v.value)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(data *jsonutils.JSONDict) error {
|
||||
err, _ := v.validateEx(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Validator) validateEx(data *jsonutils.JSONDict) (err error, isSet bool) {
|
||||
if !data.Contains(v.Key) {
|
||||
if v.defaultVal != nil {
|
||||
isSet = v.parent.setDefault(data)
|
||||
return nil, isSet
|
||||
}
|
||||
if !v.optional {
|
||||
err = newMissingKeyError(v.Key)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
value, err := data.Get(v.Key)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
v.value = value
|
||||
isSet = true
|
||||
return
|
||||
}
|
||||
|
||||
type ValidatorStringChoices struct {
|
||||
Validator
|
||||
Choices Choices
|
||||
defaultVal string
|
||||
Value string
|
||||
}
|
||||
|
||||
func NewStringChoicesValidator(key string, choices Choices) *ValidatorStringChoices {
|
||||
v := &ValidatorStringChoices{
|
||||
Validator: Validator{Key: key},
|
||||
Choices: choices,
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorStringChoices) Default(s string) IValidator {
|
||||
if v.Choices.Has(s) {
|
||||
return v.Validator.Default(s)
|
||||
}
|
||||
panic("invalid default for " + v.Key)
|
||||
}
|
||||
|
||||
func (v *ValidatorStringChoices) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorStringChoices) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
s, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return newGeneralError(v.Key, err)
|
||||
}
|
||||
if !v.Choices.Has(s) {
|
||||
return newInvalidChoiceError(v.Key, v.Choices, s)
|
||||
}
|
||||
// in case it's stringified from v.value
|
||||
data.Set(v.Key, jsonutils.NewString(s))
|
||||
v.Value = s
|
||||
return nil
|
||||
}
|
||||
|
||||
type ValidatorRange struct {
|
||||
Validator
|
||||
Lower int64
|
||||
Upper int64
|
||||
Value int64
|
||||
}
|
||||
|
||||
func (v *ValidatorRange) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorRange) Default(i int64) IValidator {
|
||||
if i >= v.Lower && i <= v.Upper {
|
||||
return v.Validator.Default(i)
|
||||
}
|
||||
panic("invalid default for " + v.Key)
|
||||
}
|
||||
func (v *ValidatorRange) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
i, err := v.value.Int()
|
||||
if err != nil {
|
||||
return newInvalidTypeError(v.Key, "integer", err)
|
||||
}
|
||||
if i < v.Lower || i > v.Upper {
|
||||
return newNotInRangeError(v.Key, i, v.Lower, v.Upper)
|
||||
}
|
||||
data.Set(v.Key, jsonutils.NewInt(i))
|
||||
v.Value = i
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRangeValidator(key string, lower int64, upper int64) *ValidatorRange {
|
||||
v := &ValidatorRange{
|
||||
Validator: Validator{Key: key},
|
||||
Lower: lower,
|
||||
Upper: upper,
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
func NewPortValidator(key string) *ValidatorRange {
|
||||
return NewRangeValidator(key, 1, 65535)
|
||||
}
|
||||
|
||||
func NewNonNegativeValidator(key string) *ValidatorRange {
|
||||
return NewRangeValidator(key, 0, math.MaxInt64)
|
||||
}
|
||||
|
||||
type ValidatorModelIdOrName struct {
|
||||
Validator
|
||||
ModelKeyword string
|
||||
ProjectId string
|
||||
ModelManager db.IModelManager
|
||||
Model db.IModel
|
||||
modelIdKey string
|
||||
}
|
||||
|
||||
func (v *ValidatorModelIdOrName) getValue() interface{} {
|
||||
return v.Model
|
||||
}
|
||||
|
||||
func NewModelIdOrNameValidator(key string, modelKeyword string, projectId string) *ValidatorModelIdOrName {
|
||||
v := &ValidatorModelIdOrName{
|
||||
Validator: Validator{Key: key},
|
||||
ProjectId: projectId,
|
||||
ModelKeyword: modelKeyword,
|
||||
modelIdKey: key + "_id",
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorModelIdOrName) ModelIdKey(modelIdKey string) *ValidatorModelIdOrName {
|
||||
v.modelIdKey = modelIdKey
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorModelIdOrName) validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
modelIdOrName, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
modelManager := db.GetModelManager(v.ModelKeyword)
|
||||
if modelManager == nil {
|
||||
return newModelManagerError(v.ModelKeyword)
|
||||
}
|
||||
v.ModelManager = modelManager
|
||||
model, err := modelManager.FetchByIdOrName(v.ProjectId, modelIdOrName)
|
||||
if err != nil {
|
||||
return newModelNotFoundError(v.ModelKeyword, modelIdOrName, err)
|
||||
}
|
||||
v.Model = model
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ValidatorModelIdOrName) Validate(data *jsonutils.JSONDict) error {
|
||||
err := v.validate(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if v.Model != nil {
|
||||
if len(v.modelIdKey) > 0 {
|
||||
data.Remove(v.Key)
|
||||
data.Set(v.modelIdKey, jsonutils.NewString(v.Model.GetId()))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *ValidatorModelIdOrName) QueryFilter(q *sqlchemy.SQuery, data *jsonutils.JSONDict) (*sqlchemy.SQuery, error) {
|
||||
err := v.validate(data)
|
||||
if err != nil {
|
||||
if IsModelNotFoundError(err) {
|
||||
// hack
|
||||
q = q.Equals(v.modelIdKey, "0")
|
||||
q = q.Equals(v.modelIdKey, "1")
|
||||
return q, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if v.Model != nil {
|
||||
q = q.Equals(v.modelIdKey, v.Model.GetId())
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
type ValidatorRegexp struct {
|
||||
Validator
|
||||
Regexp *regexp.Regexp
|
||||
Value string
|
||||
allowEmpty bool
|
||||
}
|
||||
|
||||
func (v *ValidatorRegexp) AllowEmpty(allowEmpty bool) *ValidatorRegexp {
|
||||
v.allowEmpty = allowEmpty
|
||||
return v
|
||||
}
|
||||
|
||||
func (v *ValidatorRegexp) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorRegexp) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
value, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return newInvalidTypeError(v.Key, "string", err)
|
||||
}
|
||||
if v.allowEmpty && len(value) == 0 {
|
||||
return nil
|
||||
}
|
||||
if !v.Regexp.MatchString(value) {
|
||||
return newInvalidValueError(v.Key, value)
|
||||
}
|
||||
v.Value = value
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewRegexpValidator(key string, regexp *regexp.Regexp) *ValidatorRegexp {
|
||||
v := &ValidatorRegexp{
|
||||
Validator: Validator{Key: key},
|
||||
Regexp: regexp,
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
type ValidatorDomainName struct {
|
||||
ValidatorRegexp
|
||||
}
|
||||
|
||||
func NewDomainNameValidator(key string) *ValidatorDomainName {
|
||||
v := &ValidatorDomainName{
|
||||
ValidatorRegexp: *NewRegexpValidator(key, regutils.DOMAINNAME_REG),
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
type ValidatorURLPath struct {
|
||||
ValidatorRegexp
|
||||
}
|
||||
|
||||
// URI Path as defined in https://tools.ietf.org/html/rfc3986#section-3.3
|
||||
var regexpURLPath = regexp.MustCompile(`^(?:/[a-zA-Z0-9.%$&'()*+,;=!~_-]*)*$`)
|
||||
|
||||
func NewURLPathValidator(key string) *ValidatorURLPath {
|
||||
v := &ValidatorURLPath{
|
||||
ValidatorRegexp: *NewRegexpValidator(key, regexpURLPath),
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
type ValidatorStruct struct {
|
||||
Validator
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (v *ValidatorStruct) getValue() interface{} {
|
||||
return v.Value
|
||||
}
|
||||
|
||||
func (v *ValidatorStruct) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
err := v.value.Unmarshal(v.Value)
|
||||
if err != nil {
|
||||
return newGeneralError(v.Key, err)
|
||||
}
|
||||
if valueValidator, ok := v.Value.(IValidatorBase); ok {
|
||||
err = valueValidator.Validate(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewStructValidator(key string, value interface{}) *ValidatorStruct {
|
||||
v := &ValidatorStruct{
|
||||
Validator: Validator{Key: key},
|
||||
Value: value,
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
|
||||
type ValidatorIPv4Addr struct {
|
||||
Validator
|
||||
IP net.IP
|
||||
}
|
||||
|
||||
func (v *ValidatorIPv4Addr) getValue() interface{} {
|
||||
return v.IP
|
||||
}
|
||||
|
||||
func (v *ValidatorIPv4Addr) setDefault(data *jsonutils.JSONDict) bool {
|
||||
if v.defaultVal == nil {
|
||||
return false
|
||||
}
|
||||
defaultIP, ok := v.defaultVal.(net.IP)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
value := jsonutils.NewString(defaultIP.String())
|
||||
v.value = value
|
||||
data.Set(v.Key, value)
|
||||
return true
|
||||
}
|
||||
|
||||
func (v *ValidatorIPv4Addr) Validate(data *jsonutils.JSONDict) error {
|
||||
if err, isSet := v.Validator.validateEx(data); err != nil || !isSet {
|
||||
return err
|
||||
}
|
||||
s, err := v.value.GetString()
|
||||
if err != nil {
|
||||
return newInvalidTypeError(v.Key, "string", err)
|
||||
}
|
||||
ip := net.ParseIP(s).To4()
|
||||
if ip == nil {
|
||||
return newInvalidValueError(v.Key, s)
|
||||
}
|
||||
v.IP = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewIPv4AddrValidator(key string) *ValidatorIPv4Addr {
|
||||
v := &ValidatorIPv4Addr{
|
||||
Validator: Validator{Key: key},
|
||||
}
|
||||
v.parent = v
|
||||
return v
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
package validators
|
||||
|
||||
// TODO
|
||||
//
|
||||
// - strict type, no implicit conversion
|
||||
// - test model validator
|
||||
// - invalid default for string choice, range
|
||||
|
||||
import (
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
)
|
||||
|
||||
func TestURLPathRegexp(t *testing.T) {
|
||||
cases := []struct {
|
||||
in string
|
||||
match bool
|
||||
}{
|
||||
{in: "", match: true},
|
||||
{in: "/", match: true},
|
||||
{in: "/p", match: true},
|
||||
{in: "/p/", match: true},
|
||||
{in: "p", match: false},
|
||||
{in: "p/", match: false},
|
||||
{in: "/p?", match: false},
|
||||
{in: "/p#", match: false},
|
||||
}
|
||||
for _, c := range cases {
|
||||
got := regexpURLPath.MatchString(c.in)
|
||||
if got != c.match {
|
||||
t.Errorf("%q match, want %v, got %v", c.in, c.match, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type C struct {
|
||||
Name string
|
||||
In string
|
||||
Out string
|
||||
Optional bool
|
||||
Default interface{}
|
||||
ValueWant interface{}
|
||||
Err ErrType
|
||||
}
|
||||
|
||||
func testS(t *testing.T, v IValidator, c *C) {
|
||||
j, _ := jsonutils.ParseString(c.In)
|
||||
jd := j.(*jsonutils.JSONDict)
|
||||
err := v.Validate(jd)
|
||||
if err != nil {
|
||||
verr, ok := err.(*ValidateError)
|
||||
if ok {
|
||||
if verr.ErrType != c.Err {
|
||||
t.Errorf("error want %q, got %q",
|
||||
c.Err, verr.ErrType)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("want error type ValidateError")
|
||||
}
|
||||
} else {
|
||||
if c.Err != ERR_SUCCESS {
|
||||
t.Errorf("expect error: %s", c.Err)
|
||||
}
|
||||
}
|
||||
jWant, _ := jsonutils.ParseString(c.Out)
|
||||
if !reflect.DeepEqual(j, jWant) {
|
||||
t.Errorf("json out want %s, got %s",
|
||||
jWant.String(), j.String())
|
||||
}
|
||||
value := v.getValue()
|
||||
if !reflect.DeepEqual(value, c.ValueWant) {
|
||||
t.Errorf("value want %#v, got %#v", c.ValueWant, value)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringChoicesValidator(t *testing.T) {
|
||||
choices := NewChoices("choice0", "choice1", "100")
|
||||
cases := []*C{
|
||||
{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: false,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "choice0"}`,
|
||||
Default: "choice0",
|
||||
ValueWant: "choice0",
|
||||
},
|
||||
{
|
||||
Name: "stringified",
|
||||
In: `{"s": 100}`,
|
||||
Out: `{s: "100"}`,
|
||||
ValueWant: "100",
|
||||
},
|
||||
{
|
||||
Name: "stringified invalid choice",
|
||||
In: `{"s": 101}`,
|
||||
Out: `{"s": 101}`,
|
||||
Err: ERR_INVALID_CHOICE,
|
||||
ValueWant: "",
|
||||
},
|
||||
{
|
||||
Name: "good choice",
|
||||
In: `{"s": "choice1"}`,
|
||||
Out: `{"s": "choice1"}`,
|
||||
ValueWant: "choice1",
|
||||
},
|
||||
{
|
||||
Name: "bad choice",
|
||||
In: `{"s": "badchoice"}`,
|
||||
Out: `{"s": "badchoice"}`,
|
||||
Err: ERR_INVALID_CHOICE,
|
||||
ValueWant: "",
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewStringChoicesValidator("s", choices)
|
||||
if c.Default != nil {
|
||||
s := c.Default.(string)
|
||||
v.Default(s)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRangeValidator(t *testing.T) {
|
||||
cases := []*C{
|
||||
{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: int64(0),
|
||||
},
|
||||
{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: int64(0),
|
||||
},
|
||||
{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: 1}`,
|
||||
Default: int64(1),
|
||||
ValueWant: int64(1),
|
||||
},
|
||||
{
|
||||
Name: "parsed",
|
||||
In: `{s: "100"}`,
|
||||
Out: `{s: 100}`,
|
||||
ValueWant: int64(100),
|
||||
},
|
||||
{
|
||||
Name: "parsed invalid int",
|
||||
In: `{s: "abc"}`,
|
||||
Out: `{s: "abc"}`,
|
||||
Err: ERR_INVALID_TYPE,
|
||||
ValueWant: int64(0),
|
||||
},
|
||||
{
|
||||
Name: "parsed not in range",
|
||||
In: `{s: "65536"}`,
|
||||
Out: `{s: "65536"}`,
|
||||
Err: ERR_NOT_IN_RANGE,
|
||||
ValueWant: int64(0),
|
||||
},
|
||||
{
|
||||
Name: "in range",
|
||||
In: `{s: 100}`,
|
||||
Out: `{s: 100}`,
|
||||
ValueWant: int64(100),
|
||||
},
|
||||
{
|
||||
Name: "not in range",
|
||||
In: `{s: 0}`,
|
||||
Out: `{s: 0}`,
|
||||
Err: ERR_NOT_IN_RANGE,
|
||||
ValueWant: int64(0),
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewPortValidator("s")
|
||||
if c.Default != nil {
|
||||
i := c.Default.(int64)
|
||||
v.Default(i)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegexValidator(t *testing.T) {
|
||||
type RegexC struct {
|
||||
*C
|
||||
AllowEmpty bool
|
||||
}
|
||||
cases := []*RegexC{
|
||||
{
|
||||
C: &C{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "example.com"}`,
|
||||
Default: "example.com",
|
||||
ValueWant: "example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid",
|
||||
In: `{s: "a.example.com"}`,
|
||||
Out: `{s: "a.example.com"}`,
|
||||
ValueWant: "a.example.com",
|
||||
},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid (allow empty)",
|
||||
In: `{s: ""}`,
|
||||
Out: `{s: ""}`,
|
||||
ValueWant: "",
|
||||
},
|
||||
AllowEmpty: true,
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "invalid",
|
||||
In: `{s: "/.example.com"}`,
|
||||
Out: `{s: "/.example.com"}`,
|
||||
ValueWant: "",
|
||||
Err: ERR_INVALID_VALUE,
|
||||
},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "invalid (disallow empty)",
|
||||
In: `{s: ""}`,
|
||||
Out: `{s: ""}`,
|
||||
ValueWant: "",
|
||||
Err: ERR_INVALID_VALUE,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewDomainNameValidator("s")
|
||||
if c.Default != nil {
|
||||
i := c.Default.(string)
|
||||
v.Default(i)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
if c.AllowEmpty {
|
||||
v.AllowEmpty(true)
|
||||
}
|
||||
testS(t, v, c.C)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIPv4Validator(t *testing.T) {
|
||||
var nilIP net.IP
|
||||
localIP := net.IPv4(127, 0, 0, 1).To4()
|
||||
cases := []*C{
|
||||
{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: nilIP,
|
||||
},
|
||||
{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: nilIP,
|
||||
},
|
||||
{
|
||||
Name: "missing with default",
|
||||
In: `{}`,
|
||||
Out: `{s: "127.0.0.1"}`,
|
||||
Default: localIP,
|
||||
ValueWant: localIP,
|
||||
},
|
||||
{
|
||||
Name: "valid",
|
||||
In: `{s: "127.0.0.1"}`,
|
||||
Out: `{s: "127.0.0.1"}`,
|
||||
ValueWant: localIP,
|
||||
},
|
||||
{
|
||||
Name: "valid (v4 in v6)",
|
||||
In: `{s: "::ffff:127.0.0.1"}`,
|
||||
Out: `{s: "::ffff:127.0.0.1"}`,
|
||||
ValueWant: localIP,
|
||||
},
|
||||
{
|
||||
Name: "invalid",
|
||||
In: `{s: "127.0.0"}`,
|
||||
Out: `{s: "127.0.0"}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nilIP,
|
||||
},
|
||||
{
|
||||
Name: "invalid (empty string)",
|
||||
In: `{s: ""}`,
|
||||
Out: `{s: ""}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nilIP,
|
||||
},
|
||||
{
|
||||
Name: "invalid (wrong type)",
|
||||
In: `{s: 100}`,
|
||||
Out: `{s: 100}`,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
ValueWant: nilIP,
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewIPv4AddrValidator("s")
|
||||
if c.Default != nil {
|
||||
i := c.Default.(net.IP)
|
||||
v.Default(i)
|
||||
}
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type TestStruct struct {
|
||||
F0 string
|
||||
F1 int
|
||||
F2 bool
|
||||
}
|
||||
|
||||
type TestVStruct TestStruct
|
||||
|
||||
func (v *TestVStruct) Validate(data *jsonutils.JSONDict) error {
|
||||
if v.F2 {
|
||||
return nil
|
||||
} else {
|
||||
return newInvalidValueError("F2", v.F0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStructValidator(t *testing.T) {
|
||||
type StructC struct {
|
||||
*C
|
||||
Value interface{}
|
||||
}
|
||||
defaultVal := &TestStruct{
|
||||
F0: "holy",
|
||||
F1: 100,
|
||||
F2: true,
|
||||
}
|
||||
var defaultValMiss *TestStruct
|
||||
{
|
||||
defaultValMissCopy := *defaultVal
|
||||
defaultValMissCopy.F2 = false
|
||||
defaultValMiss = &defaultValMissCopy
|
||||
}
|
||||
defaultValJsonStr := `{s: {"F0": "holy", "F1": 100, "F2": true}}`
|
||||
defaultValJsonStrMiss := `{s: {"F0": "holy", "F1": 100}}`
|
||||
defaultValJsonStrMore := `{s: {"F0": "holy", "F1": 100, "F2": true, "Foo": "bar"}}`
|
||||
|
||||
defaultVVal := (*TestVStruct)(defaultVal)
|
||||
var defaultVValBad *TestVStruct
|
||||
{
|
||||
defaultVValBadCopy := *defaultVVal
|
||||
defaultVValBadCopy.F2 = false
|
||||
defaultVValBad = &defaultVValBadCopy
|
||||
}
|
||||
defaultVValJsonStrBad := `{s: {"F0": "holy", "F1": 100, "F2": false}}`
|
||||
cases := []*StructC{
|
||||
{
|
||||
C: &C{
|
||||
Name: "missing non-optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Err: ERR_MISSING_KEY,
|
||||
ValueWant: &TestStruct{},
|
||||
},
|
||||
Value: &TestStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "missing optional",
|
||||
In: `{}`,
|
||||
Out: `{}`,
|
||||
Optional: true,
|
||||
ValueWant: &TestStruct{},
|
||||
},
|
||||
Value: &TestStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid",
|
||||
In: defaultValJsonStr,
|
||||
Out: defaultValJsonStr,
|
||||
ValueWant: defaultVal,
|
||||
},
|
||||
Value: &TestStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid (missing fields)",
|
||||
In: defaultValJsonStrMiss,
|
||||
Out: defaultValJsonStrMiss,
|
||||
ValueWant: defaultValMiss,
|
||||
},
|
||||
Value: &TestStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid (more fields)",
|
||||
In: defaultValJsonStrMore,
|
||||
Out: defaultValJsonStrMore,
|
||||
ValueWant: defaultVal,
|
||||
},
|
||||
Value: &TestStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "valid (struct says valid)",
|
||||
In: defaultValJsonStr,
|
||||
Out: defaultValJsonStr,
|
||||
ValueWant: defaultVVal,
|
||||
},
|
||||
Value: &TestVStruct{},
|
||||
},
|
||||
{
|
||||
C: &C{
|
||||
Name: "invalid (struct says invalid)",
|
||||
In: defaultVValJsonStrBad,
|
||||
Out: defaultVValJsonStrBad,
|
||||
ValueWant: defaultVValBad,
|
||||
Err: ERR_INVALID_VALUE,
|
||||
},
|
||||
Value: &TestVStruct{},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(c.Name, func(t *testing.T) {
|
||||
v := NewStructValidator("s", c.Value)
|
||||
if c.Optional {
|
||||
v.Optional(true)
|
||||
}
|
||||
testS(t, v, c.C)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user