Merge branch 'master' into master

pull/635/head
Dean Karn 4 years ago committed by GitHub
commit 8154ee6ca8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .gitignore
  2. 4
      .travis.yml
  3. 7
      README.md
  4. 164
      baked_in.go
  5. 142
      doc.go
  6. 7
      errors.go
  7. 21
      util.go
  8. 2
      validator.go
  9. 6
      validator_instance.go
  10. 1901
      validator_test.go

1
.gitignore vendored

@ -6,6 +6,7 @@
# Folders
_obj
_test
bin
# Architecture specific extensions/prefixes
*.[568vq]

@ -1,6 +1,6 @@
language: go
go:
- 1.13.7
- 1.15.2
- tip
matrix:
allow_failures:
@ -25,5 +25,5 @@ script:
- go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./...
after_success: |
[ $TRAVIS_GO_VERSION = 1.13.7 ] &&
[ $TRAVIS_GO_VERSION = 1.15.2 ] &&
goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKEN

@ -129,6 +129,7 @@ Baked-in Validations
| contains | Contains |
| containsany | Contains Any |
| containsrune | Contains Rune |
| endswith | Ends With |
| lowercase | Lowercase |
| multibyte | Multi-Byte Characters |
| number | NOT DOCUMENTED IN doc.go |
@ -197,10 +198,16 @@ Baked-in Validations
| min | Minimum |
| oneof | One Of |
| required | Required |
| required_if | Required If |
| required_unless | Required Unless |
| required_with | Required With |
| required_with_all | Required With All |
| required_without | Required Without |
| required_without_all | Required Without All |
| excluded_with | Excluded With |
| excluded_with_all | Excluded With All |
| excluded_without | Excluded Without |
| excluded_without_all | Excluded Without All |
| unique | Unique |
Benchmarks

@ -64,10 +64,16 @@ var (
// or even disregard and use your own map if so desired.
bakedInValidators = map[string]Func{
"required": hasValue,
"required_if": requiredIf,
"required_unless": requiredUnless,
"required_with": requiredWith,
"required_with_all": requiredWithAll,
"required_without": requiredWithout,
"required_without_all": requiredWithoutAll,
"excluded_with": excludedWith,
"excluded_with_all": excludedWithAll,
"excluded_without": excludedWithout,
"excluded_without_all": excludedWithoutAll,
"isdefault": isDefault,
"len": hasLengthOf,
"min": hasMinOf,
@ -174,6 +180,7 @@ var (
"lowercase": isLowercase,
"uppercase": isUppercase,
"datetime": isDatetime,
"timezone": isTimeZone,
}
)
@ -241,23 +248,33 @@ func isUnique(fl FieldLevel) bool {
switch field.Kind() {
case reflect.Slice, reflect.Array:
elem := field.Type().Elem()
if elem.Kind() == reflect.Ptr {
elem = elem.Elem()
}
if param == "" {
m := reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i), v)
m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
}
return field.Len() == m.Len()
}
sf, ok := field.Type().Elem().FieldByName(param)
sf, ok := elem.FieldByName(param)
if !ok {
panic(fmt.Sprintf("Bad field name %s", param))
}
m := reflect.MakeMap(reflect.MapOf(sf.Type, v.Type()))
sfTyp := sf.Type
if sfTyp.Kind() == reflect.Ptr {
sfTyp = sfTyp.Elem()
}
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
for i := 0; i < field.Len(); i++ {
m.SetMapIndex(field.Index(i).FieldByName(param), v)
m.SetMapIndex(reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param)), v)
}
return field.Len() == m.Len()
case reflect.Map:
@ -1129,7 +1146,7 @@ func isEq(fl FieldLevel) bool {
return int64(field.Len()) == p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() == p
@ -1383,6 +1400,75 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
}
}
// requireCheckFieldValue is a func for check field value
func requireCheckFieldValue(fl FieldLevel, param string, value string, defaultNotFoundValue bool) bool {
field, kind, _, found := fl.GetStructFieldOKAdvanced2(fl.Parent(), param)
if !found {
return defaultNotFoundValue
}
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() == asInt(value)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return field.Uint() == asUint(value)
case reflect.Float32, reflect.Float64:
return field.Float() == asFloat(value)
case reflect.Slice, reflect.Map, reflect.Array:
return int64(field.Len()) == asInt(value)
}
// default reflect.String:
return field.String() == value
}
// requiredIf is the validation function
// The field under validation must be present and not empty only if all the other specified fields are equal to the value following with the specified field.
func requiredIf(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for required_if %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if !requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true
}
}
return hasValue(fl)
}
// requiredUnless is the validation function
// The field under validation must be present and not empty only unless all the other specified fields are equal to the value following with the specified field.
func requiredUnless(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
if len(params)%2 != 0 {
panic(fmt.Sprintf("Bad param number for required_unless %s", fl.FieldName()))
}
for i := 0; i < len(params); i += 2 {
if requireCheckFieldValue(fl, params[i], params[i+1], false) {
return true
}
}
return hasValue(fl)
}
// ExcludedWith is the validation function
// The field under validation must not be present or is empty if any of the other specified fields are present.
func excludedWith(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if !requireCheckFieldKind(fl, param, true) {
return !hasValue(fl)
}
}
return true
}
// RequiredWith is the validation function
// The field under validation must be present and not empty only if any of the other specified fields are present.
func requiredWith(fl FieldLevel) bool {
@ -1395,6 +1481,18 @@ func requiredWith(fl FieldLevel) bool {
return true
}
// ExcludedWithAll is the validation function
// The field under validation must not be present or is empty if all of the other specified fields are present.
func excludedWithAll(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if requireCheckFieldKind(fl, param, true) {
return true
}
}
return !hasValue(fl)
}
// RequiredWithAll is the validation function
// The field under validation must be present and not empty only if all of the other specified fields are present.
func requiredWithAll(fl FieldLevel) bool {
@ -1407,6 +1505,15 @@ func requiredWithAll(fl FieldLevel) bool {
return hasValue(fl)
}
// ExcludedWithout is the validation function
// The field under validation must not be present or is empty when any of the other specified fields are not present.
func excludedWithout(fl FieldLevel) bool {
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) {
return !hasValue(fl)
}
return true
}
// RequiredWithout is the validation function
// The field under validation must be present and not empty only when any of the other specified fields are not present.
func requiredWithout(fl FieldLevel) bool {
@ -1416,6 +1523,18 @@ func requiredWithout(fl FieldLevel) bool {
return true
}
// RequiredWithoutAll is the validation function
// The field under validation must not be present or is empty when all of the other specified fields are not present.
func excludedWithoutAll(fl FieldLevel) bool {
params := parseOneOfParam2(fl.Param())
for _, param := range params {
if !requireCheckFieldKind(fl, param, true) {
return true
}
}
return !hasValue(fl)
}
// RequiredWithoutAll is the validation function
// The field under validation must be present and not empty only when all of the other specified fields are not present.
func requiredWithoutAll(fl FieldLevel) bool {
@ -1541,7 +1660,7 @@ func isGte(fl FieldLevel) bool {
return int64(field.Len()) >= p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() >= p
@ -1588,7 +1707,7 @@ func isGt(fl FieldLevel) bool {
return int64(field.Len()) > p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() > p
@ -1631,7 +1750,7 @@ func hasLengthOf(fl FieldLevel) bool {
return int64(field.Len()) == p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() == p
@ -1767,7 +1886,7 @@ func isLte(fl FieldLevel) bool {
return int64(field.Len()) <= p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() <= p
@ -1814,7 +1933,7 @@ func isLt(fl FieldLevel) bool {
return int64(field.Len()) < p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p := asInt(param)
p := asIntFromType(field.Type(), param)
return field.Int() < p
@ -2087,6 +2206,29 @@ func isDatetime(fl FieldLevel) bool {
if field.Kind() == reflect.String {
_, err := time.Parse(param, field.String())
return err == nil
}
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
// isTimeZone is the validation function for validating if the current field's value is a valid time zone string.
func isTimeZone(fl FieldLevel) bool {
field := fl.Field()
if field.Kind() == reflect.String {
// empty value is converted to UTC by time.LoadLocation but disallow it as it is not a valid time zone name
if field.String() == "" {
return false
}
// Local value is converted to the current system time zone by time.LoadLocation but disallow it as it is not a valid time zone name
if strings.ToLower(field.String()) == "local" {
return false
}
_, err := time.LoadLocation(field.String())
if err != nil {
return false
}

142
doc.go

@ -245,6 +245,40 @@ ensures the value is not nil.
Usage: required
Required If
The field under validation must be present and not empty only if all
the other specified fields are equal to the value following the specified
field. For strings ensures value is not "". For slices, maps, pointers,
interfaces, channels and functions ensures the value is not nil.
Usage: required_if
Examples:
// require the field if the Field1 is equal to the parameter given:
Usage: required_if=Field1 foobar
// require the field if the Field1 and Field2 is equal to the value respectively:
Usage: required_if=Field1 foo Field2 bar
Required Unless
The field under validation must be present and not empty unless all
the other specified fields are equal to the value following the specified
field. For strings ensures value is not "". For slices, maps, pointers,
interfaces, channels and functions ensures the value is not nil.
Usage: required_unless
Examples:
// require the field unless the Field1 is equal to the parameter given:
Usage: required_unless=Field1 foobar
// require the field unless the Field1 and Field2 is equal to the value respectively:
Usage: required_unless=Field1 foo Field2 bar
Required With
The field under validation must be present and not empty only if any
@ -321,8 +355,17 @@ equal to the parameter given. For strings, it checks that
the string length is exactly that number of characters. For slices,
arrays, and maps, validates the number of items.
Example #1
Usage: len=10
Example #2 (time.Duration)
For time.Duration, len will ensure that the value is equal to the duration given
in the parameter.
Usage: len=1h30m
Maximum
For numbers, max will ensure that the value is
@ -330,8 +373,17 @@ less than or equal to the parameter given. For strings, it checks
that the string length is at most that number of characters. For
slices, arrays, and maps, validates the number of items.
Example #1
Usage: max=10
Example #2 (time.Duration)
For time.Duration, max will ensure that the value is less than or equal to the
duration given in the parameter.
Usage: max=1h30m
Minimum
For numbers, min will ensure that the value is
@ -339,24 +391,51 @@ greater or equal to the parameter given. For strings, it checks that
the string length is at least that number of characters. For slices,
arrays, and maps, validates the number of items.
Example #1
Usage: min=10
Example #2 (time.Duration)
For time.Duration, min will ensure that the value is greater than or equal to
the duration given in the parameter.
Usage: min=1h30m
Equals
For strings & numbers, eq will ensure that the value is
equal to the parameter given. For slices, arrays, and maps,
validates the number of items.
Example #1
Usage: eq=10
Example #2 (time.Duration)
For time.Duration, eq will ensure that the value is equal to the duration given
in the parameter.
Usage: eq=1h30m
Not Equal
For strings & numbers, ne will ensure that the value is not
equal to the parameter given. For slices, arrays, and maps,
validates the number of items.
Example #1
Usage: ne=10
Example #2 (time.Duration)
For time.Duration, ne will ensure that the value is not equal to the duration
given in the parameter.
Usage: ne=1h30m
One Of
For strings, ints, and uints, oneof will ensure that the value
@ -386,11 +465,17 @@ For time.Time ensures the time value is greater than time.Now.UTC().
Usage: gt
Example #3 (time.Duration)
For time.Duration, gt will ensure that the value is greater than the duration
given in the parameter.
Usage: gt=1h30m
Greater Than or Equal
Same as 'min' above. Kept both to make terminology with 'len' easier.
Example #1
Usage: gte=10
@ -401,6 +486,13 @@ For time.Time ensures the time value is greater than or equal to time.Now.UTC().
Usage: gte
Example #3 (time.Duration)
For time.Duration, gte will ensure that the value is greater than or equal to
the duration given in the parameter.
Usage: gte=1h30m
Less Than
For numbers, this will ensure that the value is less than the parameter given.
@ -412,10 +504,18 @@ Example #1
Usage: lt=10
Example #2 (time.Time)
For time.Time ensures the time value is less than time.Now.UTC().
Usage: lt
Example #3 (time.Duration)
For time.Duration, lt will ensure that the value is less than the duration given
in the parameter.
Usage: lt=1h30m
Less Than or Equal
Same as 'max' above. Kept both to make terminology with 'len' easier.
@ -430,6 +530,13 @@ For time.Time ensures the time value is less than or equal to time.Now.UTC().
Usage: lte
Example #3 (time.Duration)
For time.Duration, lte will ensure that the value is less than or equal to the
duration given in the parameter.
Usage: lte=1h30m
Field Equals Another Field
This will validate the field value against another fields value either within
@ -476,9 +583,9 @@ relative to the top level struct.
Field Greater Than Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Only valid for Numbers, time.Duration and time.Time types, this will validate
the field value against another fields value either within a struct or passed in
field. usage examples are for validation of a Start and End date:
Example #1:
@ -490,7 +597,6 @@ Example #2:
// Validating by field:
validate.VarWithValue(start, end, "gtfield")
Field Greater Than Another Relative Field
This does the same as gtfield except that it validates the field provided
@ -500,9 +606,9 @@ relative to the top level struct.
Field Greater Than or Equal To Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Only valid for Numbers, time.Duration and time.Time types, this will validate
the field value against another fields value either within a struct or passed in
field. usage examples are for validation of a Start and End date:
Example #1:
@ -523,9 +629,9 @@ to the top level struct.
Less Than Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Only valid for Numbers, time.Duration and time.Time types, this will validate
the field value against another fields value either within a struct or passed in
field. usage examples are for validation of a Start and End date:
Example #1:
@ -546,9 +652,9 @@ to the top level struct.
Less Than or Equal To Another Field
Only valid for Numbers and time.Time types, this will validate the field value
against another fields value either within a struct or passed in field.
usage examples are for validation of a Start and End date:
Only valid for Numbers, time.Duration and time.Time types, this will validate
the field value against another fields value either within a struct or passed in
field. usage examples are for validation of a Start and End date:
Example #1:
@ -1095,6 +1201,14 @@ Supplied format must match the official Go time format layout as documented in h
Usage: datetime=2006-01-02
TimeZone
This validates that a string value is a valid time zone based on the time zone database present on the system.
Although empty value and Local value are allowed by time.LoadLocation golang function, they are not allowed by this validator.
More information on https://golang.org/pkg/time/#LoadLocation
Usage: timezone
Alias Validators and Tags
NOTE: When returning an error, the tag returned in "FieldError" will be

@ -155,6 +155,9 @@ type FieldError interface {
// NOTE: if no registered translator can be found it returns the same as
// calling fe.Error()
Translate(ut ut.Translator) string
// Error returns the FieldError's message
Error() string
}
// compile time interface checks
@ -254,8 +257,8 @@ func (fe *fieldError) Error() string {
// Translate returns the FieldError's translated error
// from the provided 'ut.Translator' and registered 'TranslationFunc'
//
// NOTE: is not registered translation can be found it returns the same
// as calling fe.Error()
// NOTE: if no registered translation can be found, it returns the original
// untranslated error message.
func (fe *fieldError) Translate(ut ut.Translator) string {
m, ok := fe.v.transTagFunc[ut]

@ -4,6 +4,7 @@ import (
"reflect"
"strconv"
"strings"
"time"
)
// extractTypeInternal gets the actual underlying type of field value.
@ -229,6 +230,26 @@ func asInt(param string) int64 {
return i
}
// asIntFromTimeDuration parses param as time.Duration and returns it as int64
// or panics on error.
func asIntFromTimeDuration(param string) int64 {
d, err := time.ParseDuration(param)
panicIf(err)
return int64(d)
}
// asIntFromType calls the proper function to parse param as int64,
// given a field's Type t.
func asIntFromType(t reflect.Type, param string) int64 {
switch t {
case timeDurationType:
return asIntFromTimeDuration(param)
default:
return asInt(param)
}
}
// asUint returns the parameter as a uint64
// or panics if it can't convert
func asUint(param string) uint64 {

@ -249,7 +249,7 @@ OUTER:
v.cf = cf
v.ct = ct
if !v.fldIsPointer && !hasValue(v) {
if !hasValue(v) {
return
}

@ -27,6 +27,8 @@ const (
requiredWithoutTag = "required_without"
requiredWithTag = "required_with"
requiredWithAllTag = "required_with_all"
requiredIfTag = "required_if"
requiredUnlessTag = "required_unless"
skipValidationTag = "-"
diveTag = "dive"
keysTag = "keys"
@ -41,7 +43,9 @@ const (
)
var (
timeDurationType = reflect.TypeOf(time.Duration(0))
timeType = reflect.TypeOf(time.Time{})
defaultCField = &cField{namesEqual: true}
)
@ -107,7 +111,7 @@ func New() *Validate {
switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
case requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag:
_ = v.registerValidation(k, wrapFunc(val), true, true)
default:
// no need to error check here, baked in will always be valid

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save