Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package validator
================
<img align="right" src="https://raw.githubusercontent.com/go-playground/validator/v9/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-9.6.0-green.svg)
![Project status](https://img.shields.io/badge/version-9.7.0-green.svg)
[![Build Status](https://semaphoreci.com/api/v1/joeybloggs/validator/branches/v9/badge.svg)](https://semaphoreci.com/joeybloggs/validator)
[![Coverage Status](https://coveralls.io/repos/go-playground/validator/badge.svg?branch=v9&service=github)](https://coveralls.io/github/go-playground/validator?branch=v9)
[![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/validator)](https://goreportcard.com/report/github.com/go-playground/validator)
Expand Down
11 changes: 11 additions & 0 deletions baked_in.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ var (
utf8Pipe: {},
noStructLevelTag: {},
requiredTag: {},
isdefault: {},
}

// BakedInAliasValidators is a default mapping of a single validation tag that
Expand All @@ -51,6 +52,7 @@ var (
// or even disregard and use your own map if so desired.
bakedInValidators = map[string]Func{
"required": hasValue,
"isdefault": isDefault,
"len": hasLengthOf,
"min": hasMinOf,
"max": hasMaxOf,
Expand Down Expand Up @@ -903,6 +905,11 @@ func isAlphaUnicode(fl FieldLevel) bool {
return alphaUnicodeRegex.MatchString(fl.Field().String())
}

// isDefault is the opposite of required aka hasValue
func isDefault(fl FieldLevel) bool {
return !hasValue(fl)
}

// HasValue is the validation function for validating if the current field's value is not the default static value.
func hasValue(fl FieldLevel) bool {

Expand Down Expand Up @@ -1487,6 +1494,10 @@ func isHostname(fl FieldLevel) bool {
func isFQDN(fl FieldLevel) bool {
val := fl.Field().String()

if val == "" {
return false
}

if val[len(val)-1] == '.' {
val = val[0 : len(val)-1]
}
Expand Down
5 changes: 5 additions & 0 deletions cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type tagType uint8
const (
typeDefault tagType = iota
typeOmitEmpty
typeIsDefault
typeNoStructLevel
typeStructOnly
typeDive
Expand Down Expand Up @@ -224,6 +225,10 @@ func (v *Validate) parseFieldTagsRecursive(tag string, fieldName string, alias s

default:

if t == isdefault {
current.typeof = typeIsDefault
}

// if a pipe character is needed within the param you must use the utf8Pipe representation "0x7C"
orVals := strings.Split(t, orSeparator)

Expand Down
7 changes: 7 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,13 @@ ensures the value is not nil.

Usage: required

Is Default

This validates that the value is the default value and is almost the
opposite of required.

Usage: isdefault

Length

For numbers, length will ensure that the value is
Expand Down
39 changes: 34 additions & 5 deletions validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr
return
}

if ct.typeof == typeOmitEmpty {
if ct.typeof == typeOmitEmpty || ct.typeof == typeIsDefault {
return
}

Expand Down Expand Up @@ -174,6 +174,39 @@ func (v *validate) traverseField(ctx context.Context, parent reflect.Value, curr

if ct.typeof == typeStructOnly {
goto CONTINUE
} else if ct.typeof == typeIsDefault {
// set Field Level fields
v.slflParent = parent
v.flField = current
v.cf = cf
v.ct = ct

if !ct.fn(ctx, v) {
v.str1 = string(append(ns, cf.altName...))

if v.v.hasTagNameFunc {
v.str2 = string(append(structNs, cf.name...))
} else {
v.str2 = v.str1
}

v.errs = append(v.errs,
&fieldError{
v: v.v,
tag: ct.aliasTag,
actualTag: ct.tag,
ns: v.str1,
structNs: v.str2,
fieldLen: uint8(len(cf.altName)),
structfieldLen: uint8(len(cf.name)),
value: current.Interface(),
param: ct.param,
kind: kind,
typ: typ,
},
)
return
}
}

ct = ct.next
Expand Down Expand Up @@ -404,10 +437,6 @@ OUTER:
v.cf = cf
v.ct = ct

// // report error interface functions need these
// v.ns = ns
// v.actualNs = structNs

if !ct.fn(ctx, v) {

v.str1 = string(append(ns, cf.altName...))
Expand Down
1 change: 1 addition & 0 deletions validator_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
structOnlyTag = "structonly"
noStructLevelTag = "nostructlevel"
omitempty = "omitempty"
isdefault = "isdefault"
skipValidationTag = "-"
diveTag = "dive"
requiredTag = "required"
Expand Down
59 changes: 59 additions & 0 deletions validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7189,6 +7189,7 @@ func TestFQDNValidation(t *testing.T) {
{"2001:cdba:0000:0000:0000:0000:3257:9652", false},
{"2001:cdba:0:0:0:0:3257:9652", false},
{"2001:cdba::3257:9652", false},
{"", false},
}

validate := New()
Expand All @@ -7213,3 +7214,61 @@ func TestFQDNValidation(t *testing.T) {
}
}
}

func TestIsDefault(t *testing.T) {

validate := New()

type Inner struct {
String string `validate:"isdefault"`
}
type Test struct {
String string `validate:"isdefault"`
Inner *Inner `validate:"isdefault"`
}

var tt Test

errs := validate.Struct(tt)
Equal(t, errs, nil)

tt.Inner = &Inner{String: ""}
errs = validate.Struct(tt)
NotEqual(t, errs, nil)

fe := errs.(ValidationErrors)[0]
Equal(t, fe.Field(), "Inner")
Equal(t, fe.Namespace(), "Test.Inner")
Equal(t, fe.Tag(), "isdefault")

validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]

if name == "-" {
return ""
}

return name
})

type Inner2 struct {
String string `validate:"isdefault"`
}

type Test2 struct {
Inner Inner2 `validate:"isdefault" json:"inner"`
}

var t2 Test2
errs = validate.Struct(t2)
Equal(t, errs, nil)

t2.Inner.String = "Changed"
errs = validate.Struct(t2)
NotEqual(t, errs, nil)

fe = errs.(ValidationErrors)[0]
Equal(t, fe.Field(), "inner")
Equal(t, fe.Namespace(), "Test2.inner")
Equal(t, fe.Tag(), "isdefault")
}