Code Monkey home page Code Monkey logo

ozzo-validation's Introduction

ozzo-validation

GoDoc Build Status Coverage Status Go Report

Description

ozzo-validation is a Go package that provides configurable and extensible data validation capabilities. It has the following features:

  • use normal programming constructs rather than error-prone struct tags to specify how data should be validated.
  • can validate data of different types, e.g., structs, strings, byte slices, slices, maps, arrays.
  • can validate custom data types as long as they implement the Validatable interface.
  • can validate data types that implement the sql.Valuer interface (e.g. sql.NullString).
  • customizable and well-formatted validation errors.
  • error code and message translation support.
  • provide a rich set of validation rules right out of box.
  • extremely easy to create and use custom validation rules.

For an example on how this library is used in an application, please refer to go-rest-api which is a starter kit for building RESTful APIs in Go.

Requirements

Go 1.13 or above.

Getting Started

The ozzo-validation package mainly includes a set of validation rules and two validation methods. You use validation rules to describe how a value should be considered valid, and you call either validation.Validate() or validation.ValidateStruct() to validate the value.

Installation

Run the following command to install the package:

go get github.com/go-ozzo/ozzo-validation

Validating a Simple Value

For a simple value, such as a string or an integer, you may use validation.Validate() to validate it. For example,

package main

import (
	"fmt"

	"github.com/go-ozzo/ozzo-validation/v4"
	"github.com/go-ozzo/ozzo-validation/v4/is"
)

func main() {
	data := "example"
	err := validation.Validate(data,
		validation.Required,       // not empty
		validation.Length(5, 100), // length between 5 and 100
		is.URL,                    // is a valid URL
	)
	fmt.Println(err)
	// Output:
	// must be a valid URL
}

The method validation.Validate() will run through the rules in the order that they are listed. If a rule fails the validation, the method will return the corresponding error and skip the rest of the rules. The method will return nil if the value passes all validation rules.

Validating a Struct

For a struct value, you usually want to check if its fields are valid. For example, in a RESTful application, you may unmarshal the request payload into a struct and then validate the struct fields. If one or multiple fields are invalid, you may want to get an error describing which fields are invalid. You can use validation.ValidateStruct() to achieve this purpose. A single struct can have rules for multiple fields, and a field can be associated with multiple rules. For example,

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}$"))),
	)
}

a := Address{
    Street: "123",
    City:   "Unknown",
    State:  "Virginia",
    Zip:    "12345",
}

err := a.Validate()
fmt.Println(err)
// Output:
// Street: the length must be between 5 and 50; State: must be in a valid format.

Note that when calling validation.ValidateStruct to validate a struct, you should pass to the method a pointer to the struct instead of the struct itself. Similarly, when calling validation.Field to specify the rules for a struct field, you should use a pointer to the struct field.

When the struct validation is performed, the fields are validated in the order they are specified in ValidateStruct. And when each field is validated, its rules are also evaluated in the order they are associated with the field. If a rule fails, an error is recorded for that field, and the validation will continue with the next field.

Validating a Map

Sometimes you might need to work with dynamic data stored in maps rather than a typed model. You can use validation.Map() in this situation. A single map can have rules for multiple keys, and a key can be associated with multiple rules. For example,

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(
			// Street cannot be empty, and the length must between 5 and 50
			validation.Key("Street", validation.Required, validation.Length(5, 50)),
			// City cannot be empty, and the length must between 5 and 50
			validation.Key("City", validation.Required, validation.Length(5, 50)),
			// State cannot be empty, and must be a string consisting of two letters in upper case
			validation.Key("State", validation.Required, validation.Match(regexp.MustCompile("^[A-Z]{2}$"))),
			// State cannot be empty, and must be a string consisting of five digits
			validation.Key("Zip", validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
		)),
	),
)
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format; Street: the length must be between 5 and 50.); Email: must be a valid email address.

When the map validation is performed, the keys are validated in the order they are specified in Map. And when each key is validated, its rules are also evaluated in the order they are associated with the key. If a rule fails, an error is recorded for that key, and the validation will continue with the next key.

Validation Errors

The validation.ValidateStruct method returns validation errors found in struct fields in terms of validation.Errors which is a map of fields and their corresponding errors. Nil is returned if validation passes.

By default, validation.Errors uses the struct tags named json to determine what names should be used to represent the invalid fields. The type also implements the json.Marshaler interface so that it can be marshaled into a proper JSON object. For example,

type Address struct {
	Street string `json:"street"`
	City   string `json:"city"`
	State  string `json:"state"`
	Zip    string `json:"zip"`
}

// ...perform validation here...

err := a.Validate()
b, _ := json.Marshal(err)
fmt.Println(string(b))
// Output:
// {"street":"the length must be between 5 and 50","state":"must be in a valid format"}

You may modify validation.ErrorTag to use a different struct tag name.

If you do not like the magic that ValidateStruct determines error keys based on struct field names or corresponding tag values, you may use the following alternative approach:

c := Customer{
	Name:  "Qiang Xue",
	Email: "q",
	Address: Address{
		State:  "Virginia",
	},
}

err := validation.Errors{
	"name": validation.Validate(c.Name, validation.Required, validation.Length(5, 20)),
	"email": validation.Validate(c.Name, validation.Required, is.Email),
	"zip": validation.Validate(c.Address.Zip, validation.Required, validation.Match(regexp.MustCompile("^[0-9]{5}$"))),
}.Filter()
fmt.Println(err)
// Output:
// email: must be a valid email address; zip: cannot be blank.

In the above example, we build a validation.Errors by a list of names and the corresponding validation results. At the end we call Errors.Filter() to remove from Errors all nils which correspond to those successful validation results. The method will return nil if Errors is empty.

The above approach is very flexible as it allows you to freely build up your validation error structure. You can use it to validate both struct and non-struct values. Compared to using ValidateStruct to validate a struct, it has the drawback that you have to redundantly specify the error keys while ValidateStruct can automatically find them out.

Internal Errors

Internal errors are different from validation errors in that internal errors are caused by malfunctioning code (e.g. a validator making a remote call to validate some data when the remote service is down) rather than the data being validated. When an internal error happens during data validation, you may allow the user to resubmit the same data to perform validation again, hoping the program resumes functioning. On the other hand, if data validation fails due to data error, the user should generally not resubmit the same data again.

To differentiate internal errors from validation errors, when an internal error occurs in a validator, wrap it into validation.InternalError by calling validation.NewInternalError(). The user of the validator can then check if a returned error is an internal error or not. For example,

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

Validatable Types

A type is validatable if it implements the validation.Validatable interface.

When validation.Validate is used to validate a validatable value, if it does not find any error with the given validation rules, it will further call the value's Validate() method.

Similarly, when validation.ValidateStruct is validating a struct field whose type is validatable, it will call the field's Validate method after it passes the listed rules.

Note: When implementing validation.Validatable, do not call validation.Validate() to validate the value in its original type because this will cause infinite loops. For example, if you define a new type MyString as string and implement validation.Validatable for MyString, within the Validate() function you should cast the value to string first before calling validation.Validate() to validate it.

In the following example, the Address field of Customer is validatable because Address implements validation.Validatable. Therefore, when validating a Customer struct with validation.ValidateStruct, validation will "dive" into the Address field.

type Customer struct {
	Name    string
	Gender  string
	Email   string
	Address Address
}

func (c Customer) Validate() error {
	return validation.ValidateStruct(&c,
		// Name cannot be empty, and the length must be between 5 and 20.
		validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
		// Gender is optional, and should be either "Female" or "Male".
		validation.Field(&c.Gender, validation.In("Female", "Male")),
		// Email cannot be empty and should be in a valid email format.
		validation.Field(&c.Email, validation.Required, is.Email),
		// Validate Address using its own validation rules
		validation.Field(&c.Address),
	)
}

c := Customer{
	Name:  "Qiang Xue",
	Email: "q",
	Address: Address{
		Street: "123 Main Street",
		City:   "Unknown",
		State:  "Virginia",
		Zip:    "12345",
	},
}

err := c.Validate()
fmt.Println(err)
// Output:
// Address: (State: must be in a valid format.); Email: must be a valid email address.

Sometimes, you may want to skip the invocation of a type's Validate method. To do so, simply associate a validation.Skip rule with the value being validated.

Maps/Slices/Arrays of Validatables

When validating an iterable (map, slice, or array), whose element type implements the validation.Validatable interface, the validation.Validate method will call the Validate method of every non-nil element. The validation errors of the elements will be returned as validation.Errors which maps the keys of the invalid elements to their corresponding validation errors. For example,

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)
fmt.Println(err)
// Output:
// 0: (City: cannot be blank; Street: cannot be blank.); 2: (Street: cannot be blank; Zip: must be in a valid format.).

When using validation.ValidateStruct to validate a struct, the above validation procedure also applies to those struct fields which are map/slices/arrays of validatables.

Each

The Each validation rule allows you to apply a set of rules to each element of an array, slice, or map.

type Customer struct {
    Name      string
    Emails    []string
}

func (c Customer) Validate() error {
    return validation.ValidateStruct(&c,
        // Name cannot be empty, and the length must be between 5 and 20.
		validation.Field(&c.Name, validation.Required, validation.Length(5, 20)),
		// Emails are optional, but if given must be valid.
		validation.Field(&c.Emails, validation.Each(is.Email)),
    )
}

c := Customer{
    Name:   "Qiang Xue",
    Emails: []Email{
        "[email protected]",
        "invalid",
    },
}

err := c.Validate()
fmt.Println(err)
// Output:
// Emails: (1: must be a valid email address.).

Pointers

When a value being validated is a pointer, most validation rules will validate the actual value pointed to by the pointer. If the pointer is nil, these rules will skip the validation.

An exception is the validation.Required and validation.NotNil rules. When a pointer is nil, they will report a validation error.

Types Implementing sql.Valuer

If a data type implements the sql.Valuer interface (e.g. sql.NullString), the built-in validation rules will handle it properly. In particular, when a rule is validating such data, it will call the Value() method and validate the returned value instead.

Required vs. Not Nil

When validating input values, there are two different scenarios about checking if input values are provided or not.

In the first scenario, an input value is considered missing if it is not entered or it is entered as a zero value (e.g. an empty string, a zero integer). You can use the validation.Required rule in this case.

In the second scenario, an input value is considered missing only if it is not entered. A pointer field is usually used in this case so that you can detect if a value is entered or not by checking if the pointer is nil or not. You can use the validation.NotNil rule to ensure a value is entered (even if it is a zero value).

Embedded Structs

The validation.ValidateStruct method will properly validate a struct that contains embedded structs. In particular, the fields of an embedded struct are treated as if they belong directly to the containing struct. For example,

type Employee struct {
	Name string
}

type Manager struct {
	Employee
	Level int
}

m := Manager{}
err := validation.ValidateStruct(&m,
	validation.Field(&m.Name, validation.Required),
	validation.Field(&m.Level, validation.Required),
)
fmt.Println(err)
// Output:
// Level: cannot be blank; Name: cannot be blank.

In the above code, we use &m.Name to specify the validation of the Name field of the embedded struct Employee. And the validation error uses Name as the key for the error associated with the Name field as if Name a field directly belonging to Manager.

If Employee implements the validation.Validatable interface, we can also use the following code to validate Manager, which generates the same validation result:

func (e Employee) Validate() error {
	return validation.ValidateStruct(&e,
		validation.Field(&e.Name, validation.Required),
	)
}

err := validation.ValidateStruct(&m,
	validation.Field(&m.Employee),
	validation.Field(&m.Level, validation.Required),
)
fmt.Println(err)
// Output:
// Level: cannot be blank; Name: cannot be blank.

Conditional Validation

Sometimes, we may want to validate a value only when certain condition is met. For example, we want to ensure the unit struct field is not empty only when the quantity field is not empty; or we may want to ensure either email or phone is provided. The so-called conditional validation can be achieved with the help of validation.When. The following code implements the aforementioned examples:

result := validation.ValidateStruct(&a,
    validation.Field(&a.Unit, validation.When(a.Quantity != "", validation.Required).Else(validation.Nil)),
    validation.Field(&a.Phone, validation.When(a.Email == "", validation.Required.Error('Either phone or Email is required.')),
    validation.Field(&a.Email, validation.When(a.Phone == "", validation.Required.Error('Either phone or Email is required.')),
)

Note that validation.When and validation.When.Else can take a list of validation rules. These rules will be executed only when the condition is true (When) or false (Else).

The above code can also be simplified using the shortcut validation.Required.When:

result := validation.ValidateStruct(&a,
    validation.Field(&a.Unit, validation.Required.When(a.Quantity != ""), validation.Nil.When(a.Quantity == "")),
    validation.Field(&a.Phone, validation.Required.When(a.Email == "").Error('Either phone or Email is required.')),
    validation.Field(&a.Email, validation.Required.When(a.Phone == "").Error('Either phone or Email is required.')),
)

Customizing Error Messages

All built-in validation rules allow you to customize their error messages. To do so, simply call the Error() method of the rules. For example,

data := "2123"
err := validation.Validate(data,
	validation.Required.Error("is required"),
	validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("must be a string with five digits"),
)
fmt.Println(err)
// Output:
// must be a string with five digits

You can also customize the pre-defined error(s) of a built-in rule such that the customization applies to every instance of the rule. For example, the Required rule uses the pre-defined error ErrRequired. You can customize it during the application initialization:

validation.ErrRequired = validation.ErrRequired.SetMessage("the value is required") 

Error Code and Message Translation

The errors returned by the validation rules implement the Error interface which contains the Code() method to provide the error code information. While the message of a validation error is often customized, the code is immutable. You can use error code to programmatically check a validation error or look for the translation of the corresponding message.

If you are developing your own validation rules, you can use validation.NewError() to create a validation error which implements the aforementioned Error interface.

Creating Custom Rules

Creating a custom rule is as simple as implementing the validation.Rule interface. The interface contains a single method as shown below, which should validate the value and return the validation error, if any:

// Validate validates a value and returns an error if validation fails.
Validate(value interface{}) error

If you already have a function with the same signature as shown above, you can call validation.By() to turn it into a validation rule. For example,

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))
fmt.Println(err)
// Output: must be abc

If your validation function takes additional parameters, you can use the following closure trick:

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")))
fmt.Println(err)
// Output: unexpected string

Rule Groups

When a combination of several rules are used in multiple places, you may use the following trick to create a rule group so that your code is more maintainable.

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

In the above example, we create a rule group NameRule which consists of two validation rules. We then use this rule group to validate both FirstName and LastName.

Context-aware Validation

While most validation rules are self-contained, some rules may depend dynamically on a context. A rule may implement the validation.RuleWithContext interface to support the so-called context-aware validation.

To validate an arbitrary value with a context, call validation.ValidateWithContext(). The context.Conext parameter will be passed along to those rules that implement validation.RuleWithContext.

To validate the fields of a struct with a context, call validation.ValidateStructWithContext().

You can define a context-aware rule from scratch by implementing both validation.Rule and validation.RuleWithContext. You can also use validation.WithContext() to turn a function into a context-aware rule. For example,

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

When performing context-aware validation, if a rule does not implement validation.RuleWithContext, its validation.Rule will be used instead.

Built-in Validation Rules

The following rules are provided in the validation package:

  • In(...interface{}): checks if a value can be found in the given list of values.
  • NotIn(...interface{}): checks if a value is NOT among the given list of values.
  • Length(min, max int): checks if the length of a value is within the specified range. This rule should only be used for validating strings, slices, maps, and arrays.
  • RuneLength(min, max int): checks if the length of a string is within the specified range. This rule is similar as Length except that when the value being validated is a string, it checks its rune length instead of byte length.
  • Min(min interface{}) and Max(max interface{}): checks if a value is within the specified range. These two rules should only be used for validating int, uint, float and time.Time types.
  • Match(*regexp.Regexp): checks if a value matches the specified regular expression. This rule should only be used for strings and byte slices.
  • Date(layout string): checks if a string value is a date whose format is specified by the layout. By calling Min() and/or Max(), you can check additionally if the date is within the specified range.
  • Required: checks if a value is not empty (neither nil nor zero).
  • NotNil: checks if a pointer value is not nil. Non-pointer values are considered valid.
  • NilOrNotEmpty: checks if a value is a nil pointer or a non-empty value. This differs from Required in that it treats a nil pointer as valid.
  • Nil: checks if a value is a nil pointer.
  • Empty: checks if a value is empty. nil pointers are considered valid.
  • Skip: this is a special rule used to indicate that all rules following it should be skipped (including the nested ones).
  • MultipleOf: checks if the value is a multiple of the specified range.
  • Each(rules ...Rule): checks the elements within an iterable (map/slice/array) with other rules.
  • When(condition, rules ...Rule): validates with the specified rules only when the condition is true.
  • Else(rules ...Rule): must be used with When(condition, rules ...Rule), validates with the specified rules only when the condition is false.

The is sub-package provides a list of commonly used string validation rules that can be used to check if the format of a value satisfies certain requirements. Note that these rules only handle strings and byte slices and if a string or byte slice is empty, it is considered valid. You may use a Required rule to ensure a value is not empty. Below is the whole list of the rules provided by the is package:

  • Email: validates if a string is an email or not. It also checks if the MX record exists for the email domain.
  • EmailFormat: validates if a string is an email or not. It does NOT check the existence of the MX record.
  • URL: validates if a string is a valid URL
  • RequestURL: validates if a string is a valid request URL
  • RequestURI: validates if a string is a valid request URI
  • Alpha: validates if a string contains English letters only (a-zA-Z)
  • Digit: validates if a string contains digits only (0-9)
  • Alphanumeric: validates if a string contains English letters and digits only (a-zA-Z0-9)
  • UTFLetter: validates if a string contains unicode letters only
  • UTFDigit: validates if a string contains unicode decimal digits only
  • UTFLetterNumeric: validates if a string contains unicode letters and numbers only
  • UTFNumeric: validates if a string contains unicode number characters (category N) only
  • LowerCase: validates if a string contains lower case unicode letters only
  • UpperCase: validates if a string contains upper case unicode letters only
  • Hexadecimal: validates if a string is a valid hexadecimal number
  • HexColor: validates if a string is a valid hexadecimal color code
  • RGBColor: validates if a string is a valid RGB color in the form of rgb(R, G, B)
  • Int: validates if a string is a valid integer number
  • Float: validates if a string is a floating point number
  • UUIDv3: validates if a string is a valid version 3 UUID
  • UUIDv4: validates if a string is a valid version 4 UUID
  • UUIDv5: validates if a string is a valid version 5 UUID
  • UUID: validates if a string is a valid UUID
  • CreditCard: validates if a string is a valid credit card number
  • ISBN10: validates if a string is an ISBN version 10
  • ISBN13: validates if a string is an ISBN version 13
  • ISBN: validates if a string is an ISBN (either version 10 or 13)
  • JSON: validates if a string is in valid JSON format
  • ASCII: validates if a string contains ASCII characters only
  • PrintableASCII: validates if a string contains printable ASCII characters only
  • Multibyte: validates if a string contains multibyte characters
  • FullWidth: validates if a string contains full-width characters
  • HalfWidth: validates if a string contains half-width characters
  • VariableWidth: validates if a string contains both full-width and half-width characters
  • Base64: validates if a string is encoded in Base64
  • DataURI: validates if a string is a valid base64-encoded data URI
  • E164: validates if a string is a valid E164 phone number (+19251232233)
  • CountryCode2: validates if a string is a valid ISO3166 Alpha 2 country code
  • CountryCode3: validates if a string is a valid ISO3166 Alpha 3 country code
  • DialString: validates if a string is a valid dial string that can be passed to Dial()
  • MAC: validates if a string is a MAC address
  • IP: validates if a string is a valid IP address (either version 4 or 6)
  • IPv4: validates if a string is a valid version 4 IP address
  • IPv6: validates if a string is a valid version 6 IP address
  • Subdomain: validates if a string is valid subdomain
  • Domain: validates if a string is valid domain
  • DNSName: validates if a string is valid DNS name
  • Host: validates if a string is a valid IP (both v4 and v6) or a valid DNS name
  • Port: validates if a string is a valid port number
  • MongoID: validates if a string is a valid Mongo ID
  • Latitude: validates if a string is a valid latitude
  • Longitude: validates if a string is a valid longitude
  • SSN: validates if a string is a social security number (SSN)
  • Semver: validates if a string is a valid semantic version

Credits

The is sub-package wraps the excellent validators provided by the govalidator package.

ozzo-validation's People

Contributors

0perl avatar aradilov avatar asdine avatar cebe avatar erdaltsksn avatar jhvst avatar kfreiman avatar lyoshenka avatar maratori avatar mehran-prs avatar meydjer avatar nathanbaulch avatar necryin avatar petervandenbroek avatar plbogen avatar qiangxue avatar samber avatar seriousben avatar sgleizes avatar terminalfi avatar v4n avatar vbochko avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ozzo-validation's Issues

Validating database nulls types

Hi team.
Please advise how I can validate types like sql.NullBool, sql.NullString, sql.NullInt64, sql.NullFloat64 or advanced packages like https://github.com/guregu/null ? For example, I try to validate:

import (
    "fmt"
    "gopkg.in/guregu/null.v3"
    "github.com/go-ozzo/ozzo-validation"
)

type User struct {
    Name null.String
}

func (u User) Validate(attrs ...string) error {
    return validation.StructRules{}.
        Add("Name", validation.Length(2, 25)).
        Validate(u, attrs...)
}

func main() {
    user := User{Name: null.NewString("", true)}
    if err := validation.Validate(user); err != nil {
        panic(err)
    }
    fmt.Printf("Hello %s", user.Name.String)
}

But receive error cannot get the length of struct. Should I create custom validators or exists better way?

Validating Arrays, Slice or Maps should point to an ID / PK

Ozzo, First of all thank you for this library.

But I encountered a challenge where by, when I am validating a slice, I wanted to show the a respective field of that struct. For example;

// Provided this slice ...
addresses := []Address{
    Address{Code: "id1", State: "MD", Zip: "12345"},
    Address{Code: "id2", Street: "123 Main St", City: "Vienna", State: "VA", Zip: "12345"},
    Address{Code: "id3", City: "Unknown", State: "NC", Zip: "123"},
}

I should do something like this

err := validation.Validate(addresses, "Code")
fmt.Println(err)

And it should show

ID1: (City: cannot be blank; Street: cannot be blank.); ID2: (Street: cannot be blank; Zip: must be in a valid format.).

How is that?

Validation error of structs with integer constants.

For whatever reason, validating integer constants fails when the value is 0. The following returns with "Code" is required (or something similar). If I change my constants to 1,2 instead of 0,1 it works fine, and if it change OK to NOT_OK in the example (keeping 0,1) below it works fine too.

type ResponseCode int

const (
    OK ResponseCode = 0
     NOT_OK ResponseCode = 1
)

type data struct {
    code: OK,
    something: "foo"
}

func (d Data) Validate() error {
    validation.ValidateStruct(&d, 
        validation.Field(&d.code, validation.Required),
    )
}

Use ozzo-validation type for validation errors

My use case:

in validators I use repositories and services, the simplest example of such validator "email already exists". This validator goes to check email in db/redis/whatever through repository.

Repository returns user&error.

Error == NotFound means validation passed
Error == nil means email already exists
Error == anythingElse I want to stop validation and handle it in my handler.

And now it's impossible.

I implemented this feature for myself and I would love to bring it to your lib.
But it will break all existing validators. Do you have any ideas how to implement this feature without breaking changes?

Thanks!

P.S. You can take a look at my implementation here: https://github.com/smacker/ozzo-validation

Problem with validating slice struct fields

The following test:

func TestInStruct(t *testing.T) {
	var v struct {
		Field []string
	}
	v.Field = []string{"v1"}

	err := ValidateStruct(&v,
		Field(&v.Field, In("v1", "v2")),
	)
	if err != nil {
		t.Fatal(err)
	}
}

Fails with: Field: must be a valid value.

Issue validating structs in structs

Hello,

I am a new user to Go and of the library and have found what I think is a bug.

I am trying to validate the following struct

type Team struct {
	DisplayName          string   `json:"display_name"`
	TeamDistributionList string   `json:"team_distribution_list"`
	EscalationContact    string   `json:"escalation_contact"`
	ApprovedBillingCodes []string `json:"approved_billing_codes"`
	Membership           struct {
		Maintainers []string `json:"maintainers"`
		Members     []string `json:"members"`
	} `json:"membership"`
	Environments []string `json:"environments"`
}

with the following rules

func (t Team) Validate() error {
	return validation.ValidateStruct(&t,
		validation.Field(&t.DisplayName, validation.Required),
		validation.Field(&t.TeamDistributionList,
			validation.Required
		),
		validation.Field(&t.EscalationContact,
			validation.Required
		),
		validation.Field(&t.ApprovedBillingCodes,
			validation.Required,
			validation.Each(is.Digit),
		),
		validation.Field(&t.Membership.Maintainers, validation.Required, validation.Each(is.Email)),
	)
}

When I run the validation, the following error happens (With the Membership.Maintainers field)

=== RUN   TestTeamValidation
--- FAIL: TestTeamValidation (0.00s)
    team_test.go:19: field #4 cannot be found in the struct

When digging into the error, I've pinpointed the location of the error to https://github.com/go-ozzo/ozzo-validation/blob/master/struct.go#L80

Thanks! If there is any other information I can provide, please let me know :)

Min() validator fails on uint32 field

I stumbled upon this when trying to validate a struct with uint32 field. The validation fails with

Integer: cannot convert uint32 to int64.

Here's a complete test case:

package main

import (
	"fmt"
	"testing"

	"github.com/go-ozzo/ozzo-validation"
)

type A struct {
	Integer uint32
}

func (a A) Validate() error {
	return validation.ValidateStruct(&a,
		validation.Field(&a.Integer, validation.Required, validation.Min(1)))
}

func TestValidate(t *testing.T) {
	a := A{2}
	err := a.Validate()
	if err != nil {
		fmt.Printf("validation error received: %s", err.Error())
		t.Fail()
	}
}

Building failed with vgo

Building failed with vgo. :(

go: finding github.com/go-ozzo/ozzo-routing latest
go: finding github.com/go-ozzo/ozzo-routing/access latest
go: finding github.com/go-ozzo/ozzo-routing/fault latest
go: finding github.com/go-ozzo/ozzo-routing/file latest
go: finding github.com/go-ozzo/ozzo-routing/cors latest
go: finding github.com/go-ozzo/ozzo-routing/content latest
go: finding github.com/go-ozzo/ozzo-routing/auth latest
go: finding github.com/ikeikeikeike/go-sitemap-generator/stm latest
go: finding github.com/ikeikeikeike/go-sitemap-generator latest
go: finding github.com/go-ozzo/ozzo-validation/is latest
go: finding github.com/go-ozzo/ozzo-validation latest
go: extracting github.com/go-ozzo/ozzo-validation v0.0.0-20180719025158-48037855ba41
-> unzip D:\Go\src\mod\cache\download\github.com\go-ozzo\ozzo-validation\@v\v0.0.0-20180719025158-48037855ba41.zip: invalid file name github.com/go-ozzo/[email protected]/.gitignore
go: finding github.com/go-ozzo/ozzo-routing/slash latest
go: finding github.com/jordan-wright/email latest
go: import "kassar/cmd/server" ->
	import "kassar/app" ->
	import "github.com/go-ozzo/ozzo-validation": cannot find module providing package github.com/go-ozzo/ozzo-validation
go: import "kassar/cmd/server" ->
	import "kassar/apis" ->
	import "kassar/models" ->
	import "github.com/go-ozzo/ozzo-validation/is": cannot find module providing package github.com/go-ozzo/ozzo-validation/is

Point at the end of the line

type Address struct {
	Street string
}

func Validate(a *Address) error {
	return validation.ValidateStruct(a,
		
		validation.Field(&a.Street, validation.Required, validation.Length(40, 50).Error("NAME_INVALID_LENGHT")),
	)
}

func main() {
	a := &Address{
		Street: "Krasnaya",	
	}

	err := Validate(a)
	fmt.Println(err)  // Street: INVALID_LENGHT.
}

I do not put a dot in the text of the error, where does it come from? remove her

Optional field validation

Hi guys.
Please advise how I can skip validation for empty fields. For example add new rule Optional:

return validation.StructRules{}.
    Add("Gender", validation.Optional, validation.In("Female", "Male")).
    Validate(req, attrs...)

If validation.IsEmpty(field) equal true then we skip validation.In(...). Else we run all next validators. I know about validation.Skip, but I did not find any examples how it use.

Another question: how I can skip validation from custom validator?

Thank you very much for your work.
Regards, Max

[SOLVED] Not working with a float64, don't know why

Hi,
I have the following code :

I defined a structure called Users (I'm using it to query users) :


type Users struct {
  RegisteredAfter  *time.Time `json:"registeredAfter"`
  RegisteredBefore *time.Time `json:"registeredBefore"`

  RangeInKm   *float64      `json:"rangeInKm"`
  Geolocation *common.Point `json:"geolocation"`

  Number int  `json:"number"`
  From   *int `json:"from"`
}

func (u Users) Validate() error {
  validate := validation.ValidateStruct(&u,
    validation.Field(&u.RegisteredAfter, validation.Max(time.Now())),
    validation.Field(&u.RegisteredBefore, validation.Max(time.Now())),

    validation.Field(&u.Number, validation.Required, validation.Max(60)),

    validation.Field(&u.RangeInKm, validation.Min(0), validation.Max(1000)),
    validation.Field(&u.Geolocation),
  )

  return validate
}

And this is the following error I get when I set rangeInKm which is a *float64 :

2018/12/11 12:06:06 VALIDATION rangeInKm: cannot convert float64 to int64.

I don't know why tho.

Support validating embedded structs

Considering the following:

type ListingOptions struct {
	Offset    int    `json:"offset"`
	Limit     int    `json:"limit"`
	OrderBy   string `json:"order_by"`
	OrderDesc bool   `json:"order_desc"`
}

func (l ListingOptions) Validate() error {
	return validation.ValidateStruct(&l,
		// Offset should be a positive number
		validation.Field(&l.Offset, validation.Min(0)),
		// Limit should be between 10 and 50
		validation.Field(&l.Limit, validation.Min(10), validation.Max(50)),
	)
}

type BookListingOptions struct {
	UserID   int             `json:"user_id"`
	Status   []string       `json:"status"`

	ListingOptions
}

func (l BookListingOptions) Validate() error {
	validOrderColumns := []string{
		"name", "status", "created_at", "updated_at",
	}

	return validation.ValidateStruct(&l,
		// UserID should be a positive number
		validation.Field(&l.UserID, validation.Min(0)),
		// Status should be a valid book status
		validation.Field(&l.Status, validation.In("open", "closed")),
		// ListingOptions.Order should be a valid order clause
		validation.Field(&l.ListingOptions.OrderBy, validation.In(validOrderColumns)),
		// ListingOptions has its own validation
		validation.Field(&l.ListingOptions),
	)
}

Calling BookListingOptions{}.Validate() returns the following error: field #2 cannot be found in the struct

Skip further rules when Nil is found in NilOrNotEmpty Rule

This is a ask for feature where more rules are checked after NilOrNotEmpty only if it was not found to be Nil.

It would be something like:

validation.FIeld(&req.Favoritenumbers, validation.NilOrNotEmpty, validation.By(ValidatePrime), validation.Min(100))

The struct being:

type NewPlayer struct {
Name string `json: "id"`
JerseyNumber int `json:jersey,omitempty"`
}

Where JerseyNumber is optional but if provided it must be a prime number and greater than 100.

Validate if not nil

Considering the following type:

type Foo struct {
    OptionalBar *string
}

Would it be possible to validate OptionalBar only if it's not nil?

Unexpected custom error message from ValidateStruct

Hello again,

it would be really nice if the custom errors from ValidateStruct() had the same format as from Validate(). Right now ValidateStruct() also returns the name of the failed field which makes it hard to use the custom error together with other code. Makes sense?

data := "2123"
err := validation.Validate(data,
	validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("error.mustbefive"),
)
fmt.Println(err) // Prints: "error.mustbefive"

type Foo struct {
	Bar string
}
t := Foo{Bar: "2123"}
err = validation.ValidateStruct(&t,
	validation.Field(&t.Bar, validation.Match(regexp.MustCompile("^[0-9]{5}$")).Error("error.mustbefive")),
)
fmt.Println(err) // Prints: "Bar: error.mustbefive"

Breaking changes

Heya!

One of the latest updates has removed validation.StructRules, this effectively breaks CI tools like CircleCI.

I love the package and use it quite a lot! But changes that effectively change the API without backwards compatibility make me worried :).

Just a heads up for future commits!

How to return error properly?

How to return correct http status error from validation error?

func SaveHandler(cPtr *routing.Context) error {
    var actor models.Actor
    cPtr.Read(&actor)
    err := actor.Validate()
    if err != nil {
        return err
    }
    return actor.Save()
}
...

func (m *Actor) Save() error {
    err := db.Model(m).Insert()
    if err != nil {
        fmt.Println("Exec err:", err.Error())
    }
    return err
}

Return http 500, and error in json. Is it correct?

Missing vendor

See error output:

../../../../../../github.com/go-ozzo/ozzo-validation/is/rules.go:12:2: cannot find package "github.com/asaskevich/govalidator" in any of:
	/usr/local/Cellar/go/1.11/libexec/src/github.com/asaskevich/govalidator (from $GOROOT)
	/Users/Jacky/Jacky.Wu/MyProjects/Go/src/github.com/asaskevich/govalidator (from $GOPATH)

Should we include this vendor?
Thanks.

How to access individual validation errors

Is there a way to access individual error message like :

type Address struct {
	Street string `json:"street"`
	City   string `json:"city"`
	State  string `json:"state"`
	Zip    string `json:"zip"`
}

// ...perform validation here...

err := a.Validate()
fmt.Println(reflect.ValueOf(err).Kind()) //map
streettError := err["Street"] //is this even possible? <--- give me expression "err" (error) does not support indexing

Validation Errors should use JSON tag values as the error keys when marshalling.

Related to #1, when marshalling validation.Errors It would be great if we can detect struct fields with the json tag and use that as the key in the map for validation.Errors.

A option for doing this is to convert validation.Errors to a struct that contains two fields Errs map[string]err & ErrsJSON map[string]string. Errs map key contains the Struct field name and ErrsJSON map key contains the Struct json tag value.

Another option would be to use a custom field tag e.g. validation that is checked for and if specified we use it as the map key for the error message. This isn't specifically meant to be used when marshalling json.

@qiangxue thoughts?

struct cross field validation

Is it possible to create a custom validation rule to do a cross field validation in a struct fields with validation.ValidateStruct?

thanks

Question `Marshaling vs validation`

There are two common ways for validation in go (at least that I know of):

  1. using tags (eg govalidator)
  2. using explicit validation (like this library)

Both good libraries, but both require using a Validate() function and checking error before using the struct, which is IMO not very convenient having to check every time the struct is used.

I was having this idea of moving validation into custom Unmarshaller.

package main

import (
	"encoding/json"
        "github.com/go-ozzo/ozzo-validation"
	"fmt"
)

type Person struct {
	Name string `"json:name,omitempty"`
	Age  int    `"json:age,omitempty"`
}

func (p *Person) UnmarshalJSON(b []byte) error {
	type Alias Person
	alias := &Alias{}
	err := json.Unmarshal(b, alias)
	if err != nil {
	    return err
	}
	*p = Person(*alias)
	
        // define error checks here
	if p.Age < 18 {
	    return fmt.Errorf("Age must be more than 18")
	}
        // or fields could be validated by using this library 
        return validation.ValidateStruct(p,
            // Age must be more than 18
            validation.Field(p.Age, validation.Min(18)),
        )
	
	return err
}

func main() {

	jsonPerson := []byte(`{"name":"Joye", "age":17}`)
	person := &Person{}
	if err := json.Unmarshal(jsonPerson, person); err != nil {
	    fmt.Println("error decoding json into Person: %v", err)
	    return
	}

	fmt.Println(person)

}

it just seems more convenient (having to just unmarshal rather than validate and unmarshal) also it is easier to set a default value.

could you correct me if I am mistaking or forgeting something

Comparing Struct Variables

I am trying to validate a struct that has start time and end time as variables. I implemented following to do this. Is there any simpler way to compare those values? If not, this might be a good feature to have.


type TimeRange struct {
	start time.Time
	end time.Time
}

type dateRule struct {
	message  string
	timeRange TimeRange
}


// Error sets the error message for the rule.
func (v *dateRule) Error(message string) *dateRule {
	return &dateRule{
		message: message,
		timeRange: v.timeRange,
	}
}

// Validate checks if the given value is valid or not.
func (v *dateRule) Validate(value interface{}) error {
	timeRange := v.timeRange
	if timeRange.start.After(timeRange.end) {
		return errors.New(v.message)
	}
	return nil
}
//Validation Rules
func (r TemporalRequest) Validate() error {

	var sTime, err1 = utils.ParseDatetime(r.StartTime)

	if err1 != nil {
		return err1
	}
	var eTime, err2 = utils.ParseDatetime(r.EndTime)

	if err2 != nil {
		return err2
	}

	return validation.ValidateStruct(&r,
		validation.Field(&r.StartTime, &dateRule{message: "Start Time should be before end time", timeRange:TimeRange{start:sTime, end:eTime}}),
	)
}

Error validating integer value

package model

import (
    "github.com/go-ozzo/ozzo-validation"
    "github.com/go-ozzo/ozzo-validation/is"
)

type UserConfirmation struct {
	ID int64 `json:"id" meddler:"id,pk"`
	UserID int64 `json:"user_id" meddler:"user_id" validation:"user_id"`
	Type string `json:"type" meddler:"type"`
	Code string `json:"-" meddler:"code"`
	Time int32 `json:"expire" meddler:"timestamp"`
}

func (uc UserConfirmation) Validate() error {
    return validation.StructRules{}.
        Add("UserID", validation.Required, is.Int).
        Add("Type", validation.Required, validation.In("sms","email")).
        Validate(uc)
}

and some main.go code

confirmation := &model.UserConfirmation{}
_ := c.BindJSON(confirmation)
errs := confirmation.Validate()
if errs != nil {
    errs := errs.(validation.Errors)
    c.JSON(http.StatusBadRequest, gin.H{
        "errors": errs,
    })
    return
}

Sending integer user_id value

curl -XPOST localhost:8080/api/auth/confirmation/send -d '{"user_id":4,"type":"sms"}'
{"errors":{"user_id":"must be either a string or byte slice"}}

It ask me to send a string.... But i need an int value. And sending a string causes struct error:

curl -XPOST localhost:8080/api/auth/confirmation/send -d '{"user_id":"asdsa","type":"sms"}'
{"error":"json: cannot unmarshal string into Go value of type int64"}

Any ideas? I'm new in go)

Custom validation rule with additional parameters

Hello

The validation.By method offers no way to use additional parameters.
I would like to modify it to accept a varidic list of parameters after the value to validate, like so:

func checkAbc(value interface{}, args ...interface{}) error {
	s, _ := value.(string)
        caseSensitive, _ := args[0].(bool)
	if !caseSensitive && s != "abc" {
		return errors.New("must be abc")
	}
	if caseSensitive && !EqualFold(s, "abc") {
		return errors.New("must be abc")
	}
	return nil
}

err := validation.Validate("xyz", validation.By(checkAbc, true))

What do you think about it ?

incorrect struct field name in errors if struct json tag includes omitempty

lets suppose i have this struct

type User struct {
    Username string `json:"username"`
    Password string `json:"password,omitempty"`
}

if there is a validation error on Password the name of the error field will be password,omitempty and below is the json encoded error message

{
    "password,omitempty": "password_required"
}

Could you help to provide ozzo-widget / ozzo-grid?

Dear Qian,

I am a yii1/yii2 user too, really like that elegant framework which created by you!
In these frameworks, it provide widgets which wrap bootstrap , it is very convenient to developers, especially for the grid widget.

Could you create similar widgets for ozzo framework? It should be increase the stars! Thanks.

Regards,
Scott Huang

Pre validation filters concept

Example situations:

  • E-mail validation requires trim extra spaces before the check.
  • Phone number validation requires filter unexpected character.
  • Filename validator may require ToLowerCase filter

Take a look at zend-inputfilter and zend-filter, zend-validator subpackages

$inputFilterConfig = [
    'amount' => [
        'filters'  => [
            ['name' => 'StringTrim']
        ],
        'validators' => [
            ['name' => 'Digits'],
            [
                'name' => 'Between',
                'options' => [
                    'min' => 1,
                    'max' => 10
                ]
            ]
        ]
    ],
    'email' => [
        'filters'  => [
            ['name' => 'StringTrim'],
            ['name' => 'ToLowerCase'],
        ],
        'validators' => [
            ['name' => 'EmailAddress']
        ]
    ]
];

Suggestion for library improvement

This library is great. I had been looking for a library that would be as elegant and flexible as the NodeJS Joi library developed by Walmart for their hapijs framework and I found this library that was the closest thing to the Joi library. I would like to make the following suggestions to improve the library.

  • Field names passed in to the validator should be pointer based instead of a string. For example, use &c.Name instead of "Name". This would move the error for using the wrong field name from a runtime error to a compile time error.
  • Incorporate the validation rules from the goburrow validator into the go-ozzo validator which have been really thought out and powerful. For example goburrow has the "min" validation rule, which applies the rule depending upon what has been passed in. If a string is passed in, then it checks for the minimum length. If a slice has been passed in, then it checks for the length of the array. If an integer has been passed in, then it checks if the value is equal to or greater than the min value. And so on...

Thanks for a great library!

crossfield struct validation

Hello!

There doesn't seem to be a way to do cross field validation for a struct.

Given:

type Bucket struct {
  Accept []string
  Reject []string
}

How can I write Bucket.Validate with a rule that checks that Accept and Reject are mutually exclusive?

func (b Bucket) Validate() error {
return validation.Validate(&b, validation.By(bucketMutualExclusionCheck)
}

cause an stack overflow because validation.Validate calls Validate on Bucket and so forth... infinite recursion.

The other solution also seems like a hack, because it errors on one or the other field instead of the whole struct:

func (b Bucket) Validate() error {
  return validation.ValidateStruct(&b,
    validation.Field(&b.Accept, validation.By(sliceMutualExclusionCheck(b.Reject))),
  )
}

Is there a better way to tackle this kind of validations?

More extensible way of doing validations

Any thoughts or plans or making custom validations more powerful such as allowing sql queries to be executed during the validation?

For example this is how to check if a record is unique in the database (using a ozzo-validation before c5ea90):

// UniqueRecord checks that a value is unique value in the database.
func UniqueRecord(statement string, bindvars ...interface{}) *uniqueRecord {
    return &uniqueRecord{
        message:   "Value is not unique.",
        statement: statement,
        bindvars:  bindvars,
    }
}

type uniqueRecord struct {
    message   string
    statement string
    bindvars  []interface{}
}

func (v *uniqueRecord) Validate(value interface{}, context interface{}) error {
    var exists bool
    statement := "SELECT EXISTS(" + v.statement + ")"
    err := database.SQL.Get(&exists, statement, v.bindvars...)
    if err != nil || exists {
        return errors.New(v.message)
    }
    return nil
}

To validate:

Add("Email", validation.Required, is.Email,     UniqueRecord(
  "SELECT 1 FROM email WHERE email=$1", invitation.Email)
)

This is no longer possible with the updates.
It would be great being able to more than data-type validations and handle SQL queries validation and more complex struct validation such as check if two struct keys are not equal.

Govalidator not installable due to Gopkg issues

When installing the dependency gopkg.in/asaskevich/govalidator.v4 the following error occurs.

error: RPC failed; HTTP 301 curl 22 The requested URL returned error: 301
fatal: The remote end hung up unexpectedly
package gopkg.in/asaskevich/govalidator.v4: exit status 128

This is because git no longer follows redirects niemeyer/gopkg#50

There hasn't been any updates from the Gopkg author on the issue for a while. I would recommend to usegithub.com/asaskevich/govalidator so that this library is installable out of the box.

Error message in another language

Are there any way to define error message in another language?
Right now we can define it by using custom error message, but it would be redundant when we validate a bunch of value with the same validation rule.

validation.IN cannot parse byte arrays

The validation to check if a value can be found in a given list can not parse byte arrays. I think it is pretty easy to fix. for now i will create a custom function

How can I validation field array ?

I have struct like this

type ReviewJson struct {
	Business string   `json:"business"  form:"business"`
	Feedback string   `json:"feedback" form:"feedback"`
	Point    int      `json:"point" form:"point"`
	Photos   []string `form:"photos[]"`
}

I wanna validation field Photos, for each photo has valid with MongoId.

Partial validation

I'd like to implement a partial update in my API. I can already pass in a list of struct field names to ozzo-dbx's ModelQuery.Update method, but I can't restrict validation to just those fields.

As a workaround, I can check the returned Errors map and remove all the fields I'm not interested in. But would it be possible to add a version of ValidateStruct with an include/exclude feature like ModelQuery.Update?

Pointer returns

I was browsing the docs and thought that this is a great looking library. However I don't understand why the rules like LengthRule are returned as a pointer.

https://github.com/go-ozzo/ozzo-validation/blob/master/length.go#L48

It's my understanding that a struct that is this small belongs on the stack, and by creating all these pointers in a heavy-usage scenario like an API where many models are constantly being validated is going to create unnecessary pressure on the GC. Any reasoning behind this? Is it too late to change it?

Panic on struct field pointing to zero value

When using ValidateStruct on a struct that has a field using a pointer to any field that contains a zero value, the validation panics.

package main

import validation "github.com/go-ozzo/ozzo-validation"

type A struct {
	Name *string
}

func (a *A) Validate() error {
	return validation.ValidateStruct(a,
		validation.Field(&a.Name, validation.Length(1, 32)),
	)
}
func main() {
	var a A
	a.Validate()
}
$ go run main.go
panic: reflect: call of reflect.Value.Type on zero Value

goroutine 1 [running]:
panic(0x117440, 0xc420206800)
	/usr/local/go/src/runtime/panic.go:500 +0x1a1
reflect.Value.Type(0x0, 0x0, 0x0, 0x0, 0x0)
	/usr/local/go/src/reflect/value.go:1670 +0x224
github.com/go-ozzo/ozzo-validation.Validate(0x119ca0, 0x0, 0xc420193c90, 0x1, 0x1, 0x196, 0xc4202052d0)

Should the IsEmpty check be bypassed for numbers

minAllowed := 1

return validation.Validate(0,
		validation.Min(minAllowed),
		validation.Max(maxAllowed),
	)

I would expect an error from this, as 0 is less than the min value (1), but instead nil is returned. The issue is because (r *ThresholdRule) Validate does a if isNil || IsEmpty(value) { check. Same for Max (what if I want to check if my value 0 satisfies the rule of the min value being 10).

I think that in this context, that check shouldn't be performed. It could be conditionally performed based on reflection (as it does for other reasons).

I'm happy to make a MR to do this.

Edit: I'll add in too while I'm here, thanks for the library!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.