Merge pull request #1446 from crazy-max/dependabot/go_modules/github.com/go-playground/validator/v10-10.27.0

chore(deps): bump github.com/go-playground/validator/v10 from 10.26.0 to 10.27.0
This commit is contained in:
CrazyMax
2025-08-03 16:33:51 +02:00
committed by GitHub
13 changed files with 209 additions and 254 deletions

2
go.mod
View File

@@ -20,7 +20,7 @@ require (
github.com/dromara/carbon/v2 v2.6.11 github.com/dromara/carbon/v2 v2.6.11
github.com/eclipse/paho.mqtt.golang v1.5.0 github.com/eclipse/paho.mqtt.golang v1.5.0
github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df
github.com/go-playground/validator/v10 v10.26.0 github.com/go-playground/validator/v10 v10.27.0
github.com/gregdel/pushover v1.3.1 github.com/gregdel/pushover v1.3.1
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // v1.7.2 github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // v1.7.2
github.com/jedib0t/go-pretty/v6 v6.6.7 github.com/jedib0t/go-pretty/v6 v6.6.7

4
go.sum
View File

@@ -130,8 +130,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=

View File

@@ -2,101 +2,53 @@ version: "2"
linters: linters:
default: all default: all
disable: disable:
- asasalint - noinlineerr
- asciicheck - wsl_v5
- bidichk
- bodyclose
- canonicalheader
- containedctx
- contextcheck
- copyloopvar - copyloopvar
- cyclop - cyclop
- decorder
- depguard - depguard
- dogsled - dogsled
- dupl - dupl
- dupword - dupword
- durationcheck
- err113 - err113
- errcheck
- errchkjson
- errname
- errorlint - errorlint
- exhaustive - exhaustive
- exhaustruct - exhaustruct
- exptostd
- fatcontext
- forbidigo - forbidigo
- forcetypeassert - forcetypeassert
- funlen - funlen
- ginkgolinter
- gocheckcompilerdirectives
- gochecknoglobals - gochecknoglobals
- gochecknoinits
- gochecksumtype
- gocognit - gocognit
- goconst - goconst
- gocritic - gocritic
- gocyclo - gocyclo
- godot - godot
- godox
- goheader
- gomoddirectives
- gomodguard
- goprintffuncname
- gosec - gosec
- gosmopolitan - gosmopolitan
- govet
- grouper
- iface
- importas
- inamedparam
- ineffassign
- interfacebloat - interfacebloat
- intrange - intrange
- ireturn - ireturn
- lll - lll
- loggercheck
- maintidx - maintidx
- makezero
- mirror
- misspell - misspell
- mnd - mnd
- musttag
- nakedret - nakedret
- nestif - nestif
- nilerr
- nilnesserr
- nilnil - nilnil
- nlreturn - nlreturn
- noctx
- nolintlint
- nonamedreturns - nonamedreturns
- nosprintfhostport
- paralleltest - paralleltest
- perfsprint - perfsprint
- prealloc - prealloc
- predeclared
- promlinter
- protogetter
- reassign
- recvcheck - recvcheck
- revive - revive
- rowserrcheck
- sloglint
- spancheck
- sqlclosecheck
- staticcheck - staticcheck
- tagalign - tagalign
- tagliatelle - tagliatelle
- testableexamples
- testifylint
- testpackage - testpackage
- thelper - thelper
- tparallel - tparallel
- unparam - unparam
- varnamelen - varnamelen
- whitespace
- wrapcheck - wrapcheck
- wsl - wsl
- zerologlint

View File

@@ -1,6 +1,6 @@
Package validator Package validator
================= =================
<img align="right" src="logo.png">![Project status](https://img.shields.io/badge/version-10.25.0-green.svg) <img align="right" src="logo.png">[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/go-playground/validator)](https://github.com/go-playground/validator/releases)
[![Build Status](https://github.com/go-playground/validator/actions/workflows/workflow.yml/badge.svg)](https://github.com/go-playground/validator/actions) [![Build Status](https://github.com/go-playground/validator/actions/workflows/workflow.yml/badge.svg)](https://github.com/go-playground/validator/actions)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master) [![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=master&service=github)](https://coveralls.io/github/go-playground/validator?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
@@ -262,6 +262,8 @@ validate := validator.New(validator.WithRequiredStructEnabled())
| excluded_without | Excluded Without | | excluded_without | Excluded Without |
| excluded_without_all | Excluded Without All | | excluded_without_all | Excluded Without All |
| unique | Unique | | unique | Unique |
| validateFn | Verify if the method `Validate() error` does not return an error (or any specified method) |
#### Aliases: #### Aliases:
| Tag | Description | | Tag | Description |

View File

@@ -2,10 +2,12 @@ package validator
import ( import (
"bytes" "bytes"
"cmp"
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/fs" "io/fs"
"net" "net"
@@ -244,6 +246,7 @@ var (
"cron": isCron, "cron": isCron,
"spicedb": isSpiceDB, "spicedb": isSpiceDB,
"ein": isEIN, "ein": isEIN,
"validateFn": isValidateFn,
} }
) )
@@ -294,7 +297,7 @@ func isOneOf(fl FieldLevel) bool {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v = strconv.FormatUint(field.Uint(), 10) v = strconv.FormatUint(field.Uint(), 10)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
for i := 0; i < len(vals); i++ { for i := 0; i < len(vals); i++ {
if vals[i] == v { if vals[i] == v {
@@ -310,7 +313,7 @@ func isOneOfCI(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
if field.Kind() != reflect.String { if field.Kind() != reflect.String {
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
v := field.String() v := field.String()
for _, val := range vals { for _, val := range vals {
@@ -384,13 +387,13 @@ func isUnique(fl FieldLevel) bool {
} }
if uniqueField.Kind() != field.Kind() { if uniqueField.Kind() != field.Kind() {
panic(fmt.Sprintf("Bad field type %T:%T", field.Interface(), uniqueField.Interface())) panic(fmt.Sprintf("Bad field type %s:%s", field.Type(), uniqueField.Type()))
} }
return field.Interface() != uniqueField.Interface() return getValue(field) != getValue(uniqueField)
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
} }
@@ -471,7 +474,7 @@ func isLongitude(fl FieldLevel) bool {
case reflect.Float64: case reflect.Float64:
v = strconv.FormatFloat(field.Float(), 'f', -1, 64) v = strconv.FormatFloat(field.Float(), 'f', -1, 64)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
return longitudeRegex().MatchString(v) return longitudeRegex().MatchString(v)
@@ -494,7 +497,7 @@ func isLatitude(fl FieldLevel) bool {
case reflect.Float64: case reflect.Float64:
v = strconv.FormatFloat(field.Float(), 'f', -1, 64) v = strconv.FormatFloat(field.Float(), 'f', -1, 64)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
return latitudeRegex().MatchString(v) return latitudeRegex().MatchString(v)
@@ -945,7 +948,6 @@ func isNeField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() != currentField.Int() return field.Int() != currentField.Int()
@@ -966,9 +968,8 @@ func isNeField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField).(time.Time)
t := currentField.Interface().(time.Time) fieldTime := getValue(field).(time.Time)
fieldTime := field.Interface().(time.Time)
return !fieldTime.Equal(t) return !fieldTime.Equal(t)
} }
@@ -1005,7 +1006,6 @@ func isLteCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() <= topField.Int() return field.Int() <= topField.Int()
@@ -1023,9 +1023,8 @@ func isLteCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time) topTime := getValue(topField.Convert(timeType)).(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(topTime) || fieldTime.Equal(topTime) return fieldTime.Before(topTime) || fieldTime.Equal(topTime)
} }
@@ -1052,7 +1051,6 @@ func isLtCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() < topField.Int() return field.Int() < topField.Int()
@@ -1070,9 +1068,8 @@ func isLtCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time) topTime := getValue(topField.Convert(timeType)).(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(topTime) return fieldTime.Before(topTime)
} }
@@ -1098,7 +1095,6 @@ func isGteCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() >= topField.Int() return field.Int() >= topField.Int()
@@ -1116,9 +1112,8 @@ func isGteCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time) topTime := getValue(topField.Convert(timeType)).(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.After(topTime) || fieldTime.Equal(topTime) return fieldTime.After(topTime) || fieldTime.Equal(topTime)
} }
@@ -1144,7 +1139,6 @@ func isGtCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() > topField.Int() return field.Int() > topField.Int()
@@ -1162,9 +1156,8 @@ func isGtCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time) topTime := getValue(topField.Convert(timeType)).(time.Time)
topTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.After(topTime) return fieldTime.After(topTime)
} }
@@ -1190,7 +1183,6 @@ func isNeCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return topField.Int() != field.Int() return topField.Int() != field.Int()
@@ -1211,9 +1203,8 @@ func isNeCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
t := getValue(field.Convert(timeType)).(time.Time)
t := field.Convert(timeType).Interface().(time.Time) fieldTime := getValue(topField.Convert(timeType)).(time.Time)
fieldTime := topField.Convert(timeType).Interface().(time.Time)
return !fieldTime.Equal(t) return !fieldTime.Equal(t)
} }
@@ -1239,7 +1230,6 @@ func isEqCrossStructField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return topField.Int() == field.Int() return topField.Int() == field.Int()
@@ -1260,9 +1250,8 @@ func isEqCrossStructField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && topField.Type().ConvertibleTo(timeType) {
t := getValue(field.Convert(timeType)).(time.Time)
t := field.Convert(timeType).Interface().(time.Time) fieldTime := getValue(topField.Convert(timeType)).(time.Time)
fieldTime := topField.Convert(timeType).Interface().(time.Time)
return fieldTime.Equal(t) return fieldTime.Equal(t)
} }
@@ -1288,7 +1277,6 @@ func isEqField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() == currentField.Int() return field.Int() == currentField.Int()
@@ -1309,9 +1297,8 @@ func isEqField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField.Convert(timeType)).(time.Time)
t := currentField.Convert(timeType).Interface().(time.Time) fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.Equal(t) return fieldTime.Equal(t)
} }
@@ -1332,7 +1319,6 @@ func isEq(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
return field.String() == param return field.String() == param
@@ -1367,7 +1353,7 @@ func isEq(fl FieldLevel) bool {
return field.Bool() == p return field.Bool() == p
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isEqIgnoreCase is the validation function for validating if the current field's string value is // isEqIgnoreCase is the validation function for validating if the current field's string value is
@@ -1382,7 +1368,7 @@ func isEqIgnoreCase(fl FieldLevel) bool {
return strings.EqualFold(field.String(), param) return strings.EqualFold(field.String(), param)
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2 // isPostcodeByIso3166Alpha2 validates by value which is country code in iso 3166 alpha 2
@@ -1416,7 +1402,7 @@ func isPostcodeByIso3166Alpha2Field(fl FieldLevel) bool {
} }
if kind != reflect.String { if kind != reflect.String {
panic(fmt.Sprintf("Bad field type %T", currentField.Interface())) panic(fmt.Sprintf("Bad field type %s", currentField.Type()))
} }
postcodeRegexInit.Do(initPostcodes) postcodeRegexInit.Do(initPostcodes)
@@ -1472,16 +1458,7 @@ func isURI(fl FieldLevel) bool {
return err == nil return err == nil
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
}
// isFileURL is the helper function for validating if the `path` valid file URL as per RFC8089
func isFileURL(path string) bool {
if !strings.HasPrefix(path, "file:/") {
return false
}
_, err := url.ParseRequestURI(path)
return err == nil
} }
// isURL is the validation function for validating if the current field's value is a valid URL. // isURL is the validation function for validating if the current field's value is a valid URL.
@@ -1497,23 +1474,20 @@ func isURL(fl FieldLevel) bool {
return false return false
} }
if isFileURL(s) {
return true
}
url, err := url.Parse(s) url, err := url.Parse(s)
if err != nil || url.Scheme == "" { if err != nil || url.Scheme == "" {
return false return false
} }
isFileScheme := url.Scheme == "file"
if url.Host == "" && url.Fragment == "" && url.Opaque == "" { if (isFileScheme && (len(url.Path) == 0 || url.Path == "/")) || (!isFileScheme && len(url.Host) == 0 && len(url.Fragment) == 0 && len(url.Opaque) == 0) {
return false return false
} }
return true return true
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL. // isHttpURL is the validation function for validating if the current field's value is a valid HTTP(s) URL.
@@ -1536,7 +1510,7 @@ func isHttpURL(fl FieldLevel) bool {
return url.Scheme == "http" || url.Scheme == "https" return url.Scheme == "http" || url.Scheme == "https"
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141. // isUrnRFC2141 is the validation function for validating if the current field's value is a valid URN as per RFC 2141.
@@ -1553,7 +1527,7 @@ func isUrnRFC2141(fl FieldLevel) bool {
return match return match
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isFile is the validation function for validating if the current field's value is a valid existing file path. // isFile is the validation function for validating if the current field's value is a valid existing file path.
@@ -1570,7 +1544,7 @@ func isFile(fl FieldLevel) bool {
return !fileInfo.IsDir() return !fileInfo.IsDir()
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isImage is the validation function for validating if the current field's value contains the path to a valid image file // isImage is the validation function for validating if the current field's value contains the path to a valid image file
@@ -1632,7 +1606,8 @@ func isImage(fl FieldLevel) bool {
return true return true
} }
} }
return false
panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isFilePath is the validation function for validating if the current field's value is a valid file path. // isFilePath is the validation function for validating if the current field's value is a valid file path.
@@ -1686,7 +1661,7 @@ func isFilePath(fl FieldLevel) bool {
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number. // isE164 is the validation function for validating if the current field's value is a valid e.164 formatted phone number.
@@ -1796,7 +1771,7 @@ func hasValue(fl FieldLevel) bool {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil() return !field.IsNil()
default: default:
if fl.(*validate).fldIsPointer && field.Interface() != nil { if fl.(*validate).fldIsPointer && getValue(field) != nil {
return true return true
} }
return field.IsValid() && !field.IsZero() return field.IsValid() && !field.IsZero()
@@ -1807,10 +1782,13 @@ func hasValue(fl FieldLevel) bool {
func hasNotZeroValue(fl FieldLevel) bool { func hasNotZeroValue(fl FieldLevel) bool {
field := fl.Field() field := fl.Field()
switch field.Kind() { switch field.Kind() {
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: case reflect.Slice, reflect.Map:
// For slices and maps, consider them "not zero" only if they're both non-nil AND have elements
return !field.IsNil() && field.Len() > 0
case reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return !field.IsNil() return !field.IsNil()
default: default:
if fl.(*validate).fldIsPointer && field.Interface() != nil { if fl.(*validate).fldIsPointer && getValue(field) != nil {
return !field.IsZero() return !field.IsZero()
} }
return field.IsValid() && !field.IsZero() return field.IsValid() && !field.IsZero()
@@ -1834,7 +1812,7 @@ func requireCheckFieldKind(fl FieldLevel, param string, defaultNotFoundValue boo
case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func: case reflect.Slice, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Chan, reflect.Func:
return field.IsNil() return field.IsNil()
default: default:
if nullable && field.Interface() != nil { if nullable && getValue(field) != nil {
return false return false
} }
return field.IsValid() && field.IsZero() return field.IsValid() && field.IsZero()
@@ -1851,7 +1829,6 @@ func requireCheckFieldValue(
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() == asInt(value) return field.Int() == asInt(value)
@@ -1864,7 +1841,13 @@ func requireCheckFieldValue(
case reflect.Float64: case reflect.Float64:
return field.Float() == asFloat64(value) return field.Float() == asFloat64(value)
case reflect.Slice, reflect.Map, reflect.Array: case reflect.Slice, reflect.Map:
if value == "nil" {
return field.IsNil()
}
return int64(field.Len()) == asInt(value)
case reflect.Array:
// Arrays can't be nil, so only compare lengths
return int64(field.Len()) == asInt(value) return int64(field.Len()) == asInt(value)
case reflect.Bool: case reflect.Bool:
@@ -2019,8 +2002,11 @@ func excludedWithout(fl FieldLevel) bool {
// requiredWithout is the validation function // 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. // 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 { func requiredWithout(fl FieldLevel) bool {
if requireCheckFieldKind(fl, strings.TrimSpace(fl.Param()), true) { params := parseOneOfParam2(fl.Param())
return hasValue(fl) for _, param := range params {
if requireCheckFieldKind(fl, param, true) {
return hasValue(fl)
}
} }
return true return true
} }
@@ -2060,7 +2046,6 @@ func isGteField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() >= currentField.Int() return field.Int() >= currentField.Int()
@@ -2078,9 +2063,8 @@ func isGteField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField.Convert(timeType)).(time.Time)
t := currentField.Convert(timeType).Interface().(time.Time) fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.After(t) || fieldTime.Equal(t) return fieldTime.After(t) || fieldTime.Equal(t)
} }
@@ -2106,7 +2090,6 @@ func isGtField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() > currentField.Int() return field.Int() > currentField.Int()
@@ -2124,9 +2107,8 @@ func isGtField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField.Convert(timeType)).(time.Time)
t := currentField.Convert(timeType).Interface().(time.Time) fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.After(t) return fieldTime.After(t)
} }
@@ -2147,7 +2129,6 @@ func isGte(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
p := asInt(param) p := asInt(param)
@@ -2181,15 +2162,14 @@ func isGte(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type().ConvertibleTo(timeType) { if field.Type().ConvertibleTo(timeType) {
now := time.Now().UTC() now := time.Now().UTC()
t := field.Convert(timeType).Interface().(time.Time) t := getValue(field.Convert(timeType)).(time.Time)
return t.After(now) || t.Equal(now) return t.After(now) || t.Equal(now)
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isGt is the validation function for validating if the current field's value is greater than the param's value. // isGt is the validation function for validating if the current field's value is greater than the param's value.
@@ -2198,7 +2178,6 @@ func isGt(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
p := asInt(param) p := asInt(param)
@@ -2232,11 +2211,11 @@ func isGt(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type().ConvertibleTo(timeType) { if field.Type().ConvertibleTo(timeType) {
return field.Convert(timeType).Interface().(time.Time).After(time.Now().UTC()) return getValue(field.Convert(timeType)).(time.Time).After(time.Now().UTC())
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// hasLengthOf is the validation function for validating if the current field's value is equal to the param's value. // hasLengthOf is the validation function for validating if the current field's value is equal to the param's value.
@@ -2245,7 +2224,6 @@ func hasLengthOf(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
p := asInt(param) p := asInt(param)
@@ -2277,7 +2255,7 @@ func hasLengthOf(fl FieldLevel) bool {
return field.Float() == p return field.Float() == p
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// hasMinOf is the validation function for validating if the current field's value is greater than or equal to the param's value. // hasMinOf is the validation function for validating if the current field's value is greater than or equal to the param's value.
@@ -2296,7 +2274,6 @@ func isLteField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() <= currentField.Int() return field.Int() <= currentField.Int()
@@ -2314,9 +2291,8 @@ func isLteField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField.Convert(timeType)).(time.Time)
t := currentField.Convert(timeType).Interface().(time.Time) fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(t) || fieldTime.Equal(t) return fieldTime.Before(t) || fieldTime.Equal(t)
} }
@@ -2342,7 +2318,6 @@ func isLtField(fl FieldLevel) bool {
} }
switch kind { switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return field.Int() < currentField.Int() return field.Int() < currentField.Int()
@@ -2360,9 +2335,8 @@ func isLtField(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) { if fieldType.ConvertibleTo(timeType) && currentField.Type().ConvertibleTo(timeType) {
t := getValue(currentField.Convert(timeType)).(time.Time)
t := currentField.Convert(timeType).Interface().(time.Time) fieldTime := getValue(field.Convert(timeType)).(time.Time)
fieldTime := field.Convert(timeType).Interface().(time.Time)
return fieldTime.Before(t) return fieldTime.Before(t)
} }
@@ -2383,7 +2357,6 @@ func isLte(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
p := asInt(param) p := asInt(param)
@@ -2417,15 +2390,14 @@ func isLte(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type().ConvertibleTo(timeType) { if field.Type().ConvertibleTo(timeType) {
now := time.Now().UTC() now := time.Now().UTC()
t := field.Convert(timeType).Interface().(time.Time) t := getValue(field.Convert(timeType)).(time.Time)
return t.Before(now) || t.Equal(now) return t.Before(now) || t.Equal(now)
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isLt is the validation function for validating if the current field's value is less than the param's value. // isLt is the validation function for validating if the current field's value is less than the param's value.
@@ -2434,7 +2406,6 @@ func isLt(fl FieldLevel) bool {
param := fl.Param() param := fl.Param()
switch field.Kind() { switch field.Kind() {
case reflect.String: case reflect.String:
p := asInt(param) p := asInt(param)
@@ -2468,11 +2439,11 @@ func isLt(fl FieldLevel) bool {
case reflect.Struct: case reflect.Struct:
if field.Type().ConvertibleTo(timeType) { if field.Type().ConvertibleTo(timeType) {
return field.Convert(timeType).Interface().(time.Time).Before(time.Now().UTC()) return getValue(field.Convert(timeType)).(time.Time).Before(time.Now().UTC())
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// hasMaxOf is the validation function for validating if the current field's value is less than or equal to the param's value. // hasMaxOf is the validation function for validating if the current field's value is less than or equal to the param's value.
@@ -2642,7 +2613,7 @@ func isDir(fl FieldLevel) bool {
return fileInfo.IsDir() return fileInfo.IsDir()
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isDirPath is the validation function for validating if the current field's value is a valid directory. // isDirPath is the validation function for validating if the current field's value is a valid directory.
@@ -2699,7 +2670,7 @@ func isDirPath(fl FieldLevel) bool {
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isJSON is the validation function for validating if the current field's value is a valid json string. // isJSON is the validation function for validating if the current field's value is a valid json string.
@@ -2714,12 +2685,12 @@ func isJSON(fl FieldLevel) bool {
fieldType := field.Type() fieldType := field.Type()
if fieldType.ConvertibleTo(byteSliceType) { if fieldType.ConvertibleTo(byteSliceType) {
b := field.Convert(byteSliceType).Interface().([]byte) b := getValue(field.Convert(byteSliceType)).([]byte)
return json.Valid(b) return json.Valid(b)
} }
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isJWT is the validation function for validating if the current field's value is a valid JWT string. // isJWT is the validation function for validating if the current field's value is a valid JWT string.
@@ -2766,7 +2737,7 @@ func isLowercase(fl FieldLevel) bool {
return field.String() == strings.ToLower(field.String()) return field.String() == strings.ToLower(field.String())
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isUppercase is the validation function for validating if the current field's value is an uppercase string. // isUppercase is the validation function for validating if the current field's value is an uppercase string.
@@ -2780,7 +2751,7 @@ func isUppercase(fl FieldLevel) bool {
return field.String() == strings.ToUpper(field.String()) return field.String() == strings.ToUpper(field.String())
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isDatetime is the validation function for validating if the current field's value is a valid datetime string. // isDatetime is the validation function for validating if the current field's value is a valid datetime string.
@@ -2794,7 +2765,7 @@ func isDatetime(fl FieldLevel) bool {
return err == nil return err == nil
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isTimeZone is the validation function for validating if the current field's value is a valid time zone string. // isTimeZone is the validation function for validating if the current field's value is a valid time zone string.
@@ -2816,7 +2787,7 @@ func isTimeZone(fl FieldLevel) bool {
return err == nil return err == nil
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code. // isIso3166Alpha2 is the validation function for validating if the current field's value is a valid iso3166-1 alpha-2 country code.
@@ -2860,7 +2831,7 @@ func isIso3166AlphaNumeric(fl FieldLevel) bool {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
code = int(field.Uint() % 1000) code = int(field.Uint() % 1000)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
_, ok := iso3166_1_alpha_numeric[code] _, ok := iso3166_1_alpha_numeric[code]
@@ -2884,7 +2855,7 @@ func isIso3166AlphaNumericEU(fl FieldLevel) bool {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
code = int(field.Uint() % 1000) code = int(field.Uint() % 1000)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
_, ok := iso3166_1_alpha_numeric_eu[code] _, ok := iso3166_1_alpha_numeric_eu[code]
@@ -2914,7 +2885,7 @@ func isIso4217Numeric(fl FieldLevel) bool {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
code = int(field.Uint()) code = int(field.Uint())
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
_, ok := iso4217_numeric[code] _, ok := iso4217_numeric[code]
@@ -2930,7 +2901,7 @@ func isBCP47LanguageTag(fl FieldLevel) bool {
return err == nil return err == nil
} }
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
// isIsoBicFormat is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362 // isIsoBicFormat is the validation function for validating if the current field's value is a valid Business Identifier Code (SWIFT code), defined in ISO 9362
@@ -3053,7 +3024,7 @@ func hasLuhnChecksum(fl FieldLevel) bool {
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
str = strconv.FormatUint(field.Uint(), 10) str = strconv.FormatUint(field.Uint(), 10)
default: default:
panic(fmt.Sprintf("Bad field type %T", field.Interface())) panic(fmt.Sprintf("Bad field type %s", field.Type()))
} }
size := len(str) size := len(str)
if size < 2 { // there has to be at least one digit that carries a meaning + the checksum if size < 2 { // there has to be at least one digit that carries a meaning + the checksum
@@ -3079,3 +3050,60 @@ func isEIN(fl FieldLevel) bool {
return einRegex().MatchString(field.String()) return einRegex().MatchString(field.String())
} }
func isValidateFn(fl FieldLevel) bool {
const defaultParam = `Validate`
field := fl.Field()
validateFn := cmp.Or(fl.Param(), defaultParam)
ok, err := tryCallValidateFn(field, validateFn)
if err != nil {
return false
}
return ok
}
var (
errMethodNotFound = errors.New(`method not found`)
errMethodReturnNoValues = errors.New(`method return o values (void)`)
errMethodReturnInvalidType = errors.New(`method should return invalid type`)
)
func tryCallValidateFn(field reflect.Value, validateFn string) (bool, error) {
method := field.MethodByName(validateFn)
if field.CanAddr() && !method.IsValid() {
method = field.Addr().MethodByName(validateFn)
}
if !method.IsValid() {
return false, fmt.Errorf("unable to call %q on type %q: %w",
validateFn, field.Type().String(), errMethodNotFound)
}
returnValues := method.Call([]reflect.Value{})
if len(returnValues) == 0 {
return false, fmt.Errorf("unable to use result of method %q on type %q: %w",
validateFn, field.Type().String(), errMethodReturnNoValues)
}
firstReturnValue := returnValues[0]
switch firstReturnValue.Kind() {
case reflect.Bool:
return firstReturnValue.Bool(), nil
case reflect.Interface:
errorType := reflect.TypeOf((*error)(nil)).Elem()
if firstReturnValue.Type().Implements(errorType) {
return firstReturnValue.IsNil(), nil
}
return false, fmt.Errorf("unable to use result of method %q on type %q: %w (got interface %v expect error)",
validateFn, field.Type().String(), errMethodReturnInvalidType, firstReturnValue.Type().String())
default:
return false, fmt.Errorf("unable to use result of method %q on type %q: %w (got %v expect error or bool)",
validateFn, field.Type().String(), errMethodReturnInvalidType, firstReturnValue.Type().String())
}
}

View File

@@ -124,7 +124,6 @@ func (v *Validate) extractStructCache(current reflect.Value, sName string) *cStr
var customName string var customName string
for i := 0; i < numFields; i++ { for i := 0; i < numFields; i++ {
fld = typ.Field(i) fld = typ.Field(i)
if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 { if !v.privateFieldValidation && !fld.Anonymous && len(fld.PkgPath) > 0 {
@@ -191,7 +190,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
} else { } else {
next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true) next, curr := v.parseFieldTagsRecursive(tagsVal, fieldName, t, true)
current.next, current = next, curr current.next, current = next, curr
} }
continue continue
} }
@@ -210,7 +208,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
switch t { switch t {
case diveTag: case diveTag:
current.typeof = typeDive current.typeof = typeDive
continue
case keysTag: case keysTag:
current.typeof = typeKeys current.typeof = typeKeys
@@ -219,8 +216,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag)) panic(fmt.Sprintf("'%s' tag must be immediately preceded by the '%s' tag", keysTag, diveTag))
} }
current.typeof = typeKeys
// need to pass along only keys tag // need to pass along only keys tag
// need to increment i to skip over the keys tags // need to increment i to skip over the keys tags
b := make([]byte, 0, 64) b := make([]byte, 0, 64)
@@ -228,7 +223,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
i++ i++
for ; i < len(tags); i++ { for ; i < len(tags); i++ {
b = append(b, tags[i]...) b = append(b, tags[i]...)
b = append(b, ',') b = append(b, ',')
@@ -238,7 +232,6 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
} }
current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false) current.keys, _ = v.parseFieldTagsRecursive(string(b[:len(b)-1]), fieldName, "", false)
continue
case endKeysTag: case endKeysTag:
current.typeof = typeEndKeys current.typeof = typeEndKeys
@@ -256,19 +249,15 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s
case omitempty: case omitempty:
current.typeof = typeOmitEmpty current.typeof = typeOmitEmpty
continue
case omitnil: case omitnil:
current.typeof = typeOmitNil current.typeof = typeOmitNil
continue
case structOnlyTag: case structOnlyTag:
current.typeof = typeStructOnly current.typeof = typeStructOnly
continue
case noStructLevelTag: case noStructLevelTag:
current.typeof = typeNoStructLevel current.typeof = typeNoStructLevel
continue
default: default:
if t == isdefault { if t == isdefault {

View File

@@ -188,7 +188,7 @@ Same as structonly tag except that any struct level validations will not run.
# Omit Empty # Omit Empty
Allows conditional validation, for example if a field is not set with Allows conditional validation, for example, if a field is not set with
a value (Determined by the "required" validator) then other validation a value (Determined by the "required" validator) then other validation
such as min or max won't run, but if a value is set validation will run. such as min or max won't run, but if a value is set validation will run.
@@ -756,6 +756,20 @@ in a field of the struct specified via a parameter.
// For slices of struct: // For slices of struct:
Usage: unique=field Usage: unique=field
# ValidateFn
This validates that an object responds to a method that can return error or bool.
By default it expects an interface `Validate() error` and check that the method
does not return an error. Other methods can be specified using two signatures:
If the method returns an error, it check if the return value is nil.
If the method returns a boolean, it checks if the value is true.
// to use the default method Validate() error
Usage: validateFn
// to use the custom method IsValid() bool (or error)
Usage: validateFn=IsValid
# Alpha Only # Alpha Only
This validates that a string value contains ASCII alpha characters only This validates that a string value contains ASCII alpha characters only

View File

@@ -24,7 +24,6 @@ type InvalidValidationError struct {
// Error returns InvalidValidationError message // Error returns InvalidValidationError message
func (e *InvalidValidationError) Error() string { func (e *InvalidValidationError) Error() string {
if e.Type == nil { if e.Type == nil {
return "validator: (nil)" return "validator: (nil)"
} }
@@ -41,11 +40,9 @@ type ValidationErrors []FieldError
// All information to create an error message specific to your application is contained within // All information to create an error message specific to your application is contained within
// the FieldError found within the ValidationErrors array // the FieldError found within the ValidationErrors array
func (ve ValidationErrors) Error() string { func (ve ValidationErrors) Error() string {
buff := bytes.NewBufferString("") buff := bytes.NewBufferString("")
for i := 0; i < len(ve); i++ { for i := 0; i < len(ve); i++ {
buff.WriteString(ve[i].Error()) buff.WriteString(ve[i].Error())
buff.WriteString("\n") buff.WriteString("\n")
} }
@@ -55,7 +52,6 @@ func (ve ValidationErrors) Error() string {
// Translate translates all of the ValidationErrors // Translate translates all of the ValidationErrors
func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations { func (ve ValidationErrors) Translate(ut ut.Translator) ValidationErrorsTranslations {
trans := make(ValidationErrorsTranslations) trans := make(ValidationErrorsTranslations)
var fe *fieldError var fe *fieldError
@@ -109,22 +105,24 @@ type FieldError interface {
// StructNamespace returns the namespace for the field error, with the field's // StructNamespace returns the namespace for the field error, with the field's
// actual name. // actual name.
// //
// eq. "User.FirstName" see Namespace for comparison // eg. "User.FirstName" see Namespace for comparison
// //
// NOTE: this field can be blank when validating a single primitive field // NOTE: this field can be blank when validating a single primitive field
// using validate.Field(...) as there is no way to extract its name // using validate.Field(...) as there is no way to extract its name
StructNamespace() string StructNamespace() string
// Field returns the fields name with the tag name taking precedence over the // Field returns the field's name with the tag name taking precedence over the
// field's actual name. // field's actual name.
// //
// eq. JSON name "fname" // `RegisterTagNameFunc` must be registered to get tag value.
//
// eg. JSON name "fname"
// see StructField for comparison // see StructField for comparison
Field() string Field() string
// StructField returns the field's actual name from the struct, when able to determine. // StructField returns the field's actual name from the struct, when able to determine.
// //
// eq. "FirstName" // eg. "FirstName"
// see Field for comparison // see Field for comparison
StructField() string StructField() string
@@ -204,7 +202,6 @@ func (fe *fieldError) StructNamespace() string {
// Field returns the field's name with the tag name taking precedence over the // Field returns the field's name with the tag name taking precedence over the
// field's actual name. // field's actual name.
func (fe *fieldError) Field() string { func (fe *fieldError) Field() string {
return fe.ns[len(fe.ns)-int(fe.fieldLen):] return fe.ns[len(fe.ns)-int(fe.fieldLen):]
// // return fe.field // // return fe.field
// fld := fe.ns[len(fe.ns)-int(fe.fieldLen):] // fld := fe.ns[len(fe.ns)-int(fe.fieldLen):]

View File

@@ -107,7 +107,6 @@ func (v *validate) ExtractType(field reflect.Value) (reflect.Value, reflect.Kind
// ReportError reports an error just by passing the field and tag information // ReportError reports an error just by passing the field and tag information
func (v *validate) ReportError(field interface{}, fieldName, structFieldName, tag, param string) { func (v *validate) ReportError(field interface{}, fieldName, structFieldName, tag, param string) {
fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false) fv, kind, _ := v.extractTypeInternal(reflect.ValueOf(field), false)
if len(structFieldName) == 0 { if len(structFieldName) == 0 {
@@ -123,7 +122,6 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
} }
if kind == reflect.Invalid { if kind == reflect.Invalid {
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v, v: v.v,
@@ -149,7 +147,7 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
structNs: v.str2, structNs: v.str2,
fieldLen: uint8(len(fieldName)), fieldLen: uint8(len(fieldName)),
structfieldLen: uint8(len(structFieldName)), structfieldLen: uint8(len(structFieldName)),
value: fv.Interface(), value: getValue(fv),
param: param, param: param,
kind: kind, kind: kind,
typ: fv.Type(), typ: fv.Type(),
@@ -161,11 +159,9 @@ func (v *validate) ReportError(field interface{}, fieldName, structFieldName, ta
// //
// NOTE: this function prepends the current namespace to the relative ones. // NOTE: this function prepends the current namespace to the relative ones.
func (v *validate) ReportValidationErrors(relativeNamespace, relativeStructNamespace string, errs ValidationErrors) { func (v *validate) ReportValidationErrors(relativeNamespace, relativeStructNamespace string, errs ValidationErrors) {
var err *fieldError var err *fieldError
for i := 0; i < len(errs); i++ { for i := 0; i < len(errs); i++ {
err = errs[i].(*fieldError) err = errs[i].(*fieldError)
err.ns = string(append(append(v.ns, relativeNamespace...), err.ns...)) err.ns = string(append(append(v.ns, relativeNamespace...), err.ns...))
err.structNs = string(append(append(v.actualNs, relativeStructNamespace...), err.structNs...)) err.structNs = string(append(append(v.actualNs, relativeStructNamespace...), err.structNs...))

View File

@@ -13,7 +13,6 @@ import (
// It will dive into pointers, customTypes and return you the // It will dive into pointers, customTypes and return you the
// underlying value and it's kind. // underlying value and it's kind.
func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) { func (v *validate) extractTypeInternal(current reflect.Value, nullable bool) (reflect.Value, reflect.Kind, bool) {
BEGIN: BEGIN:
switch current.Kind() { switch current.Kind() {
case reflect.Ptr: case reflect.Ptr:
@@ -44,7 +43,6 @@ BEGIN:
default: default:
if v.v.hasCustomFuncs { if v.v.hasCustomFuncs {
if fn, ok := v.v.customFuncs[current.Type()]; ok { if fn, ok := v.v.customFuncs[current.Type()]; ok {
current = reflect.ValueOf(fn(current)) current = reflect.ValueOf(fn(current))
goto BEGIN goto BEGIN
@@ -61,7 +59,6 @@ BEGIN:
// NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field // NOTE: when not successful ok will be false, this can happen when a nested struct is nil and so the field
// could not be retrieved because it didn't exist. // could not be retrieved because it didn't exist.
func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) { func (v *validate) getStructFieldOKInternal(val reflect.Value, namespace string) (current reflect.Value, kind reflect.Kind, nullable bool, found bool) {
BEGIN: BEGIN:
current, kind, nullable = v.ExtractType(val) current, kind, nullable = v.ExtractType(val)
if kind == reflect.Invalid { if kind == reflect.Invalid {
@@ -74,7 +71,6 @@ BEGIN:
} }
switch kind { switch kind {
case reflect.Ptr, reflect.Interface: case reflect.Ptr, reflect.Interface:
return return
@@ -85,7 +81,6 @@ BEGIN:
var ns string var ns string
if !typ.ConvertibleTo(timeType) { if !typ.ConvertibleTo(timeType) {
idx := strings.Index(namespace, namespaceSeparator) idx := strings.Index(namespace, namespaceSeparator)
if idx != -1 { if idx != -1 {
@@ -222,7 +217,7 @@ BEGIN:
panic("Invalid field namespace") panic("Invalid field namespace")
} }
// asInt returns the parameter as a int64 // asInt returns the parameter as an int64
// or panics if it can't convert // or panics if it can't convert
func asInt(param string) int64 { func asInt(param string) int64 {
i, err := strconv.ParseInt(param, 0, 64) i, err := strconv.ParseInt(param, 0, 64)
@@ -256,7 +251,6 @@ func asIntFromType(t reflect.Type, param string) int64 {
// asUint returns the parameter as a uint64 // asUint returns the parameter as a uint64
// or panics if it can't convert // or panics if it can't convert
func asUint(param string) uint64 { func asUint(param string) uint64 {
i, err := strconv.ParseUint(param, 0, 64) i, err := strconv.ParseUint(param, 0, 64)
panicIf(err) panicIf(err)
@@ -282,7 +276,6 @@ func asFloat32(param string) float64 {
// asBool returns the parameter as a bool // asBool returns the parameter as a bool
// or panics if it can't convert // or panics if it can't convert
func asBool(param string) bool { func asBool(param string) bool {
i, err := strconv.ParseBool(param) i, err := strconv.ParseBool(param)
panicIf(err) panicIf(err)
@@ -303,7 +296,7 @@ func fieldMatchesRegexByStringerValOrString(regexFn func() *regexp.Regexp, fl Fi
case reflect.String: case reflect.String:
return regex.MatchString(fl.Field().String()) return regex.MatchString(fl.Field().String())
default: default:
if stringer, ok := fl.Field().Interface().(fmt.Stringer); ok { if stringer, ok := getValue(fl.Field()).(fmt.Stringer); ok {
return regex.MatchString(stringer.String()) return regex.MatchString(stringer.String())
} else { } else {
return regex.MatchString(fl.Field().String()) return regex.MatchString(fl.Field().String())

View File

@@ -32,14 +32,12 @@ type validate struct {
// parent and current will be the same the first run of validateStruct // parent and current will be the same the first run of validateStruct
func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) { func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, current reflect.Value, typ reflect.Type, ns []byte, structNs []byte, ct *cTag) {
cs, ok := v.v.structCache.Get(typ) cs, ok := v.v.structCache.Get(typ)
if !ok { if !ok {
cs = v.v.extractStructCache(current, typ.Name()) cs = v.v.extractStructCache(current, typ.Name())
} }
if len(ns) == 0 && len(cs.name) != 0 { if len(ns) == 0 && len(cs.name) != 0 {
ns = append(ns, cs.name...) ns = append(ns, cs.name...)
ns = append(ns, '.') ns = append(ns, '.')
@@ -50,21 +48,17 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur
// ct is nil on top level struct, and structs as fields that have no tag info // ct is nil on top level struct, and structs as fields that have no tag info
// so if nil or if not nil and the structonly tag isn't present // so if nil or if not nil and the structonly tag isn't present
if ct == nil || ct.typeof != typeStructOnly { if ct == nil || ct.typeof != typeStructOnly {
var f *cField var f *cField
for i := 0; i < len(cs.fields); i++ { for i := 0; i < len(cs.fields); i++ {
f = cs.fields[i] f = cs.fields[i]
if v.isPartial { if v.isPartial {
if v.ffn != nil { if v.ffn != nil {
// used with StructFiltered // used with StructFiltered
if v.ffn(append(structNs, f.name...)) { if v.ffn(append(structNs, f.name...)) {
continue continue
} }
} else { } else {
// used with StructPartial & StructExcept // used with StructPartial & StructExcept
_, ok = v.includeExclude[string(append(structNs, f.name...))] _, ok = v.includeExclude[string(append(structNs, f.name...))]
@@ -83,7 +77,6 @@ func (v *validate) validateStruct(ctx context.Context, parent reflect.Value, cur
// first iteration will have no info about nostructlevel tag, and is checked prior to // first iteration will have no info about nostructlevel tag, and is checked prior to
// calling the next iteration of validateStruct called from traverseField. // calling the next iteration of validateStruct called from traverseField.
if cs.fn != nil { if cs.fn != nil {
v.slflParent = parent v.slflParent = parent
v.slCurrent = current v.slCurrent = current
v.ns = ns v.ns = ns
@@ -267,7 +260,7 @@ OUTER:
return return
} }
default: default:
if v.fldIsPointer && field.Interface() == nil { if v.fldIsPointer && getValue(field) == nil {
return return
} }
} }
@@ -291,7 +284,6 @@ OUTER:
reusableCF := &cField{} reusableCF := &cField{}
for i := 0; i < current.Len(); i++ { for i := 0; i < current.Len(); i++ {
i64 = int64(i) i64 = int64(i)
v.misc = append(v.misc[0:0], cf.name...) v.misc = append(v.misc[0:0], cf.name...)
@@ -304,7 +296,6 @@ OUTER:
if cf.namesEqual { if cf.namesEqual {
reusableCF.altName = reusableCF.name reusableCF.altName = reusableCF.name
} else { } else {
v.misc = append(v.misc[0:0], cf.altName...) v.misc = append(v.misc[0:0], cf.altName...)
v.misc = append(v.misc, '[') v.misc = append(v.misc, '[')
v.misc = strconv.AppendInt(v.misc, i64, 10) v.misc = strconv.AppendInt(v.misc, i64, 10)
@@ -321,8 +312,7 @@ OUTER:
reusableCF := &cField{} reusableCF := &cField{}
for _, key := range current.MapKeys() { for _, key := range current.MapKeys() {
pv = fmt.Sprintf("%v", key)
pv = fmt.Sprintf("%v", key.Interface())
v.misc = append(v.misc[0:0], cf.name...) v.misc = append(v.misc[0:0], cf.name...)
v.misc = append(v.misc, '[') v.misc = append(v.misc, '[')
@@ -347,6 +337,18 @@ OUTER:
// can be nil when just keys being validated // can be nil when just keys being validated
if ct.next != nil { if ct.next != nil {
v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next) v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct.next)
} else {
// Struct fallback when map values are structs
val := current.MapIndex(key)
switch val.Kind() {
case reflect.Ptr:
if val.Elem().Kind() == reflect.Struct {
// Dive into the struct so its own tags run
v.traverseField(ctx, parent, val, ns, structNs, reusableCF, nil)
}
case reflect.Struct:
v.traverseField(ctx, parent, val, ns, structNs, reusableCF, nil)
}
} }
} else { } else {
v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct) v.traverseField(ctx, parent, current.MapIndex(key), ns, structNs, reusableCF, ct)
@@ -366,7 +368,6 @@ OUTER:
v.misc = v.misc[0:0] v.misc = v.misc[0:0]
for { for {
// set Field Level fields // set Field Level fields
v.slflParent = parent v.slflParent = parent
v.flField = current v.flField = current
@@ -381,7 +382,6 @@ OUTER:
// drain rest of the 'or' values, then continue or leave // drain rest of the 'or' values, then continue or leave
for { for {
ct = ct.next ct = ct.next
if ct == nil { if ct == nil {
@@ -418,7 +418,6 @@ OUTER:
} }
if ct.hasAlias { if ct.hasAlias {
v.errs = append(v.errs, v.errs = append(v.errs,
&fieldError{ &fieldError{
v: v.v, v: v.v,
@@ -434,9 +433,7 @@ OUTER:
typ: typ, typ: typ,
}, },
) )
} else { } else {
tVal := string(v.misc)[1:] tVal := string(v.misc)[1:]
v.errs = append(v.errs, v.errs = append(v.errs,
@@ -500,7 +497,6 @@ OUTER:
ct = ct.next ct = ct.next
} }
} }
} }
func getValue(val reflect.Value) interface{} { func getValue(val reflect.Value) interface{} {

View File

@@ -104,7 +104,6 @@ type Validate struct {
// in essence only parsing your validation tags once per struct type. // in essence only parsing your validation tags once per struct type.
// Using multiple instances neglects the benefit of caching. // Using multiple instances neglects the benefit of caching.
func New(options ...Option) *Validate { func New(options ...Option) *Validate {
tc := new(tagCache) tc := new(tagCache)
tc.m.Store(make(map[string]*cTag)) tc.m.Store(make(map[string]*cTag))
@@ -126,7 +125,6 @@ func New(options ...Option) *Validate {
// must copy validators for separate validations to be used in each instance // must copy validators for separate validations to be used in each instance
for k, val := range bakedInValidators { for k, val := range bakedInValidators {
switch k { switch k {
// these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour // these require that even if the value is nil that the validation should run, omitempty still overrides this behaviour
case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag, case requiredIfTag, requiredUnlessTag, requiredWithTag, requiredWithAllTag, requiredWithoutTag, requiredWithoutAllTag,
@@ -233,30 +231,12 @@ func (v *Validate) RegisterValidationCtx(tag string, fn FuncCtx, callValidationE
return v.registerValidation(tag, fn, false, nilCheckable) return v.registerValidation(tag, fn, false, nilCheckable)
} }
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error {
if len(tag) == 0 {
return errors.New("function Key cannot be empty")
}
if fn == nil {
return errors.New("function cannot be empty")
}
_, ok := restrictedTags[tag]
if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) {
panic(fmt.Sprintf(restrictedTagErr, tag))
}
v.validations[tag] = internalValidationFuncWrapper{fn: fn, runValidationOnNil: nilCheckable}
return nil
}
// RegisterAlias registers a mapping of a single validation tag that // RegisterAlias registers a mapping of a single validation tag that
// defines a common or complex set of validation(s) to simplify adding validation // defines a common or complex set of validation(s) to simplify adding validation
// to structs. // to structs.
// //
// NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation // NOTE: this function is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterAlias(alias, tags string) { func (v *Validate) RegisterAlias(alias, tags string) {
_, ok := restrictedTags[alias] _, ok := restrictedTags[alias]
if ok || strings.ContainsAny(alias, restrictedTagChars) { if ok || strings.ContainsAny(alias, restrictedTagChars) {
@@ -280,7 +260,6 @@ func (v *Validate) RegisterStructValidation(fn StructLevelFunc, types ...interfa
// NOTE: // NOTE:
// - this method is not thread-safe it is intended that these all be registered prior to any validation // - this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) { func (v *Validate) RegisterStructValidationCtx(fn StructLevelFuncCtx, types ...interface{}) {
if v.structLevelFuncs == nil { if v.structLevelFuncs == nil {
v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx) v.structLevelFuncs = make(map[reflect.Type]StructLevelFuncCtx)
} }
@@ -327,7 +306,6 @@ func (v *Validate) RegisterStructValidationMapRules(rules map[string]string, typ
// //
// NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation // NOTE: this method is not thread-safe it is intended that these all be registered prior to any validation
func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) { func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{}) {
if v.customFuncs == nil { if v.customFuncs == nil {
v.customFuncs = make(map[reflect.Type]CustomTypeFunc) v.customFuncs = make(map[reflect.Type]CustomTypeFunc)
} }
@@ -341,7 +319,6 @@ func (v *Validate) RegisterCustomTypeFunc(fn CustomTypeFunc, types ...interface{
// RegisterTranslation registers translations against the provided tag. // RegisterTranslation registers translations against the provided tag.
func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) { func (v *Validate) RegisterTranslation(tag string, trans ut.Translator, registerFn RegisterTranslationsFunc, translationFn TranslationFunc) (err error) {
if v.transTagFunc == nil { if v.transTagFunc == nil {
v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc) v.transTagFunc = make(map[ut.Translator]map[string]TranslationFunc)
} }
@@ -375,7 +352,6 @@ func (v *Validate) Struct(s interface{}) error {
// It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise. // It returns InvalidValidationError for bad values passed in and nil or ValidationErrors as error otherwise.
// You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors. // You will need to assert the error if it's not nil eg. err.(validator.ValidationErrors) to access the array of errors.
func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) { func (v *Validate) StructCtx(ctx context.Context, s interface{}) (err error) {
val := reflect.ValueOf(s) val := reflect.ValueOf(s)
top := val top := val
@@ -492,10 +468,8 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
name := typ.Name() name := typ.Name()
for _, k := range fields { for _, k := range fields {
flds := strings.Split(k, namespaceSeparator) flds := strings.Split(k, namespaceSeparator)
if len(flds) > 0 { if len(flds) > 0 {
vd.misc = append(vd.misc[0:0], name...) vd.misc = append(vd.misc[0:0], name...)
// Don't append empty name for unnamed structs // Don't append empty name for unnamed structs
if len(vd.misc) != 0 { if len(vd.misc) != 0 {
@@ -503,7 +477,6 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
} }
for _, s := range flds { for _, s := range flds {
idx := strings.Index(s, leftBracket) idx := strings.Index(s, leftBracket)
if idx != -1 { if idx != -1 {
@@ -519,7 +492,6 @@ func (v *Validate) StructPartialCtx(ctx context.Context, s interface{}, fields .
idx = strings.Index(s, leftBracket) idx = strings.Index(s, leftBracket)
} }
} else { } else {
vd.misc = append(vd.misc, s...) vd.misc = append(vd.misc, s...)
vd.includeExclude[string(vd.misc)] = struct{}{} vd.includeExclude[string(vd.misc)] = struct{}{}
} }
@@ -582,7 +554,6 @@ func (v *Validate) StructExceptCtx(ctx context.Context, s interface{}, fields ..
name := typ.Name() name := typ.Name()
for _, key := range fields { for _, key := range fields {
vd.misc = vd.misc[0:0] vd.misc = vd.misc[0:0]
if len(name) > 0 { if len(name) > 0 {
@@ -709,3 +680,20 @@ func (v *Validate) VarWithValueCtx(ctx context.Context, field interface{}, other
v.pool.Put(vd) v.pool.Put(vd)
return return
} }
func (v *Validate) registerValidation(tag string, fn FuncCtx, bakedIn bool, nilCheckable bool) error {
if len(tag) == 0 {
return errors.New("function Key cannot be empty")
}
if fn == nil {
return errors.New("function cannot be empty")
}
_, ok := restrictedTags[tag]
if !bakedIn && (ok || strings.ContainsAny(tag, restrictedTagChars)) {
panic(fmt.Sprintf(restrictedTagErr, tag))
}
v.validations[tag] = internalValidationFuncWrapper{fn: fn, runValidationOnNil: nilCheckable}
return nil
}

2
vendor/modules.txt vendored
View File

@@ -232,7 +232,7 @@ github.com/go-playground/locales/currency
# github.com/go-playground/universal-translator v0.18.1 # github.com/go-playground/universal-translator v0.18.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/go-playground/universal-translator github.com/go-playground/universal-translator
# github.com/go-playground/validator/v10 v10.26.0 # github.com/go-playground/validator/v10 v10.27.0
## explicit; go 1.20 ## explicit; go 1.20
github.com/go-playground/validator/v10 github.com/go-playground/validator/v10
# github.com/gogo/protobuf v1.3.2 # github.com/gogo/protobuf v1.3.2