| /* |
| Package validation provides methods for validating parameter value using reflection. |
| */ |
| package validation |
| |
| // Copyright 2017 Microsoft Corporation |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| import ( |
| "fmt" |
| "reflect" |
| "regexp" |
| "strings" |
| ) |
| |
| // Constraint stores constraint name, target field name |
| // Rule and chain validations. |
| type Constraint struct { |
| |
| // Target field name for validation. |
| Target string |
| |
| // Constraint name e.g. minLength, MaxLength, Pattern, etc. |
| Name string |
| |
| // Rule for constraint e.g. greater than 10, less than 5 etc. |
| Rule interface{} |
| |
| // Chain Validations for struct type |
| Chain []Constraint |
| } |
| |
| // Validation stores parameter-wise validation. |
| type Validation struct { |
| TargetValue interface{} |
| Constraints []Constraint |
| } |
| |
| // Constraint list |
| const ( |
| Empty = "Empty" |
| Null = "Null" |
| ReadOnly = "ReadOnly" |
| Pattern = "Pattern" |
| MaxLength = "MaxLength" |
| MinLength = "MinLength" |
| MaxItems = "MaxItems" |
| MinItems = "MinItems" |
| MultipleOf = "MultipleOf" |
| UniqueItems = "UniqueItems" |
| InclusiveMaximum = "InclusiveMaximum" |
| ExclusiveMaximum = "ExclusiveMaximum" |
| ExclusiveMinimum = "ExclusiveMinimum" |
| InclusiveMinimum = "InclusiveMinimum" |
| ) |
| |
| // Validate method validates constraints on parameter |
| // passed in validation array. |
| func Validate(m []Validation) error { |
| for _, item := range m { |
| v := reflect.ValueOf(item.TargetValue) |
| for _, constraint := range item.Constraints { |
| var err error |
| switch v.Kind() { |
| case reflect.Ptr: |
| err = validatePtr(v, constraint) |
| case reflect.String: |
| err = validateString(v, constraint) |
| case reflect.Struct: |
| err = validateStruct(v, constraint) |
| case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
| err = validateInt(v, constraint) |
| case reflect.Float32, reflect.Float64: |
| err = validateFloat(v, constraint) |
| case reflect.Array, reflect.Slice, reflect.Map: |
| err = validateArrayMap(v, constraint) |
| default: |
| err = createError(v, constraint, fmt.Sprintf("unknown type %v", v.Kind())) |
| } |
| |
| if err != nil { |
| return err |
| } |
| } |
| } |
| return nil |
| } |
| |
| func validateStruct(x reflect.Value, v Constraint, name ...string) error { |
| //Get field name from target name which is in format a.b.c |
| s := strings.Split(v.Target, ".") |
| f := x.FieldByName(s[len(s)-1]) |
| if isZero(f) { |
| return createError(x, v, fmt.Sprintf("field %q doesn't exist", v.Target)) |
| } |
| |
| return Validate([]Validation{ |
| { |
| TargetValue: getInterfaceValue(f), |
| Constraints: []Constraint{v}, |
| }, |
| }) |
| } |
| |
| func validatePtr(x reflect.Value, v Constraint) error { |
| if v.Name == ReadOnly { |
| if !x.IsNil() { |
| return createError(x.Elem(), v, "readonly parameter; must send as nil or empty in request") |
| } |
| return nil |
| } |
| if x.IsNil() { |
| return checkNil(x, v) |
| } |
| if v.Chain != nil { |
| return Validate([]Validation{ |
| { |
| TargetValue: getInterfaceValue(x.Elem()), |
| Constraints: v.Chain, |
| }, |
| }) |
| } |
| return nil |
| } |
| |
| func validateInt(x reflect.Value, v Constraint) error { |
| i := x.Int() |
| r, ok := toInt64(v.Rule) |
| if !ok { |
| return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| switch v.Name { |
| case MultipleOf: |
| if i%r != 0 { |
| return createError(x, v, fmt.Sprintf("value must be a multiple of %v", r)) |
| } |
| case ExclusiveMinimum: |
| if i <= r { |
| return createError(x, v, fmt.Sprintf("value must be greater than %v", r)) |
| } |
| case ExclusiveMaximum: |
| if i >= r { |
| return createError(x, v, fmt.Sprintf("value must be less than %v", r)) |
| } |
| case InclusiveMinimum: |
| if i < r { |
| return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r)) |
| } |
| case InclusiveMaximum: |
| if i > r { |
| return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r)) |
| } |
| default: |
| return createError(x, v, fmt.Sprintf("constraint %v is not applicable for type integer", v.Name)) |
| } |
| return nil |
| } |
| |
| func validateFloat(x reflect.Value, v Constraint) error { |
| f := x.Float() |
| r, ok := v.Rule.(float64) |
| if !ok { |
| return createError(x, v, fmt.Sprintf("rule must be float value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| switch v.Name { |
| case ExclusiveMinimum: |
| if f <= r { |
| return createError(x, v, fmt.Sprintf("value must be greater than %v", r)) |
| } |
| case ExclusiveMaximum: |
| if f >= r { |
| return createError(x, v, fmt.Sprintf("value must be less than %v", r)) |
| } |
| case InclusiveMinimum: |
| if f < r { |
| return createError(x, v, fmt.Sprintf("value must be greater than or equal to %v", r)) |
| } |
| case InclusiveMaximum: |
| if f > r { |
| return createError(x, v, fmt.Sprintf("value must be less than or equal to %v", r)) |
| } |
| default: |
| return createError(x, v, fmt.Sprintf("constraint %s is not applicable for type float", v.Name)) |
| } |
| return nil |
| } |
| |
| func validateString(x reflect.Value, v Constraint) error { |
| s := x.String() |
| switch v.Name { |
| case Empty: |
| if len(s) == 0 { |
| return checkEmpty(x, v) |
| } |
| case Pattern: |
| reg, err := regexp.Compile(v.Rule.(string)) |
| if err != nil { |
| return createError(x, v, err.Error()) |
| } |
| if !reg.MatchString(s) { |
| return createError(x, v, fmt.Sprintf("value doesn't match pattern %v", v.Rule)) |
| } |
| case MaxLength: |
| if _, ok := v.Rule.(int); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| if len(s) > v.Rule.(int) { |
| return createError(x, v, fmt.Sprintf("value length must be less than or equal to %v", v.Rule)) |
| } |
| case MinLength: |
| if _, ok := v.Rule.(int); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be integer value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| if len(s) < v.Rule.(int) { |
| return createError(x, v, fmt.Sprintf("value length must be greater than or equal to %v", v.Rule)) |
| } |
| case ReadOnly: |
| if len(s) > 0 { |
| return createError(reflect.ValueOf(s), v, "readonly parameter; must send as nil or empty in request") |
| } |
| default: |
| return createError(x, v, fmt.Sprintf("constraint %s is not applicable to string type", v.Name)) |
| } |
| |
| if v.Chain != nil { |
| return Validate([]Validation{ |
| { |
| TargetValue: getInterfaceValue(x), |
| Constraints: v.Chain, |
| }, |
| }) |
| } |
| return nil |
| } |
| |
| func validateArrayMap(x reflect.Value, v Constraint) error { |
| switch v.Name { |
| case Null: |
| if x.IsNil() { |
| return checkNil(x, v) |
| } |
| case Empty: |
| if x.IsNil() || x.Len() == 0 { |
| return checkEmpty(x, v) |
| } |
| case MaxItems: |
| if _, ok := v.Rule.(int); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| if x.Len() > v.Rule.(int) { |
| return createError(x, v, fmt.Sprintf("maximum item limit is %v; got: %v", v.Rule, x.Len())) |
| } |
| case MinItems: |
| if _, ok := v.Rule.(int); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be integer for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| if x.Len() < v.Rule.(int) { |
| return createError(x, v, fmt.Sprintf("minimum item limit is %v; got: %v", v.Rule, x.Len())) |
| } |
| case UniqueItems: |
| if x.Kind() == reflect.Array || x.Kind() == reflect.Slice { |
| if !checkForUniqueInArray(x) { |
| return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.Target, x)) |
| } |
| } else if x.Kind() == reflect.Map { |
| if !checkForUniqueInMap(x) { |
| return createError(x, v, fmt.Sprintf("all items in parameter %q must be unique; got:%v", v.Target, x)) |
| } |
| } else { |
| return createError(x, v, fmt.Sprintf("type must be array, slice or map for constraint %v; got: %v", v.Name, x.Kind())) |
| } |
| case ReadOnly: |
| if x.Len() != 0 { |
| return createError(x, v, "readonly parameter; must send as nil or empty in request") |
| } |
| case Pattern: |
| reg, err := regexp.Compile(v.Rule.(string)) |
| if err != nil { |
| return createError(x, v, err.Error()) |
| } |
| keys := x.MapKeys() |
| for _, k := range keys { |
| if !reg.MatchString(k.String()) { |
| return createError(k, v, fmt.Sprintf("map key doesn't match pattern %v", v.Rule)) |
| } |
| } |
| default: |
| return createError(x, v, fmt.Sprintf("constraint %v is not applicable to array, slice and map type", v.Name)) |
| } |
| |
| if v.Chain != nil { |
| return Validate([]Validation{ |
| { |
| TargetValue: getInterfaceValue(x), |
| Constraints: v.Chain, |
| }, |
| }) |
| } |
| return nil |
| } |
| |
| func checkNil(x reflect.Value, v Constraint) error { |
| if _, ok := v.Rule.(bool); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| if v.Rule.(bool) { |
| return createError(x, v, "value can not be null; required parameter") |
| } |
| return nil |
| } |
| |
| func checkEmpty(x reflect.Value, v Constraint) error { |
| if _, ok := v.Rule.(bool); !ok { |
| return createError(x, v, fmt.Sprintf("rule must be bool value for %v constraint; got: %v", v.Name, v.Rule)) |
| } |
| |
| if v.Rule.(bool) { |
| return createError(x, v, "value can not be null or empty; required parameter") |
| } |
| return nil |
| } |
| |
| func checkForUniqueInArray(x reflect.Value) bool { |
| if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 { |
| return false |
| } |
| arrOfInterface := make([]interface{}, x.Len()) |
| |
| for i := 0; i < x.Len(); i++ { |
| arrOfInterface[i] = x.Index(i).Interface() |
| } |
| |
| m := make(map[interface{}]bool) |
| for _, val := range arrOfInterface { |
| if m[val] { |
| return false |
| } |
| m[val] = true |
| } |
| return true |
| } |
| |
| func checkForUniqueInMap(x reflect.Value) bool { |
| if x == reflect.Zero(reflect.TypeOf(x)) || x.Len() == 0 { |
| return false |
| } |
| mapOfInterface := make(map[interface{}]interface{}, x.Len()) |
| |
| keys := x.MapKeys() |
| for _, k := range keys { |
| mapOfInterface[k.Interface()] = x.MapIndex(k).Interface() |
| } |
| |
| m := make(map[interface{}]bool) |
| for _, val := range mapOfInterface { |
| if m[val] { |
| return false |
| } |
| m[val] = true |
| } |
| return true |
| } |
| |
| func getInterfaceValue(x reflect.Value) interface{} { |
| if x.Kind() == reflect.Invalid { |
| return nil |
| } |
| return x.Interface() |
| } |
| |
| func isZero(x interface{}) bool { |
| return x == reflect.Zero(reflect.TypeOf(x)).Interface() |
| } |
| |
| func createError(x reflect.Value, v Constraint, err string) error { |
| return fmt.Errorf("autorest/validation: validation failed: parameter=%s constraint=%s value=%#v details: %s", |
| v.Target, v.Name, getInterfaceValue(x), err) |
| } |
| |
| func toInt64(v interface{}) (int64, bool) { |
| if i64, ok := v.(int64); ok { |
| return i64, true |
| } |
| // older generators emit max constants as int, so if int64 fails fall back to int |
| if i32, ok := v.(int); ok { |
| return int64(i32), true |
| } |
| return 0, false |
| } |
| |
| // NewErrorWithValidationError appends package type and method name in |
| // validation error. |
| // |
| // Deprecated: Please use validation.NewError() instead. |
| func NewErrorWithValidationError(err error, packageType, method string) error { |
| return NewError(packageType, method, err.Error()) |
| } |