[full-ci] chore: bump reva to v2.43.0 (#2630)

This commit is contained in:
Viktor Scharf
2026-04-20 16:05:48 +02:00
committed by GitHub
parent 7d98f03703
commit 6099ca3658
36 changed files with 883 additions and 227 deletions
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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))