Skip to content

Commit

Permalink
Merge branch 'go-playground:master' into feature/omitzero
Browse files Browse the repository at this point in the history
  • Loading branch information
zeewell authored Dec 19, 2024
2 parents 835464d + 6c3307e commit dcb76a8
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 4 deletions.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package validator
=================
<img align="right" src="logo.png">[![Join the chat at https://gitter.im/go-playground/validator](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/go-playground/validator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
![Project status](https://img.shields.io/badge/version-10.22.0-green.svg)
![Project status](https://img.shields.io/badge/version-10.23.0-green.svg)
[![Build Status](https://travis-ci.org/go-playground/validator.svg?branch=master)](https://travis-ci.org/go-playground/validator)
[![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)
Expand All @@ -22,6 +22,11 @@ It has the following **unique** features:
- Customizable i18n aware error messages.
- Default validator for the [gin](https://github.com/gin-gonic/gin) web framework; upgrading from v8 to v9 in gin see [here](https://github.com/go-playground/validator/tree/master/_examples/gin-upgrading-overriding)

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.

Installation
------------

Expand Down
35 changes: 34 additions & 1 deletion baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ var (
"fqdn": isFQDN,
"unique": isUnique,
"oneof": isOneOf,
"oneofci": isOneOfCI,
"html": isHTML,
"html_encoded": isHTMLEncoded,
"url_encoded": isURLEncoded,
Expand All @@ -214,6 +215,7 @@ var (
"json": isJSON,
"jwt": isJWT,
"hostname_port": isHostnamePort,
"port": isPort,
"lowercase": isLowercase,
"uppercase": isUppercase,
"datetime": isDatetime,
Expand Down Expand Up @@ -300,6 +302,23 @@ func isOneOf(fl FieldLevel) bool {
return false
}

// isOneOfCI is the validation function for validating if the current field's value is one of the provided string values (case insensitive).
func isOneOfCI(fl FieldLevel) bool {
vals := parseOneOfParam2(fl.Param())
field := fl.Field()

if field.Kind() != reflect.String {
panic(fmt.Sprintf("Bad field type %T", field.Interface()))
}
v := field.String()
for _, val := range vals {
if strings.EqualFold(val, v) {
return true
}
}
return false
}

// isUnique is the validation function for validating if each array|slice|map value is unique
func isUnique(fl FieldLevel) bool {
field := fl.Field()
Expand Down Expand Up @@ -1843,7 +1862,14 @@ func requireCheckFieldValue(
return int64(field.Len()) == asInt(value)

case reflect.Bool:
return field.Bool() == asBool(value)
return field.Bool() == (value == "true")

case reflect.Ptr:
if field.IsNil() {
return value == "nil"
}
// Handle non-nil pointers
return requireCheckFieldValue(fl, param, value, defaultNotFoundValue)
}

// default reflect.String:
Expand Down Expand Up @@ -2719,6 +2745,13 @@ func isHostnamePort(fl FieldLevel) bool {
return true
}

// IsPort validates if the current field's value represents a valid port
func isPort(fl FieldLevel) bool {
val := fl.Field().Uint()

return val >= 1 && val <= 65535
}

// isLowercase is the validation function for validating if the current field's value is a lowercase string.
func isLowercase(fl FieldLevel) bool {
field := fl.Field()
Expand Down
9 changes: 8 additions & 1 deletion doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,12 +489,19 @@ For strings, ints, and uints, oneof will ensure that the value
is one of the values in the parameter. The parameter should be
a list of values separated by whitespace. Values may be
strings or numbers. To match strings with spaces in them, include
the target string between single quotes.
the target string between single quotes. Kind of like an 'enum'.
Usage: oneof=red green
oneof='red green' 'blue yellow'
oneof=5 7 9
# One Of Case Insensitive
Works the same as oneof but is case insensitive and therefore only accepts strings.
Usage: oneofci=red green
oneofci='red green' 'blue yellow'
# Greater Than
For numbers, this will ensure that the value is greater than the
Expand Down
2 changes: 1 addition & 1 deletion regexes.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const (
cveRegexString = `^CVE-(1999|2\d{3})-(0[^0]\d{2}|0\d[^0]\d{1}|0\d{2}[^0]|[1-9]{1}\d{3,})$` // CVE Format Id https://cve.mitre.org/cve/identifiers/syntaxchange.html
mongodbIdRegexString = "^[a-f\\d]{24}$"
mongodbConnStringRegexString = "^mongodb(\\+srv)?:\\/\\/(([a-zA-Z\\d]+):([a-zA-Z\\d$:\\/?#\\[\\]@]+)@)?(([a-z\\d.-]+)(:[\\d]+)?)((,(([a-z\\d.-]+)(:(\\d+))?))*)?(\\/[a-zA-Z-_]{1,64})?(\\?(([a-zA-Z]+)=([a-zA-Z\\d]+))(&(([a-zA-Z\\d]+)=([a-zA-Z\\d]+))?)*)?$"
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|(\d+(\/|-)\d+)|\d+|\*) ?){5,7})`
cronRegexString = `(@(annually|yearly|monthly|weekly|daily|hourly|reboot))|(@every (\d+(ns|us|µs|ms|s|m|h))+)|((((\d+,)+\d+|((\*|\d+)(\/|-)\d+)|\d+|\*) ?){5,7})`
spicedbIDRegexString = `^(([a-zA-Z0-9/_|\-=+]{1,})|\*)$`
spicedbPermissionRegexString = "^([a-z][a-z0-9_]{1,62}[a-z0-9])?$"
spicedbTypeRegexString = "^([a-z][a-z0-9_]{1,61}[a-z0-9]/)?[a-z][a-z0-9_]{1,62}[a-z0-9]$"
Expand Down
122 changes: 122 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5680,6 +5680,82 @@ func TestOneOfValidation(t *testing.T) {
}, "Bad field type float64")
}

func TestOneOfCIValidation(t *testing.T) {
validate := New()

passSpecs := []struct {
f interface{}
t string
}{
{f: "red", t: "oneofci=RED GREEN"},
{f: "RED", t: "oneofci=red green"},
{f: "red", t: "oneofci=red green"},
{f: "RED", t: "oneofci=RED GREEN"},
{f: "green", t: "oneofci=red green"},
{f: "red green", t: "oneofci='red green' blue"},
{f: "blue", t: "oneofci='red green' blue"},
{f: "GREEN", t: "oneofci=Red Green"},
{f: "ReD", t: "oneofci=RED GREEN"},
{f: "gReEn", t: "oneofci=rEd GrEeN"},
{f: "RED GREEN", t: "oneofci='red green' blue"},
{f: "red Green", t: "oneofci='RED GREEN' Blue"},
{f: "Red green", t: "oneofci='Red Green' BLUE"},
{f: "rEd GrEeN", t: "oneofci='ReD gReEn' BlUe"},
{f: "BLUE", t: "oneofci='Red Green' BLUE"},
{f: "BlUe", t: "oneofci='RED GREEN' Blue"},
{f: "bLuE", t: "oneofci='red green' BLUE"},
}

for _, spec := range passSpecs {
t.Logf("%#v", spec)
errs := validate.Var(spec.f, spec.t)
Equal(t, errs, nil)
}

failSpecs := []struct {
f interface{}
t string
}{
{f: "", t: "oneofci=red green"},
{f: "yellow", t: "oneofci=red green"},
{f: "green", t: "oneofci='red green' blue"},
}

for _, spec := range failSpecs {
t.Logf("%#v", spec)
errs := validate.Var(spec.f, spec.t)
AssertError(t, errs, "", "", "", "", "oneofci")
}

panicSpecs := []struct {
f interface{}
t string
}{
{f: 3.14, t: "oneofci=red green"},
{f: 5, t: "oneofci=red green"},
{f: uint(6), t: "oneofci=7"},
{f: int8(5), t: "oneofci=red green"},
{f: int16(5), t: "oneofci=red green"},
{f: int32(5), t: "oneofci=red green"},
{f: int64(5), t: "oneofci=red green"},
{f: uint(5), t: "oneofci=red green"},
{f: uint8(5), t: "oneofci=red green"},
{f: uint16(5), t: "oneofci=red green"},
{f: uint32(5), t: "oneofci=red green"},
{f: uint64(5), t: "oneofci=red green"},
}

panicCount := 0
for _, spec := range panicSpecs {
t.Logf("%#v", spec)
PanicMatches(t, func() {
_ = validate.Var(spec.f, spec.t)
}, fmt.Sprintf("Bad field type %T", spec.f))
panicCount++
}
Equal(t, panicCount, len(panicSpecs))
}

func TestBase32Validation(t *testing.T) {
validate := New()

Expand Down Expand Up @@ -12002,6 +12078,25 @@ func TestExcludedIf(t *testing.T) {
errs = validate.Struct(test10)
Equal(t, errs, nil)

test11 := struct {
Field1 bool
Field2 *string `validate:"excluded_if=Field1 false"`
}{
Field1: false,
Field2: nil,
}
errs = validate.Struct(test11)
Equal(t, errs, nil)

test12 := struct {
Field1 bool
Field2 *string `validate:"excluded_if=Field1 !Field1"`
}{
Field1: true,
Field2: nil,
}
errs = validate.Struct(test12)
Equal(t, errs, nil)
// Checks number of params in struct tag is correct
defer func() {
if r := recover(); r == nil {
Expand Down Expand Up @@ -12377,6 +12472,32 @@ func Test_hostnameport_validator(t *testing.T) {
}
}

func Test_port_validator(t *testing.T) {
type Host struct {
Port uint32 `validate:"port"`
}

type testInput struct {
data uint32
expected bool
}
testData := []testInput{
{0, false},
{1, true},
{65535, true},
{65536, false},
{65538, false},
}
for _, td := range testData {
h := Host{Port: td.data}
v := New()
err := v.Struct(h)
if td.expected != (err == nil) {
t.Fatalf("Test failed for data: %v Error: %v", td.data, err)
}
}
}

func TestLowercaseValidation(t *testing.T) {
tests := []struct {
param string
Expand Down Expand Up @@ -13637,6 +13758,7 @@ func TestCronExpressionValidation(t *testing.T) {
{"*/20 * * * *", "cron", true},
{"0 15 10 ? * MON-FRI", "cron", true},
{"0 15 10 ? * 6#3", "cron", true},
{"0 */15 * * *", "cron", true},
{"wrong", "cron", false},
}

Expand Down

0 comments on commit dcb76a8

Please sign in to comment.