[full-ci] chore: bump reva to v2.43.0 (#2630)
This commit is contained in:
+5
-4
@@ -15,7 +15,7 @@ It has the following **unique** features:
|
||||
- Slice, Array and Map diving, which allows any or all levels of a multidimensional field to be validated.
|
||||
- Ability to dive into both map keys and values for validation
|
||||
- Handles type interface by determining it's underlying type prior to validation.
|
||||
- Handles custom field types such as sql driver Valuer see [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29)
|
||||
- Handles custom field types such as sql driver [Valuer](https://golang.org/src/database/sql/driver/types.go?s=1210:1293#L29) and the [Valuer interface](https://github.com/go-playground/validator/blob/master/_examples/valuer/main.go)
|
||||
- Alias validation tags, which allows for mapping of several validations to a single tag for easier defining of validations on structs
|
||||
- Extraction of custom defined Field Name e.g. can specify to extract the JSON name while validating and have it available in the resulting FieldError
|
||||
- Customizable i18n aware error messages.
|
||||
@@ -24,7 +24,7 @@ It has the following **unique** features:
|
||||
A Call for Maintainers
|
||||
----------------------
|
||||
|
||||
Please read the discussiong started [here](https://github.com/go-playground/validator/discussions/1330) if you are interested in contributing/helping maintain this package.
|
||||
Please read the discussion started [here](https://github.com/go-playground/validator/discussions/1330) if you are interested in contributing/helping maintain this package.
|
||||
|
||||
Installation
|
||||
------------
|
||||
@@ -178,13 +178,14 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
| spicedb | SpiceDb ObjectID/Permission/Type |
|
||||
| datetime | Datetime |
|
||||
| e164 | e164 formatted phone number |
|
||||
| ein | U.S. Employeer Identification Number |
|
||||
| ein | U.S. Employer Identification Number |
|
||||
| email | E-mail String
|
||||
| eth_addr | Ethereum Address |
|
||||
| hexadecimal | Hexadecimal String |
|
||||
| hexcolor | Hexcolor String |
|
||||
| hsl | HSL String |
|
||||
| hsla | HSLA String |
|
||||
| cmyk | CMYK String |
|
||||
| html | HTML Tags |
|
||||
| html_encoded | HTML Encoded |
|
||||
| isbn | International Standard Book Number |
|
||||
@@ -274,7 +275,7 @@ validate := validator.New(validator.WithRequiredStructEnabled())
|
||||
#### Aliases:
|
||||
| Tag | Description |
|
||||
| - | - |
|
||||
| iscolor | hexcolor\|rgb\|rgba\|hsl\|hsla |
|
||||
| iscolor | hexcolor\|rgb\|rgba\|hsl\|hsla\|cmyk |
|
||||
| country_code | iso3166_1_alpha2\|iso3166_1_alpha3\|iso3166_1_alpha_numeric |
|
||||
|
||||
Benchmarks
|
||||
|
||||
+96
-41
@@ -70,7 +70,7 @@ var (
|
||||
// defines a common or complex set of validation(s) to simplify
|
||||
// adding validation to structs.
|
||||
bakedInAliases = map[string]string{
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla",
|
||||
"iscolor": "hexcolor|rgb|rgba|hsl|hsla|cmyk",
|
||||
"country_code": "iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric",
|
||||
"eu_country_code": "iso3166_1_alpha2_eu|iso3166_1_alpha3_eu|iso3166_1_alpha_numeric_eu",
|
||||
}
|
||||
@@ -134,6 +134,7 @@ var (
|
||||
"rgba": isRGBA,
|
||||
"hsl": isHSL,
|
||||
"hsla": isHSLA,
|
||||
"cmyk": isCMYK,
|
||||
"e164": isE164,
|
||||
"email": isEmail,
|
||||
"url": isURL,
|
||||
@@ -335,61 +336,110 @@ func isOneOfCI(fl FieldLevel) bool {
|
||||
func isUnique(fl FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
param := fl.Param()
|
||||
v := reflect.ValueOf(struct{}{})
|
||||
|
||||
// sentinel used as map key for nil values
|
||||
var nilKey = struct{}{}
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
elem := field.Type().Elem()
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
elem = elem.Elem()
|
||||
}
|
||||
seen := make(map[interface{}]struct{})
|
||||
|
||||
if param == "" {
|
||||
m := reflect.MakeMap(reflect.MapOf(elem, v.Type()))
|
||||
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
m.SetMapIndex(reflect.Indirect(field.Index(i)), v)
|
||||
}
|
||||
return field.Len() == m.Len()
|
||||
}
|
||||
|
||||
sf, ok := elem.FieldByName(param)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("Bad field name %s", param))
|
||||
}
|
||||
|
||||
sfTyp := sf.Type
|
||||
if sfTyp.Kind() == reflect.Ptr {
|
||||
sfTyp = sfTyp.Elem()
|
||||
}
|
||||
|
||||
m := reflect.MakeMap(reflect.MapOf(sfTyp, v.Type()))
|
||||
var fieldlen int
|
||||
for i := 0; i < field.Len(); i++ {
|
||||
key := reflect.Indirect(reflect.Indirect(field.Index(i)).FieldByName(param))
|
||||
if key.IsValid() {
|
||||
fieldlen++
|
||||
m.SetMapIndex(key, v)
|
||||
elem := field.Index(i)
|
||||
|
||||
// -------- unique (no param) --------
|
||||
if param == "" {
|
||||
var key interface{}
|
||||
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
if elem.IsNil() {
|
||||
key = nilKey
|
||||
} else {
|
||||
key = elem.Elem().Interface() // <-- compare underlying value
|
||||
}
|
||||
} else {
|
||||
key = elem.Interface()
|
||||
}
|
||||
|
||||
if _, ok := seen[key]; ok {
|
||||
return false
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
continue
|
||||
}
|
||||
|
||||
// -------- unique=Field --------
|
||||
|
||||
if elem.Kind() == reflect.Ptr {
|
||||
if elem.IsNil() {
|
||||
if _, ok := seen[nilKey]; ok {
|
||||
return false
|
||||
}
|
||||
seen[nilKey] = struct{}{}
|
||||
continue
|
||||
}
|
||||
elem = elem.Elem()
|
||||
}
|
||||
|
||||
if elem.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("Bad field type %s", elem.Type()))
|
||||
}
|
||||
|
||||
sf := elem.FieldByName(param)
|
||||
if !sf.IsValid() {
|
||||
panic(fmt.Sprintf("Bad field name %s", param))
|
||||
}
|
||||
|
||||
var key interface{}
|
||||
|
||||
if sf.Kind() == reflect.Ptr {
|
||||
if sf.IsNil() {
|
||||
key = nilKey
|
||||
} else {
|
||||
key = sf.Elem().Interface()
|
||||
}
|
||||
} else {
|
||||
key = sf.Interface()
|
||||
}
|
||||
|
||||
if _, ok := seen[key]; ok {
|
||||
return false
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
}
|
||||
return fieldlen == m.Len()
|
||||
|
||||
return true
|
||||
|
||||
case reflect.Map:
|
||||
var m reflect.Value
|
||||
if field.Type().Elem().Kind() == reflect.Ptr {
|
||||
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem().Elem(), v.Type()))
|
||||
} else {
|
||||
m = reflect.MakeMap(reflect.MapOf(field.Type().Elem(), v.Type()))
|
||||
}
|
||||
seen := make(map[interface{}]struct{})
|
||||
|
||||
for _, k := range field.MapKeys() {
|
||||
m.SetMapIndex(reflect.Indirect(field.MapIndex(k)), v)
|
||||
val := field.MapIndex(k)
|
||||
|
||||
var key interface{}
|
||||
|
||||
if val.Kind() == reflect.Ptr {
|
||||
if val.IsNil() {
|
||||
key = nilKey
|
||||
} else {
|
||||
key = val.Elem().Interface() // <-- compare underlying value
|
||||
}
|
||||
} else {
|
||||
key = val.Interface()
|
||||
}
|
||||
|
||||
if _, ok := seen[key]; ok {
|
||||
return false
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
}
|
||||
|
||||
return field.Len() == m.Len()
|
||||
return true
|
||||
|
||||
default:
|
||||
if parent := fl.Parent(); parent.Kind() == reflect.Struct {
|
||||
uniqueField := parent.FieldByName(param)
|
||||
if uniqueField == reflect.ValueOf(nil) {
|
||||
if !uniqueField.IsValid() {
|
||||
panic(fmt.Sprintf("Bad field name provided %s", param))
|
||||
}
|
||||
|
||||
@@ -1721,6 +1771,11 @@ func isHSLA(fl FieldLevel) bool {
|
||||
return hslaRegex().MatchString(fl.Field().String())
|
||||
}
|
||||
|
||||
// isCMYK is the validation function for validating if the current field's value is a valid CMYK color.
|
||||
func isCMYK(fl FieldLevel) bool {
|
||||
return cmykRegex().MatchString(fl.Field().String())
|
||||
}
|
||||
|
||||
// isHSL is the validation function for validating if the current field's value is a valid HSL color.
|
||||
func isHSL(fl FieldLevel) bool {
|
||||
return hslRegex().MatchString(fl.Field().String())
|
||||
|
||||
+41
-1
@@ -52,6 +52,40 @@ Custom Validation functions can be added. Example:
|
||||
// NOTES: using the same tag name as an existing function
|
||||
// will overwrite the existing one
|
||||
|
||||
# Valuer Interface
|
||||
|
||||
Custom types can implement the Valuer interface to return the value that should
|
||||
be validated. This is useful when a type wraps another value and you want
|
||||
validation to run against the unwrapped value.
|
||||
|
||||
type Nullable[T any] struct {
|
||||
Data T
|
||||
}
|
||||
|
||||
func (n Nullable[T]) ValidatorValue() any {
|
||||
return n.Data
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Name string `validate:"required"`
|
||||
}
|
||||
|
||||
type Record struct {
|
||||
Config Nullable[Config] `validate:"required"`
|
||||
}
|
||||
|
||||
r := Record{
|
||||
Config: Nullable[Config]{
|
||||
Data: Config{Name: "validator"},
|
||||
},
|
||||
}
|
||||
|
||||
err := validate.Struct(r)
|
||||
|
||||
The library also supports types like sql/driver.Valuer using
|
||||
RegisterCustomTypeFunc. See _examples/valuer/main.go and
|
||||
_examples/custom/main.go for both approaches.
|
||||
|
||||
# Cross-Field Validation
|
||||
|
||||
Cross-Field Validation can be done via the following tags:
|
||||
@@ -886,6 +920,12 @@ This validates that a string value contains a valid hsla color
|
||||
|
||||
Usage: hsla
|
||||
|
||||
# CMYK String
|
||||
|
||||
This validates that a string value contains a valid cmyk color
|
||||
|
||||
Usage: cmyk
|
||||
|
||||
# E.164 Phone Number String
|
||||
|
||||
This validates that a string value contains a valid E.164 Phone number
|
||||
@@ -1170,7 +1210,7 @@ This validates that a string value contains a valid longitude.
|
||||
|
||||
Usage: longitude
|
||||
|
||||
# Employeer Identification Number EIN
|
||||
# Employer Identification Number EIN
|
||||
|
||||
This validates that a string value contains a valid U.S. Employer Identification Number.
|
||||
|
||||
|
||||
+2
@@ -50,6 +50,7 @@ var postCodePatternDict = map[string]string{
|
||||
"KH": `^\d{5}$`,
|
||||
"CV": `^\d{4}$`,
|
||||
"CL": `^\d{7}$`,
|
||||
"CO": `^\d{6}$`,
|
||||
"CR": `^\d{4,5}|\d{3}-\d{4}$`,
|
||||
"HR": `^\d{5}$`,
|
||||
"CY": `^\d{4}$`,
|
||||
@@ -149,6 +150,7 @@ var postCodePatternDict = map[string]string{
|
||||
"MQ": `^9[78]2\d{2}$`,
|
||||
"NC": `^988\d{2}$`,
|
||||
"NE": `^\d{4}$`,
|
||||
"VG": `^VG\d{4}$`,
|
||||
"VI": `^008(([0-4]\d)|(5[01]))([ \-]\d{4})?$`,
|
||||
"VN": `^[0-9]{1,6}$`,
|
||||
"PF": `^987\d{2}$`,
|
||||
|
||||
+3
-1
@@ -20,6 +20,7 @@ const (
|
||||
rgbaRegexString = "^rgba\\(\\s*(?:(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])|(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%\\s*,\\s*(?:0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
hslRegexString = "^hsl\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*\\)$"
|
||||
hslaRegexString = "^hsla\\(\\s*(?:0|[1-9]\\d?|[12]\\d\\d|3[0-5]\\d|360)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0|[1-9]\\d?|100)%)\\s*,\\s*(?:(?:0.[1-9]*)|[01])\\s*\\)$"
|
||||
cmykRegexString = "^cmyk\\((100|[1-9]?\\d)%\\s*,\\s*(100|[1-9]?\\d)%\\s*,\\s*(100|[1-9]?\\d)%\\s*,\\s*(100|[1-9]?\\d)%\\)$"
|
||||
emailRegexString = "^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$"
|
||||
e164RegexString = "^\\+?[1-9]\\d{7,14}$"
|
||||
base32RegexString = "^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=|[A-Z2-7]{8})$"
|
||||
@@ -57,7 +58,7 @@ const (
|
||||
sSNRegexString = `^[0-9]{3}[ -]?(0[1-9]|[1-9][0-9])[ -]?([1-9][0-9]{3}|[0-9][1-9][0-9]{2}|[0-9]{2}[1-9][0-9]|[0-9]{3}[1-9])$`
|
||||
hostnameRegexStringRFC952 = `^[a-zA-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$` // https://tools.ietf.org/html/rfc952
|
||||
hostnameRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62}){1}(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?$` // accepts hostname starting with a digit https://tools.ietf.org/html/rfc1123
|
||||
fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.')
|
||||
fqdnRegexStringRFC1123 = `^([a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})(\.[a-zA-Z0-9]{1}[a-zA-Z0-9-]{0,62})*?(\.[a-zA-Z]{1}[a-zA-Z0-9-]{0,62})\.?$` // same as hostnameRegexStringRFC1123 but must contain a non numerical TLD (possibly ending with '.')
|
||||
btcAddressRegexString = `^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$` // bitcoin address
|
||||
btcAddressUpperRegexStringBech32 = `^BC1[02-9AC-HJ-NP-Z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
btcAddressLowerRegexStringBech32 = `^bc1[02-9ac-hj-np-z]{7,76}$` // bitcoin bech32 address https://en.bitcoin.it/wiki/Bech32
|
||||
@@ -109,6 +110,7 @@ var (
|
||||
rgbaRegex = lazyRegexCompile(rgbaRegexString)
|
||||
hslRegex = lazyRegexCompile(hslRegexString)
|
||||
hslaRegex = lazyRegexCompile(hslaRegexString)
|
||||
cmykRegex = lazyRegexCompile(cmykRegexString)
|
||||
e164Regex = lazyRegexCompile(e164RegexString)
|
||||
emailRegex = lazyRegexCompile(emailRegexString)
|
||||
base32Regex = lazyRegexCompile(base32RegexString)
|
||||
|
||||
+28
@@ -9,6 +9,13 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Valuer is an interface that allows you to expose a method on a type
|
||||
// (including generic types) that returns a value that is supposed to be validated.
|
||||
type Valuer interface {
|
||||
// ValidatorValue returns the value that is supposed to be validated.
|
||||
ValidatorValue() any
|
||||
}
|
||||
|
||||
// extractTypeInternal gets the actual underlying type of field value.
|
||||
// It will dive into pointers, customTypes and return you the
|
||||
// underlying value and it's kind.
|
||||
@@ -23,6 +30,13 @@ BEGIN:
|
||||
return current, reflect.Ptr, nullable
|
||||
}
|
||||
|
||||
if current.CanInterface() {
|
||||
if v, ok := current.Interface().(Valuer); ok {
|
||||
current = reflect.ValueOf(v.ValidatorValue())
|
||||
goto BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
current = current.Elem()
|
||||
goto BEGIN
|
||||
|
||||
@@ -34,6 +48,13 @@ BEGIN:
|
||||
return current, reflect.Interface, nullable
|
||||
}
|
||||
|
||||
if current.CanInterface() {
|
||||
if v, ok := current.Interface().(Valuer); ok {
|
||||
current = reflect.ValueOf(v.ValidatorValue())
|
||||
goto BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
current = current.Elem()
|
||||
goto BEGIN
|
||||
|
||||
@@ -42,6 +63,13 @@ BEGIN:
|
||||
|
||||
default:
|
||||
|
||||
if current.CanInterface() {
|
||||
if v, ok := current.Interface().(Valuer); ok {
|
||||
current = reflect.ValueOf(v.ValidatorValue())
|
||||
goto BEGIN
|
||||
}
|
||||
}
|
||||
|
||||
if v.v.hasCustomFuncs {
|
||||
if fn, ok := v.v.customFuncs[current.Type()]; ok {
|
||||
current = reflect.ValueOf(fn(current))
|
||||
|
||||
Reference in New Issue
Block a user