A Laravel-like data validator for Go.
Allows you to define a list of validation rules (constraints) for a specific field and produces a summary with failures.
A rule can change the value it validates (only during validation, the original value remains unchanged), which can be beneficial in later validation. Some rules adapt to the currently validated value and act differently. More details can be found in descriptions of rules.
go get github.com/donatorsky/go-validatorThe library is still a work in progress. More details soon.
Each validator has a context counterpart which lets you set the context used during validation. It will be passed to rules. The default context is context.Background().
Validates a map[string]any. You can specify a map of rules for each key and internal values (either slices, arrays, maps or structs).
Returns ErrorsBag with keys being the map keys of input map.
Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.
validator.ForMap( map[string]any{ "foo": 123, "bar": "bar", "baz": map[string]any{ "inner_foo": 123, "inner_bar": "bar", // ... }, }, validator.RulesMap{ "foo": { rule.Required(), rule.Integer[int](), }, "bar": { rule.Required(), rule.String(), }, "baz.inner_foo": { rule.Required(), rule.Integer[int](), }, "baz.inner_bar": { rule.Required(), rule.Slice(), }, // ... }, )Validates a struct. You can specify a map of rules for each field name and internal values (either slices, arrays, maps or structs).
Note that you can also use custom name for a field using validation tag.
You can also pass pointer which will be automatically dereferenced.
Returns ErrorsBag with keys being the field names (or values from validation if provided) of input struct.
Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.
type SomeRequest struct { Foo int Bar string `validation:"bar"` Baz SomeRequestBaz } type SomeRequestBaz struct { InnerFoo int `validation:"foo"` InnerBar string // ... } validator.ForStruct( SomeRequest{ Foo: 123, Bar: "bar", Baz: SomeRequestBaz{ InnerFoo: 123, InnerBar: "bar", // ... }, }, validator.RulesMap{ "Foo": { rule.Required(), rule.Integer[int](), }, "bar": { rule.Required(), rule.String(), }, "Baz.foo": { rule.Required(), rule.Integer[int](), }, "Baz.InnerBar": { rule.Required(), rule.Slice(), }, // ... }, )Validates both a slice []any or an array [size]any. You can specify a list of rules for each element in given slice/array.
You can also pass pointer which will be automatically dereferenced.
Returns ErrorsBag with keys being the indices of input slice/array.
Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.
validator.ForSlice( []any{ "foo", "bar", }, validator.RulesMap{ rule.Required(), rule.String(), // ... }, )ForValue[In any](value In, rules []vr.Rule, options ...forValueValidatorOption), ForValueWithContext
Validates any value. You can specify a list of rules for given value.
If you pass a pointer, it will not be dereferenced.
Returns a slice of ValidationError.
Sets a pointer to a variable to which data will be exported after successful validation.
validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.Min(100), // ... }, )It is possible to validate nested objects (i.e.: slice, array, map or struct) using the dot notation:
- For slices and arrays: it refers to the index of element, e.g.: given
"slice": []int{1, 2, 3},,slice.1refers to the value2. - For maps: it refers to the element by given key, e.g.: given
"map": map[string]int{"foo": 1, "bar": 2, "baz": 3},,map.barrefers to the value2. - For structs: it refers to the field with same
validationtag or field name if tag is not present, e.g.: given"struct": {Foo: 1, Bar: 2, Baz: 3},,struct.Barrefers to the value2.
You can also validate every single value of slice and array by using * wildcard symbol.
type SomeRequest struct { SingleValue int Array [3]string Slice []string Map map[string]string Struct SomeRequestStruct SliceOfStructs []SomeRequestStruct MapOfStructs map[string]SomeRequestStruct SliceOfSlices [][]string } type SomeRequestStruct struct { InnerSingleValue int InnerArray [3]string InnerSlice []string InnerMap map[string]string InnerStruct SomeRequestInnerStruct } type SomeRequestInnerStruct struct { InnerInnerSingleValue int InnerInnerArray [3]string InnerInnerSlice []string InnerInnerMap map[string]string // ... } validator.ForStruct( SomeRequest{ SingleValue: 123, Array: [3]string{"foo", "bar", "baz"}, Slice: []string{"foo", "bar", "baz"}, Map: map[string]string{"foo": 1, "bar": 2}, Struct: SomeRequestStruct{ InnerSingleValue: 123, InnerArray: [3]string{"foo", "bar", "baz"}, InnerSlice: []string{"foo", "bar", "baz"}, InnerMap: map[string]string{"foo": 1, "bar": 2}, InnerStruct: SomeRequestInnerStruct{ // ... }, }, SliceOfStructs: []SomeRequestStruct{ { InnerSingleValue: 123, InnerSlice: []string{"foo", "bar", "baz"}, // ... }, // ... }, MapOfStructs: map[string]SomeRequestStruct{ "foo": { InnerSingleValue: 123, InnerSlice: []string{"foo", "bar", "baz"}, // ... }, "bar": { InnerSingleValue: 123, InnerSlice: []string{"foo", "bar", "baz"}, // ... }, // ... }, SliceOfSlices: [][]string{ {"foo", "bar", "baz"}, {"foo", "bar"}, }, }, validator.RulesMap{ // Rules for SomeRequest "SingleValue": { rule.Required(), rule.Integer[int](), }, "Array": { rule.Required(), rule.SliceOf[string](), }, "Array.*": { rule.Required(), rule.String(), }, "Slice": { rule.Required(), rule.SliceOf[string](), rule.Length(3), }, "Slice.*": { rule.Required(), rule.String(), }, "Map.foo": { rule.Required(), rule.Integer[int](), }, "Map.bar": { rule.Required(), rule.Integer[int](), }, // Rules for SomeRequestStruct "Struct.InnerSingleValue": { rule.Required(), rule.Integer[int](), }, "Struct.InnerArray": { rule.Required(), rule.SliceOf[string](), }, "Struct.InnerArray.*": { rule.Required(), rule.String(), }, "Struct.InnerSlice": { rule.Required(), rule.SliceOf[string](), rule.Length(3), }, "Struct.InnerSlice.*": { rule.Required(), rule.String(), }, "Struct.InnerMap.foo": { rule.Required(), rule.Integer[int](), }, "Struct.InnerMap.bar": { rule.Required(), rule.Integer[int](), }, // Rules for SomeRequest (complex examples) "SliceOfStructs": { rule.Required(), rule.Length(1), }, "SliceOfStructs.*.InnerSingleValue": { rule.Required(), rule.Integer[int](), }, "SliceOfStructs.*.InnerSlice.*": { rule.Required(), rule.String(), }, "MapOfStructs": { rule.Required(), }, "MapOfStructs.foo.InnerSingleValue": { rule.Required(), rule.Integer[int](), }, "MapOfStructs.foo.InnerSlice.*": { rule.Required(), rule.String(), }, "SliceOfSlices": { rule.Required(), rule.Slice(), rule.Length(2), }, "SliceOfSlices.*": { rule.Required(), rule.Slice(), rule.Min(2), }, "SliceOfSlices.*.*": { rule.Required(), rule.String(), }, // Non-existing keys "IDoNotExist": { rule.Required(), }, "IDoNotExist.*": { rule.Required(), }, "IDoNotExist.foo": { rule.Required(), }, "SliceOfSlices.*.*.*": { rule.Required(), }, "SliceOfSlices.*.*.foo": { rule.Required(), }, // ... }, )As a result you will get errors for each element separately, e.g.: SliceOfSlices.0.1, SliceOfSlices.3.0 etc. Note that some wildcards will not be matched. In that case you will get * for every unmatched nested element, e.g.: IDoNotExist.*, "IDoNotExist.foo", "SliceOfSlices.0.0.*", "SliceOfSlices.0.1.*", "SliceOfSlices.0.0.foo", "SliceOfSlices.0.1.foo" etc.
Some rules stop validation of given element once they fail (e.g.: Required since further validation makes no sense when value is not present).
You can also manually stop validation by using Bail pseudo-rule.
validator.ForValue( "123", validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.Bail(), // Next rules will not be checked if value is not an integer rule.Min(100), // ... }, )You can add validation rules based on custom conditions. It can be either simple boolean value using When or complex condition using WhenFunc.
Conditional rules can be nested to cover more complex requirements.
Once conditional rule is valid, its rules will be merged to the main list of rules. It also includes the Bail pseudo-rule which will stop any further validation of given rules list, no matter how deep it was defined.
This pseudo-rule allows for adding rules based on simple boolean value.
validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.When(true, rule.Min(100), rule.When(false, rule.Min(200), // ... ), rule.Bail(), // ... ), // ... }, )The example above becomes:
validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.Min(100), rule.Bail(), // ... // ... }, )This pseudo-rule allows for adding rules based on a custom logic.
It receives the context passed to the validator, the currently validated value and the original data passed to the validator.
validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.WhenFunc( func(ctx context.Context, value any, data any) bool { value, isNil := rule.Dereference(value) if isNil { return false } return value.(int)%2 == 1 }, rule.Min(100), rule.WhenFunc( func(_ context.Context, value any, _ any) bool { value, isNil := rule.Dereference(value) if isNil { return false } return value.(int)%2 == 0 }, rule.Min(200), // ... ), rule.Bail(), // ... ), // ... }, )The example above becomes:
validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.Min(100), rule.Bail(), // ... // ... }, )Common types:
integerType- Any integer type, i.e.:~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64.floatType- Any integer type, i.e.:~float32 | ~float64.numberType- Any number, i.e.:integerType | floatType.afterComparable- Any object that implements the following interface:
type afterComparable interface { After(time.Time) bool }afterOrEqualComparable- Any object that implements the following interface:
type afterOrEqualComparable interface { Equal(time.Time) bool After(time.Time) bool }beforeComparable- Any object that implements the following interface:
type beforeComparable interface { Before(time.Time) bool }beforeOrEqualComparable- Any object that implements the following interface:
type beforeOrEqualComparable interface { Equal(time.Time) bool Before(time.Time) bool }Comparator- Any object of the following type:
type Comparator func(x, y any) boolChecks whether a value is after after date.
Applies to:
nil: passes.afterComparable: passes only when a value is afterafterdate.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is after or equal to afterOrEqual date.
Applies to:
nil: passes.afterOrEqualComparable: passes only when a value is after or equal toafterOrEqualdate.any: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of array type.
Applies to:
nil: passes.any: passes only when a value is of array type or its pointer, any length and element type.
Modifies output:
No.
Bails:
Yes.
Checks and ensures that a value is of [n]Out type.
Applies to:
nil: passes.any: passes only when a value is of[n]Outtype or its pointer, any length.
Modifies output:
nil: nil array of[0]Out.any: input value.
Bails:
Yes.
Checks whether a value is before before date.
Applies to:
nil: passes.any: passes only when a value implementsbeforeComparableinterface and is beforebeforedate.
Modifies output:
No.
Bails:
No.
Checks whether a value is before or equal to beforeOrEqual date.
Applies to:
nil: passes.any: passes only when a value implementsbeforeOrEqualComparableinterface and is before or equal tobeforeOrEqualdate.
Modifies output:
No.
Bails:
No.
Checks whether a value is between min and max, inclusive.
Applies to:
nil: passes.numberType: checks if a value is betweenminandmax.string: checks if string's length is betweenminandmax.slice,array: checks if slice/array has betweenminandmaxelements.map: checks if map has at leastminandmaxkeys.
Modifies output:
No.
Bails:
No.
Checks whether a value is between min and max, exclusive.
Applies to:
nil: passes.numberType: checks if a value is betweenminandmax.string: checks if string's length is betweenminandmax.slice,array: checks if slice/array has betweenminandmaxelements.map: checks if map has at leastminandmaxkeys.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of bool type.
Applies to:
nil: any value.bool: any value.integerType: when a value equals to0or1.floatType: when a value equals to0.0or1.0.string: when string is convertible toboolaccording to thestrconv.ParseBoolfunction.any: fails.
Modifies output:
nil: returns*boolnil pinter.bool: input value.integerType:falsewhen0,truewhen1.floatType:falsewhen0.0,truewhen1.0.string: according to thestrconv.ParseBoolfunction.
Bails:
Yes.
Checks whether a value is of time.Time type, its pointer or valid date string in time.RFC3339Nano format.
Applies to:
nil: passes.time.Duration: any value.string: when string is convertible totime.Durationaccording to thetime.Parsefunction andtime.RFC3339Nanoformat.any: fails.
Modifies output:
nil: returns*time.Timenil pointer.time.Time: input value.string: according to thetime.Parsefunction.
Bails:
No.
Checks whether a value is of time.Time type, its pointer or valid date string in format format.
Applies to:
nil: passes.time.Duration: any value.string: when string is convertible totime.Durationaccording to thetime.Parsefunction andformatformat.any: fails.
Modifies output:
nil: returns*time.Timenil pinter.time.Time: input value.string: according to thetime.Parsefunction.
Bails:
No.
Checks whether a value is a string not ending with any of provided suffixes.
Applies to:
nil: passes.string: checks if string does not end with any of provided suffixes (case-sensitive).any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is a string not starting with any of provided prefixes.
Applies to:
nil: passes.string: checks if string does not start with any of provided prefixes (case-sensitive).any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is of time.Duration type, its pointer or valid duration string.
Applies to:
nil: passes.time.Duration: any value.string: when string is convertible totime.Durationaccording to thetime.ParseDurationfunction.any: fails.
Modifies output:
nil: returns*time.Durationnil pinter.time.Duration: returns unchanged value.string: according to thetime.ParseDurationfunction.
Bails:
No.
Checks whether a value is valid email address, according to the net/mail.ParseAddress function.
Applies to:
nil: passes.string: passes only when a value is successfully parsed bynet/mail.ParseAddressfunction.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is valid email address, according to the net/mail.ParseAddress function.
Applies to:
nil: passes.string: passes only when a value is successfully parsed bynet/mail.ParseAddressfunction.any: fails.
Modifies output:
nil: input value.string: unlike theEmail()rule, it returns the email address of the string. E.g. given a valueFoo Bar <foo@bar.baz> (some comment), the output will befoo@bar.baz.
Bails:
No.
Checks whether a value is a string ending with one of provided suffixes.
Applies to:
nil: passes.string: checks if string ends with one of provided suffixes (case-sensitive).any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is not empty when it is present.
Applies to:
nil: passes.*any: passes.!nil: checks if value is not a zero value.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of Out type or its pointer.
Applies to:
nil: passes.floatType: passes.any: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value exists in values.
Options:
InRuleWithComparator(comparator Comparator): sets custom elements comparator.comparatorreceives an input value and each element ofvalues, one at a time.InRuleWithoutAutoDereference(): disables automatic dereference of a value, i.e.valueswill be compared against the exact input value which may be a pointer.
Applies to:
nil: passes with auto-dereference enabled. Otherwise, fails when not present invalues.comparable: passes only when a value exists invalues, optionally using customcomparator.any: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of Out type or its pointer.
Applies to:
nil: passes.integerType: passes only when a value is ofOuttype or its pointer.any: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value is a string in IP v4 or v6 format.
Applies to:
nil: passes.string: checks if a value is in IP v4 or v6 format according to thenet.ParseIPfunction.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is exactly length.
Applies to:
nil: passes.string: checks if string's length is exactlylengthcharacters.slice,array: checks if slice/array has exactlylengthelements.map: checks if map has exactlylengthkeys.any: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of map type.
Applies to:
nil: passes.map: passes.any: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value is at most max.
Applies to:
nil: passes.numberType: checks if a value is at mostmax.string: checks if string's length is at mostmaxcharacters.slice,array: checks if slice/array has at mostmaxelements.map: checks if map has at mostmaxkeys.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is less than max.
Applies to:
nil: passes.numberType: checks if a value is less thanmax.string: checks if string's length is less thanmaxcharacters.slice,array: checks if slice/array has less thanmaxelements.map: checks if map has less thanmaxkeys.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is at least min.
Applies to:
nil: passes.numberType: checks if a value is at leastmin.string: checks if string's length is at leastmincharacters.slice,array: checks if slice/array has at leastminelements.map: checks if map has at leastminkeys.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is greater than min.
Applies to:
nil: passes.numberType: checks if a value is greater thanmin.string: checks if string's length is more thanmincharacters.slice,array: checks if slice/array has more thanminelements.map: checks if map has more thanminkeys.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value does not exist in values.
Options:
NotInRuleWithComparator(comparator Comparator): sets custom elements comparator.comparatorreceives an input value and each element ofvalues, one at a time.NotInRuleWithoutAutoDereference(): disables automatic dereference of a value, i.e.valueswill be compared against the exact input value which may be a pointer.
Applies to:
nil: passes with auto-dereference enabled. Otherwise, fails when present invalues.comparable: passes only when a value does not exist invalues, optionally using customcomparator.any: passes.
Modifies output:
No.
Bails:
No.
Checks whether a value does not match regex expression.
Applies to:
nil: passes.string: checks if string does not matchregexexpression.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is a numeric value or a string that can be converted to one.
Applies to:
nil: passes.numberType,complex64,complex128: passes.string: passes only when a value can be converted to number usingstrconv.ParseInt,strconv.ParseUint,strconv.ParseFloatandstrconv.ParseComplex, in that order.any: fails.
Modifies output:
nil: unchanged value.numberType,complex64,complex128: unchanged value.string: value converted to number.
Bails:
No.
Checks whether a value matches regex expression.
Applies to:
nil: passes.string: checks if string matchesregexexpression.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is not nil.
Applies to:
any: passes only when a value notnil.
Modifies output:
No.
Bails:
Yes.
Checks and ensures that a value is of slice type or its pointer.
Applies to:
nil: passes.slice: passes.any: fails.
Modifies output:
No.
Bails:
Yes.
Checks and ensures that a value is of []Out type.
Applies to:
nil: passes.slice: passes only when a value is of[]Outtype or its pointer, any length.any: fails.
Modifies output:
nil: returnsnilslice of[]Outtype.any: input value.
Bails:
Yes.
Checks whether a value is a string starting with one of provided prefixes.
Applies to:
nil: passes.string: checks if string starts with one of provided prefixes (case-sensitive).any: fails.
Modifies output:
No.
Bails:
No.
Checks and ensures that a value is of string type or its pointer.
Applies to:
nil: passes.string: passes.any: fails.
Modifies output:
nil: returns string nil pointer.string: input value.
Bails:
Yes.
Checks and ensures that a value is of struct type.
Applies to:
nil: passes.struct: passes.any: fails.
Modifies output:
No.
Bails:
Yes.
Checks whether a value is a valid URL string.
Applies to:
nil: passes.string: checks if string is a valid URL according tonet/url.ParseRequestURIfunction.any: fails.
Modifies output:
No.
Bails:
No.
Checks whether a value is a valid RFC 4122 (version 1, 3, 4 or 5) universally unique identifier (UUID).
Options:
UUIDRuleVersion1(): allows for UUIDv1.UUIDRuleVersion3(): allows for UUIDv3.UUIDRuleVersion4(): allows for UUIDv4.UUIDRuleVersion5(): allows for UUIDv5.UUIDRuleDisallowNilUUID(): disallows for nil UUID, i.e.00000000-0000-0000-0000-000000000000.
Applies to:
nil: passes.string: checks if string is a valid UUID.any: fails.
Modifies output:
No.
Bails:
No.
You can write a custom validator to cover custom needs. There are to ways of doing it: by implementing rule.Rule interface or by using rule.Custom rule.
A custom rule struct can also implement BailingRule interface so that it may stop further validation. There is also Bailer helper struct for that.
The rule.Custom rule can return any error. In that case, the error is added to the response. However, you can return a custom message by returning an error of error.ValidationError type.
Since the value can be anything, including pointer, there is a helper function rule.Dereference that returns the underlying value.
import ve "github.com/donatorsky/go-validator/error" type DividesByNValidationError struct { ve.BasicValidationError Divider int `json:"divider"` } func (e DividesByNValidationError) Error() string { return fmt.Sprintf("Cannot be divided by %d", e.Divider) } type DividesByN struct { divider int } func (r DividesByN) Apply(_ context.Context, value any, _ any) (any, ve.ValidationError) { v, isNil := Dereference(value) if isNil { return value, nil } if v.(int) % r.divider != 0 { return value, &DividesByNValidationError{ BasicValidationError: ve.BasicValidationError{ Rule: ve.TypeCustom, }, Divider: r.divider, } } return value, nil } validator.ForValue( 123, validator.RulesMap{ rule.Required(), rule.Integer[int](), rule.Custom(func(_ context.Context, value int, _ any) (newValue int, err error) { switch value % 2 { case 0: return value, nil case 1: return value + 1, nil } }), rule.Min(124), // passes because custom rule modified the value by adding 1 &DividesByN{divider: 3}, // fails // ... }, )Both error.ValidationError and error.ErrorsBag support JSON marshalling giving your application a handy way of reporting errors that occured.
package main import ( "encoding/json" "fmt" "os" "github.com/donatorsky/go-validator" "github.com/donatorsky/go-validator/rule" ) func main() { errorsBag := validator.ForMap(map[string]any{...}, validator.RulesMap{...}) fmt.Println(errorsBag) fmt.Println() printJSON(errorsBag) } func printJSON(data any) { encoder := json.NewEncoder(os.Stdout) encoder.SetIndent("", " ") _ = encoder.Encode(data) }Produces something similar to:
4 field(s) failed: int: [1][must be at least 150] child.id: [1][must be an int but is float64] child.roles.*: [1][is required] array.4: [2][must end with "oo"; does not exist in [Foo foo]}] { "int": [ { "rule": "MIN.NUMBER" "threshold": 150 } ], "child.id": [ { "rule": "INT" "expected_type": "int" "actual_type": "float64" } ], "child.roles.*": [ { "rule": "REQUIRED" } ], "array.4": [ { "rule": "ENDS_WITH" "end_part": "oo" }, { "rule": "IN", "values": [ "Foo", "foo" ] } ], }