ozzo-validation

repository·master·Indexed 26 days ago

https://github.com/go-ozzo/ozzo-validation

A Go package providing configurable and extensible data validation using programming constructs instead of struct tags. It supports validating simple values, structs, and maps, and includes a comprehensive set of built-in rules and a specialized 'is' sub-package for string and byte slice validation. Features include context-aware validation, custom rule implementation via the validation.Rule interface, and support for the validation.Validatable interface.

Tokens
11.5K
Snippets
25
Records
84
Agent score
84%

What's inside ozzo-validation

  1. Perform context-aware validation

    master

    Some validation rules depend dynamically on a context. To use them, implement the validation.RuleWithContext interface.

    To validate an arbitrary value with a context, use validation.ValidateWithContext(). The context.Context parameter is passed to rules implementing validation.RuleWithContext. If a rule only implements validation.Rule, that standard implementation is used instead.

    To validate struct fields with a context, use validation.ValidateStructWithContext().

    You can convert a function into a context-aware rule using validation.WithContext().

    rule := validation.WithContext(func(ctx context.Context, value interface{}) error {
    	if ctx.Value("secret") == value.(string) {
    	    return nil
    	}
    	return errors.New("value incorrect")
    })
    value := "xyz"
    ctx := context.WithValue(context.Background(), "secret", "example")
    err := validation.ValidateWithContext(ctx, value, rule)
    fmt.Println(err)
    // Output: value incorrect
  2. Create Custom Validation Rules

    master

    To create a custom rule, implement the validation.Rule interface (which requires a Validate(value interface{}) error method).

    If you have a function with the signature func(interface{}) error, you can convert it into a rule using validation.By(yourFunc).

    For rules requiring parameters, use a closure that returns a validation.RuleFunc.

    // Using validation.By with a simple function
    func checkAbc(value interface{}) error {
    	s, _ := value.(string)
    	if s != "abc" {
    		return errors.New("must be abc")
    	}
    	return nil
    }
    err := validation.Validate("xyz", validation.By(checkAbc))
    
    // Using a closure for parameterized rules
    func stringEquals(str string) validation.RuleFunc {
    	return func(value interface{}) error {
    		s, _ := value.(string)
    	    if s != str {
    			return errors.New("unexpected string")
    		}
    		return nil
    	}
    }
    err := validation.Validate("xyz", validation.By(stringEquals("abc")))
  3. Update error message placeholders when upgrading from 3.x to 4.x

    master

    When upgrading from version 3.x to 4.x, if you use custom error messages for built-in validation rules, you must replace the %v printf-style placeholders with Go template variable placeholders.

    Use the following mappings:

    • Length: Use {{.min}} and {{.max}}.
    • Min: Use {{.threshold}}.
    • Max: Use {{.threshold}}.
    • MultipleOf: Use {{.base}}.
    // 3.x
    lengthRule := validation.Length(2,10).Error("the length must be between %v and %v")
    
    // 4.x
    lengthRule := validation.Length(2,10).Error("the length must be between {{.min}} and {{.max}}")
  4. Migrate rule validation from 2.x to 3.x

    master

    In version 3.x, instead of creating a validation.Rules object and calling .Validate(data), call validation.Validate(data, rules...) directly with the rules as arguments.

    data := "example"
    
    // 2.x usage
    rules := validation.Rules{
    	validation.Required,      
    	validation.Length(5, 100),
    	is.URL,                   
    }
    err := rules.Validate(data)
    
    // 3.x usage
    err := validation.Validate(data,
    	validation.Required,      
    	validation.Length(5, 100),
    	is.URL,                   
    )
  5. Migrate struct validation from 2.x to 3.x

    master

    In version 3.x, StructRules is deprecated. Use validation.ValidateStruct() instead. This new method requires a pointer to the struct and a list of validation.Field() calls that map the struct field to its rules.

    // 2.x usage
    err := validation.StructRules{}.
    	Add("Street", validation.Required, validation.Length(5, 50)).
    	Add("City", validation.Required, validation.Length(5, 50)).
    	Add("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))).
    	Add("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))).
    	Validate(a)
    
    // 3.x usage
    err := validation.ValidateStruct(&a,
    	validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
    	validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
    	validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
    	validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
    )
  6. Manually construct validation.Errors

    master

    If you want full control over error keys instead of relying on ValidateStruct's automatic tag detection, you can manually build a validation.Errors map and call .Filter() to remove any nil entries (successful validations).

    err := validation.Errors{
    	"name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)),
    	"email": validation.Validate(c.Email, validation.Required, is.Email),
    	"zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
    }.Filter()
    
    if err != nil {
        fmt.Println(err)
    }
  7. Group Validation Rules

    master

    For better maintainability, you can group multiple rules into a slice of validation.Rule and reuse them across different fields using the ... spread operator.

    var NameRule = []validation.Rule{
    	validation.Required,
    	validation.Length(5, 20),
    }
    
    type User struct {
    	FirstName string
    	LastName  string
    }
    
    func (u User) Validate() error {
    	return validation.ValidateStruct(&u,
    		validation.Field(&u.FirstName, NameRule...),
    		validation.Field(&u.LastName, NameRule...),
    	)
    }
  8. Validate a map with validation.Map()

    master

    For dynamic data in maps, use validation.Map() within validation.Validate(). You can define rules for specific keys using validation.Key(). Keys are validated in the order specified, and validation continues to the next key even if a rule fails for the current one.

    c := map[string]interface{}{
    	"Name":  "Qiang Xue",
    	"Email": "q",
    	"Address": map[string]interface{}{
    		"Street": "123",
    		"City":   "Unknown",
    		"State":  "Virginia",
    		"Zip":    "12345",
    	},
    }
    
    err := validation.Validate(c,
    	validation.Map(
    		// Name cannot be empty, and the length must be between 5 and 20.
    		validation.Key("Name", validation.Required, validation.Length(5, 20)),
    		// Email cannot be empty and should be in a valid email format.
    		validation.Key("Email", validation.Required, is.Email),
    		// Validate Address using its own validation rules
    		validation.Key("Address", validation.Map(
    			validation.Key("Street", validation.Required, validation.Length(5, 50)),
    			validation.Key("City", validation.Required, validation.Length(5, 50)),
    			validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
    			validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
    		)),
    	),
    )
  9. Validate a struct with validation.ValidateStruct()

    master

    To validate the fields of a struct, use validation.ValidateStruct().

    Important Requirements:

    • Pass a pointer to the struct to ValidateStruct.
    • Use a pointer to the struct field when calling validation.Field.

    Rules are evaluated in the order specified. If a field's rule fails, an error is recorded for that field, and validation continues with the next field.

    type Address struct {
    	Street string
    	City   string
    	State  string
    	Zip    string
    }
    
    func (a Address) Validate() error {
    	return validation.ValidateStruct(&a,
    		// Street cannot be empty, and the length must between 5 and 50
    		validation.Field(&a.Street, validation.Required, validation.Length(5, 50)),
    		// City cannot be empty, and the length must between 5 and 50
    		validation.Field(&a.City, validation.Required, validation.Length(5, 50)),
    		// State cannot be empty, and must be a string consisting of two letters in upper case
    		validation.Field(&a.State, validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
    		// State cannot be empty, and must be a string consisting of five digits
    		validation.Field(&a.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
    	)
    }
  10. Validate Maps, Slices, and Arrays of Validatables

    master

    When validating an iterable (map, slice, or array) where the elements implement validation.Validatable, validation.Validate will automatically call Validate() on every non-nil element. Errors are returned as validation.Errors, mapping the element's key or index to its specific validation error.

    To apply specific rules to every element in an iterable, use the validation.Each rule.

    // Validating a slice of validatables
    addresses := []Address{
    	Address{State: "MD", Zip: "12345"},
    	Address{Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"},
    	Address{City: "Unknown", State: "NC", Zip: "123"},
    }
    err := validation.Validate(addresses)
    // Output: 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).
    
    // Using Each to validate elements of a slice
    type Customer struct {
        Name      string
        Emails    []string
    }
    
    func (c Customer) Validate() error {
        return validation.ValidateStruct(&c,
    		validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
    		validation.Field(&c.Emails, validation.Each(is.Email)),
        )
    }
  11. Differentiate internal errors using validation.InternalError

    master

    Internal errors (e.g., a validator failing due to a network issue rather than invalid data) should be wrapped using validation.NewInternalError(). You can detect these errors by type-asserting the returned error to validation.InternalError and calling the .InternalError() method.

    if err := a.Validate(); err != nil {
    	if e, ok := err.(validation.InternalError); ok {
    		// an internal error happened
    		fmt.Println(e.InternalError())
    	}
    }