Skip to content

A Laravel-like data validator for Go.

License

Notifications You must be signed in to change notification settings

donatorsky/go-validator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Validator

A Laravel-like data validator for Go.

GitHub license Build codecov

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.

Installation

go get github.com/donatorsky/go-validator

The library is still a work in progress. More details soon.

Available validators

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().

ForMap(data map[string]any, rules RulesMap, options ...forMapValidatorOption), ForMapWithContext

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.

Options

ForMapWithDataCollector(collector DataCollector)

Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.

Example

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(),
        },

        // ...
    },
)

ForStruct(data any, rules RulesMap, options ...forStructValidatorOption), ForStructWithContext

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.

Options

ForStructWithDataCollector(collector DataCollector)

Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.

Example

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(),
        },

        // ...
    },
)

ForSlice(data any, rules []vr.Rule, options ...forSliceValidatorOption), ForSliceWithContext

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.

Options

ForSliceWithDataCollector(collector DataCollector)

Sets a DataCollector instance to be used while validating data which will collect all successfully validated data.

Example

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.

Options

ForValueWithValueExporter[Out any](value *Out)

Sets a pointer to a variable to which data will be exported after successful validation.

Example

validator.ForValue(
    123,
    validator.RulesMap{
        rule.Required(),
        rule.Integer[int](),
        rule.Min(100),
        // ...
    },
)

Validation of nested objects

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.1 refers to the value 2.
  • For maps: it refers to the element by given key, e.g.: given "map": map[string]int{"foo": 1, "bar": 2, "baz": 3},, map.bar refers to the value 2.
  • For structs: it refers to the field with same validation tag or field name if tag is not present, e.g.: given "struct": {Foo: 1, Bar: 2, Baz: 3},, struct.Bar refers to the value 2.

You can also validate every single value of slice and array by using * wildcard symbol.

Example

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.

Stopping validation on first error

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.

Example

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),
        // ...
    },
)

Conditional validation

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.

When

This pseudo-rule allows for adding rules based on simple boolean value.

Example

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(),
        // ...
        // ...
    },
)

WhenFunc

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.

Example

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(),
        // ...
        // ...
    },
)

Available rules

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) bool

After(after time.Time)

Checks whether a value is after after date.

Applies to:

  • nil: passes.
  • afterComparable: passes only when a value is after after date.
  • any: fails.

Modifies output:

No.

Bails:

No.

AfterOrEqual(afterOrEqual time.Time)

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 to afterOrEqual date.
  • any: fails.

Modifies output:

No.

Bails:

No.

Array()

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.

ArrayOf[Out any]()

Checks and ensures that a value is of [n]Out type.

Applies to:

  • nil: passes.
  • any: passes only when a value is of [n]Out type or its pointer, any length.

Modifies output:

  • nil: nil array of [0]Out.
  • any: input value.

Bails:

Yes.

Before(before time.Time)

Checks whether a value is before before date.

Applies to:

  • nil: passes.
  • any: passes only when a value implements beforeComparable interface and is before before date.

Modifies output:

No.

Bails:

No.

BeforeOrEqual(beforeOrEqual time.Time)

Checks whether a value is before or equal to beforeOrEqual date.

Applies to:

  • nil: passes.
  • any: passes only when a value implements beforeOrEqualComparable interface and is before or equal to beforeOrEqual date.

Modifies output:

No.

Bails:

No.

Between[T numberType](min, max T)

Checks whether a value is between min and max, inclusive.

Applies to:

  • nil: passes.
  • numberType: checks if a value is between min and max.
  • string: checks if string's length is between min and max.
  • slice, array: checks if slice/array has between min and max elements.
  • map: checks if map has at least min and max keys.

Modifies output:

No.

Bails:

No.

BetweenExclusive[T numberType](min, max T)

Checks whether a value is between min and max, exclusive.

Applies to:

  • nil: passes.
  • numberType: checks if a value is between min and max.
  • string: checks if string's length is between min and max.
  • slice, array: checks if slice/array has between min and max elements.
  • map: checks if map has at least min and max keys.

Modifies output:

No.

Bails:

No.

Boolean()

Checks and ensures that a value is of bool type.

Applies to:

  • nil: any value.
  • bool: any value.
  • integerType: when a value equals to 0 or 1.
  • floatType: when a value equals to 0.0 or 1.0.
  • string: when string is convertible to bool according to the strconv.ParseBool function.
  • any: fails.

Modifies output:

  • nil: returns *bool nil pinter.
  • bool: input value.
  • integerType: false when 0, true when 1.
  • floatType: false when 0.0, true when 1.0.
  • string: according to the strconv.ParseBool function.

Bails:

Yes.

Date()

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 to time.Duration according to the time.Parse function and time.RFC3339Nano format.
  • any: fails.

Modifies output:

  • nil: returns *time.Time nil pointer.
  • time.Time: input value.
  • string: according to the time.Parse function.

Bails:

No.

DateFormat(format string)

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 to time.Duration according to the time.Parse function and format format.
  • any: fails.

Modifies output:

  • nil: returns *time.Time nil pinter.
  • time.Time: input value.
  • string: according to the time.Parse function.

Bails:

No.

DoesntEndWith(suffix string, suffixes ...string)

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.

DoesntStartWith(prefix string, prefixes ...string)

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.

Duration()

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 to time.Duration according to the time.ParseDuration function.
  • any: fails.

Modifies output:

  • nil: returns *time.Duration nil pinter.
  • time.Duration: returns unchanged value.
  • string: according to the time.ParseDuration function.

Bails:

No.

Email()

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 by net/mail.ParseAddress function.
  • any: fails.

Modifies output:

No.

Bails:

No.

EmailAddress()

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 by net/mail.ParseAddress function.
  • any: fails.

Modifies output:

  • nil: input value.
  • string: unlike the Email() rule, it returns the email address of the string. E.g. given a value Foo Bar <foo@bar.baz> (some comment), the output will be foo@bar.baz.

Bails:

No.

EndsWith(suffix string)

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.

Filled()

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.

Float[Out floatType]()

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.

In[T comparable](values []T, options ...inRuleOption)

Checks whether a value exists in values.

Options:

  • InRuleWithComparator(comparator Comparator): sets custom elements comparator. comparator receives an input value and each element of values, one at a time.
  • InRuleWithoutAutoDereference(): disables automatic dereference of a value, i.e. values will 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 in values.
  • comparable: passes only when a value exists in values, optionally using custom comparator.
  • any: fails.

Modifies output:

No.

Bails:

No.

Integer[Out integerType]()

Checks and ensures that a value is of Out type or its pointer.

Applies to:

  • nil: passes.
  • integerType: passes only when a value is of Out type or its pointer.
  • any: fails.

Modifies output:

No.

Bails:

Yes.

IP()

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 the net.ParseIP function.
  • any: fails.

Modifies output:

No.

Bails:

No.

Length[T integerType](length T)

Checks whether a value is exactly length.

Applies to:

  • nil: passes.
  • string: checks if string's length is exactly length characters.
  • slice, array: checks if slice/array has exactly length elements.
  • map: checks if map has exactly length keys.
  • any: fails.

Modifies output:

No.

Bails:

No.

Map()

Checks and ensures that a value is of map type.

Applies to:

  • nil: passes.
  • map: passes.
  • any: fails.

Modifies output:

No.

Bails:

Yes.

Max[T numberType](max T)

Checks whether a value is at most max.

Applies to:

  • nil: passes.
  • numberType: checks if a value is at most max .
  • string: checks if string's length is at most max characters.
  • slice, array: checks if slice/array has at most max elements.
  • map: checks if map has at most max keys.
  • any: fails.

Modifies output:

No.

Bails:

No.

MaxExclusive[T numberType](max T)

Checks whether a value is less than max.

Applies to:

  • nil: passes.
  • numberType: checks if a value is less than max .
  • string: checks if string's length is less than max characters.
  • slice, array: checks if slice/array has less than max elements.
  • map: checks if map has less than max keys.
  • any: fails.

Modifies output:

No.

Bails:

No.

Min[T numberType](min T)

Checks whether a value is at least min.

Applies to:

  • nil: passes.
  • numberType: checks if a value is at least min.
  • string: checks if string's length is at least min characters.
  • slice, array: checks if slice/array has at least min elements.
  • map: checks if map has at least min keys.
  • any: fails.

Modifies output:

No.

Bails:

No.

MinExclusive[T numberType](min T)

Checks whether a value is greater than min.

Applies to:

  • nil: passes.
  • numberType: checks if a value is greater than min.
  • string: checks if string's length is more than min characters.
  • slice, array: checks if slice/array has more than min elements.
  • map: checks if map has more than min keys.
  • any: fails.

Modifies output:

No.

Bails:

No.

NotIn[T comparable](values []T, options ...notInRuleOption)

Checks whether a value does not exist in values.

Options:

  • NotInRuleWithComparator(comparator Comparator): sets custom elements comparator. comparator receives an input value and each element of values, one at a time.
  • NotInRuleWithoutAutoDereference(): disables automatic dereference of a value, i.e. values will be compared against the exact input value which may be a pointer.

Applies to:

  • nil: passes with auto-dereference enabled. Otherwise, fails when present in values.
  • comparable: passes only when a value does not exist in values, optionally using custom comparator.
  • any: passes.

Modifies output:

No.

Bails:

No.

NotRegex(regex *regexp.Regexp)

Checks whether a value does not match regex expression.

Applies to:

  • nil: passes.
  • string: checks if string does not match regex expression.
  • any: fails.

Modifies output:

No.

Bails:

No.

Numeric()

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 using strconv.ParseInt, strconv.ParseUint, strconv.ParseFloat and strconv.ParseComplex, in that order.
  • any: fails.

Modifies output:

  • nil: unchanged value.
  • numberType, complex64, complex128: unchanged value.
  • string: value converted to number.

Bails:

No.

Regex(regex *regexp.Regexp)

Checks whether a value matches regex expression.

Applies to:

  • nil: passes.
  • string: checks if string matches regex expression.
  • any: fails.

Modifies output:

No.

Bails:

No.

Required()

Checks whether a value is not nil.

Applies to:

  • any: passes only when a value not nil.

Modifies output:

No.

Bails:

Yes.

Slice()

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.

SliceOf[Out any]()

Checks and ensures that a value is of []Out type.

Applies to:

  • nil: passes.
  • slice: passes only when a value is of []Out type or its pointer, any length.
  • any: fails.

Modifies output:

  • nil: returns nil slice of []Out type.
  • any: input value.

Bails:

Yes.

StartsWith(prefix string, prefixes ...string)

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.

String()

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.

Struct()

Checks and ensures that a value is of struct type.

Applies to:

  • nil: passes.
  • struct: passes.
  • any: fails.

Modifies output:

No.

Bails:

Yes.

URL()

Checks whether a value is a valid URL string.

Applies to:

  • nil: passes.
  • string: checks if string is a valid URL according to net/url.ParseRequestURI function.
  • any: fails.

Modifies output:

No.

Bails:

No.

UUID(options ...uuidRuleOption)

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.

Custom validation

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.

Example

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
        // ...
    },
)

JSON formatting

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"
      ]
    }
  ],
}