validation

package module
v0.17.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 11, 2023 License: MIT Imports: 17 Imported by: 0

README

Golang validation framework

Go Reference GitHub go.mod Go version GitHub release (latest by date) GitHub tests Go Report Card Maintainability Test Coverage Scrutinizer Code Quality Contributor Covenant

Golang validation framework based on static typing and generics. Designed to create complex validation rules with abilities to hook into the validation process.

This project is inspired by Symfony Validator component.

Key features

  • Flexible and customizable API built in mind to use benefits of static typing and generics
  • Declarative style of describing a validation process in code
  • Validation of different types: booleans, numbers, strings, slices, maps, and time
  • Validation of custom data types that implements Validatable interface
  • Customizable validation errors with translations and pluralization supported out of the box
  • Easy way to create own validation rules with context propagation and message translations

Work-in-progress notice

This package is under active development and API may be changed until the first major version will be released. Minor versions n 0.n.m may contain breaking changes. Patch versions m 0.n.m may contain only bug fixes.

Goals before making stable release:

  • implementation of static type arguments by generics;
  • mechanism for asynchronous validation (lazy violations by async/await pattern);
  • implement all common constraints.

Installation

Run the following command to install the package

go get -u github.com/muonsoft/validation

How to use

Basic concepts

The validation process is built around functional options and passing values by specific typed arguments. A common way to use validation is to call the validator.Validate method and pass the argument option with the list of validation constraints.

err := validator.Validate(context.Background(), validation.String("", it.IsNotBlank()))

fmt.Println(err)
// Output:
// violation: This value should not be blank.

List of common validation arguments:

  • validation.Nil() - passes result of comparison to nil to test against nil constraints;
  • validation.Bool() - passes boolean value;
  • validation.NilBool() - passes boolean pointer value;
  • validation.Number[T]() - passes generic numeric value;
  • validation.NilNumber[T]() - passes generic numeric pointer value;
  • validation.String() - passes string value;
  • validation.NilString() - passes string pointer value;
  • validation.Countable() - passes result of len() to test against constraints based on count of the elements;
  • validation.Time() - passes time.Time value;
  • validation.NilTime() - passes time.Time pointer value;
  • validation.EachNumber[T]() - passes slice of generic numbers to test each of the element against numeric constraints;
  • validation.EachString() - passes slice of strings to test each of the element against string constraints;
  • validation.Valid() - passes Validatable value to run embedded validation;
  • validation.ValidSlice[T]() - passes slice of []Validatable value to run embedded validation on each of the elements;
  • validation.ValidMap[T]() - passes map[string]Validatable value to run embedded validation on each of the elements;
  • validation.Comparable[T]() - passes generic comparable value to test against comparable constraints;
  • validation.NilComparable[T]() - passes generic comparable pointer value to test against comparable constraints;
  • validation.Comparables[T]() - passes generic slice of comparable values (can be used to check for uniqueness of the elements);
  • validation.Check() - passes result of any boolean expression;
  • validation.CheckNoViolations() - passes error to check err for violations, can be used for embedded validation.

For single value validation, you can use shorthand versions of the validation method:

  • validator.ValidateBool() - shorthand for validator.Bool();
  • validator.ValidateInt() - shorthand for validation.Number[int]();
  • validator.ValidateFloat() - shorthand for validation.Number[float64]();
  • validator.ValidateString() - shorthand for validation.String();
  • validator.ValidateStrings() - shorthand for validation.Comparables[[]string]();
  • validator.ValidateCountable() - shorthand for validation.Countable();
  • validator.ValidateTime() - shorthand for validation.Time();
  • validator.ValidateEachString() - shorthand for validation.EachString();
  • validator.ValidateIt() - shorthand for validation.Valid().

See usage examples in the documentation.

How to use the validator

There are two ways to use the validator service. You can build your instance of validator service by using validation.NewValidator() or use singleton service from package github.com/muonsoft/validation/validator.

Example of creating a new instance of the validator service.

// import "github.com/muonsoft/validation"

validator, err := validation.NewValidator(
    validation.DefaultLanguage(language.Russian), // passing default language of translations
    validation.Translations(russian.Messages), // setting up custom or built-in translations
    validation.SetViolationFactory(userViolationFactory), // if you want to override creation of violations
)

// don't forget to check for errors
if err != nil {
    fmt.Println(err)
}

If you want to use a singleton service make sure to set up your configuration once during the initialization of your application.

// import "github.com/muonsoft/validation/validator"

err := validator.SetUp(
    validation.DefaultLanguage(language.Russian), // passing default language of translations
    validation.Translations(russian.Messages), // setting up custom or built-in translations
    validation.SetViolationFactory(userViolationFactory), // if you want to override creation of violations
)

// don't forget to check for errors
if err != nil {
    fmt.Println(err)
}
Processing property paths

One of the main concepts of the package is to provide helpful violation descriptions for complex data structures. For example, if you have lots of structures used in other structures you want somehow to describe property paths to violated attributes.

The property path generated by the validator indicates how it reached the invalid value from the root element. Property path is denoted by dots, while array access is denoted by square brackets. For example, book.keywords[0] means that the violation occurred on the first element of array keywords in the book object.

You can pass a property path by calling At function on any argument.

err := validator.Validate(
    context.Background(),
    validation.String(
        "",
        it.IsNotBlank(),
    ).At(
        validation.PropertyName("properties"),
        validation.ArrayIndex(1),
        validation.PropertyName("tag"),
    ),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println("property path:", violation.PropertyPath().String())
        return nil
    })
}
// Output:
// property path: properties[1].tag

Also, you can create context validator by using validator.At(), validator.AtProperty() or validator.AtIndex() methods. It can be used to validate a couple of attributes of one object.

err := validator.
    AtProperty("properties").
    AtIndex(1).
    AtProperty("tag").
    Validate(context.Background(), validation.String("", it.IsNotBlank()))

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println("property path:", violation.PropertyPath().String())
        return nil
    })
}
// Output:
// property path: properties[1].tag

For a better experience with struct validation, you can use shorthand versions of validation arguments with passing property names:

  • validation.NilProperty();
  • validation.BoolProperty();
  • validation.NilBoolProperty();
  • validation.NumberProperty();
  • validation.NilNumberProperty();
  • validation.StringProperty();
  • validation.NilStringProperty();
  • validation.CountableProperty();
  • validation.TimeProperty();
  • validation.NilTimeProperty();
  • validation.EachNumberProperty();
  • validation.EachStringProperty();
  • validation.ValidProperty();
  • validation.ValidSliceProperty();
  • validation.ValidMapProperty();
  • validation.ComparableProperty();
  • validation.ComparablesProperty();
  • validation.CheckProperty().
err := validator.Validate(
    context.Background(),
    validation.StringProperty("property", "", it.IsNotBlank()),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println("property path:", violation.PropertyPath().String())
        return nil
    })
}
// Output:
// property path: property
Validation of structs

There are few ways to validate structs. The simplest one is to call the validator.Validate method with property arguments.

document := Document{
    Title:    "",
    Keywords: []string{"", "book", "fantasy", "book"},
}

err := validator.Validate(
    context.Background(),
    validation.StringProperty("title", document.Title, it.IsNotBlank()),
    validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)),
    validation.ComparablesProperty[string]("keywords", document.Keywords, it.HasUniqueValues[string]()),
    validation.EachStringProperty("keywords", document.Keywords, it.IsNotBlank()),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation)
        return nil
    })
}
// Output:
// violation at 'title': This value should not be blank.
// violation at 'keywords': This collection should contain 5 elements or more.
// violation at 'keywords': This collection should contain only unique elements.
// violation at 'keywords[0]': This value should not be blank.

The recommended way is to implement the validation.Validatable interface for your structures. By using it you can build complex validation rules on a set of objects used in other objects.

type Product struct {
    Name       string
    Tags       []string
    Components []Component
}

func (p Product) Validate(ctx context.Context, validator *validation.Validator) error {
    return validator.Validate(
        ctx,
        validation.StringProperty("name", p.Name, it.IsNotBlank()),
        validation.AtProperty(
            "tags",
            validation.Countable(len(p.Tags), it.HasMinCount(5)),
            validation.Comparables[string](p.Tags, it.HasUniqueValues[string]()),
            validation.EachString(p.Tags, it.IsNotBlank()),
        ),
        validation.AtProperty(
            "components",
            validation.Countable(len(p.Components), it.HasMinCount(1)),
            // this runs validation on each of the components
            validation.ValidSlice(p.Components),
        ),
    )
}

type Component struct {
    ID   int
    Name string
    Tags []string
}

func (c Component) Validate(ctx context.Context, validator *validation.Validator) error {
    return validator.Validate(
        ctx,
        validation.StringProperty("name", c.Name, it.IsNotBlank()),
        validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)),
    )
}

func main() {
    p := Product{
        Name: "",
        Tags: []string{"device", "", "phone", "device"},
        Components: []Component{
            {
                ID:   1,
                Name: "",
            },
        },
    }
    
    err := validator.ValidateIt(context.Background(), p)

    if violations, ok := validation.UnwrapViolationList(err); ok {
        violations.ForEach(func (i int, violation validation.Violation) error {
            fmt.Println(violation)
            return nil
        })
    }
    // Output:
    // violation at 'name': This value should not be blank.
    // violation at 'tags': This collection should contain 5 elements or more.
    // violation at 'tags': This collection should contain only unique elements.
    // violation at 'tags[1]': This value should not be blank.
    // violation at 'components[0].name': This value should not be blank.
    // violation at 'components[0].tags': This collection should contain 1 element or more.
}
Conditional validation

You can use the When() method on any of the built-in constraints to execute conditional validation on it.

err := validator.Validate(
    context.Background(),
    validation.StringProperty("text", note.Text, it.IsNotBlank().When(note.IsPublic)),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation)
        return nil
    })
}
// Output:
// violation at 'text': This value should not be blank.
Conditional validation based on groups

By default, when validating an object all constraints of it will be checked whether or not they pass. In some cases, however, you will need to validate an object against only some specific group of constraints. To do this, you can organize each constraint into one or more validation groups and then apply validation against one group of constraints.

Validation groups are working together only with validation groups passed to a constraint by WhenGroups() method. This method is implemented in all built-in constraints. If you want to use validation groups for your own constraints do not forget to implement this method in your constraint.

Be careful, empty groups are considered as the default group. Its value is equal to the validation.DefaultGroup.

See example.

Working with violations and errors

There are two types of errors returned from the validator. One is validation violations and another is internal errors ( for example, when attempting to apply a constraint on not applicable argument type). The best way to handle validation errors is to check for implementing the validation.ViolationList struct. You can use the default way to unwrap errors.

err := validator.Validate(/* validation arguments */)

if err != nil {
    var violations *validation.ViolationList
    if errors.As(err, &violations) {
        // handle violations
    } else {
        // handle internal error
    }
}

Also, you can use helper function validation.UnwrapViolationList().

err := validator.Validate(/* validation arguments */)
if violations, ok := validation.UnwrapViolationList(err); ok {
    // handle violations
} else if err != nil {
    // handle internal error
}

The validation error called violation consists of a few parameters.

  • error - underlying static error. This error can be used as a unique, short, and semantic code of violation. You can use it to test Violation for specific static error by errors.Is from standard library. Built-in error values are defined in the github.com/muonsoft/validation/errors.go. Error code values are protected by backward compatibility rules, template values are not protected.
  • message - translated message with injected values from constraint. It can be used to show a description of a violation to the end-user. Possible values for build-in constraints are defined in the github.com/muonsoft/validation/message package and can be changed at any time, even in patch versions.
  • messageTemplate - template for rendering message. Alongside parameters it can be used to render the message on the client-side of the library.
  • parameters is the map of the template variables and their values provided by the specific constraint.
  • propertyPath points to the violated property as it described in the previous section.

Thanks to the static error codes provided, you can quickly test the resulting validation error for a specific violation error using standard errors.Is() function.

err := validator.Validate(context.Background(), validation.String("", it.IsNotBlank()))

fmt.Println("is validation.ErrIsBlank =", errors.Is(err, validation.ErrIsBlank))
// Output:
// is validation.ErrIsBlank = true

You can hook into process of violation generation by implementing validation.ViolationFactory interface and passing it via validation.SetViolationFactory() option. Custom violation must implement validation.Violation interface.

How to use translations

By default, all violation messages are generated in the English language with pluralization capabilities. To use a custom language you have to load translations on validator initialization. Built-in translations are available in the sub-packages of the package github.com/muonsoft/message/translations. The translation mechanism is provided by the golang.org/x/text package (be aware, it has no stable version yet).

// import "github.com/muonsoft/validation/message/translations/russian"

validator, err := validation.NewValidator(
    validation.Translations(russian.Messages),
)

There are different ways to initialize translation to a specific language.

The first one is to use the default language. In that case, all messages will be translated to this language.

validator, _ := validation.NewValidator(
    validation.Translations(russian.Messages),
    validation.DefaultLanguage(language.Russian),
)

err := validator.ValidateString(context.Background(), "", it.IsNotBlank())

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation.Error())
        return nil
    })
}
// Output:
// violation: Значение не должно быть пустым.

The second way is to use the validator.WithLanguage() method to create context validator and use it in different places.

validator, _ := validation.NewValidator(
    validation.Translations(russian.Messages),
)

err := validator.WithLanguage(language.Russian).Validate(
    context.Background(),
    validation.String("", it.IsNotBlank()),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation.Error())
        return nil
    })
}
// Output:
// violation: Значение не должно быть пустым.

The last way is to pass language via context. It is provided by the github.com/muonsoft/language package and can be useful in combination with language middleware.

// import "github.com/muonsoft/language"

validator, _ := validation.NewValidator(
    validation.Translations(russian.Messages),
)

ctx := language.WithContext(context.Background(), language.Russian)
err := validator.ValidateString(ctx, "", it.IsNotBlank())

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation.Error())
        return nil
    })
}
// Output:
// violation: Значение не должно быть пустым.

You can see the complex example with handling HTTP request here.

The priority of language selection methods:

  • validator.WithLanguage() has the highest priority and will override any other options;
  • if the validator language is not specified, the validator will try to get the language from the context;
  • in all other cases, the default language specified in the translator will be used.

Also, there is an ability to totally override translations behaviour. You can use your own translator by implementing validation.Translator interface and passing it to validator constructor via SetTranslator option.

type CustomTranslator struct {
    // some attributes
}

func (t *CustromTranslator) Translate(tag language.Tag, message string, pluralCount int) string {
    // your implementation of translation mechanism
}

translator := &CustomTranslator{}

validator, err := validation.NewValidator(validation.SetTranslator(translator))
if err != nil {
    log.Fatal(err)
}
Customizing violation messages

You may customize the violation message on any of the built-in constraints by calling the Message() method or similar if the constraint has more than one template. Also, you can include template parameters in it. See details of a specific constraint to know what parameters are available.

err := validator.ValidateString(context.Background(), "", it.IsNotBlank().Message("this value is required"))

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation.Error())
        return nil
    })
}
// Output:
// violation: this value is required

To use pluralization and message translation you have to load up your translations via validation.Translations() option to the validator. See golang.org/x/text package documentation for details of translations.

const customMessage = "tags should contain more than {{ limit }} element(s)"
validator, _ := validation.NewValidator(
    validation.Translations(map[language.Tag]map[string]catalog.Message{
        language.Russian: {
            customMessage: plural.Selectf(1, "",
                plural.One, "теги должны содержать {{ limit }} элемент и более",
                plural.Few, "теги должны содержать более {{ limit }} элемента",
                plural.Other, "теги должны содержать более {{ limit }} элементов"),
        },
    }),
)

var tags []string
err := validator.ValidateIterable(
    context.Background(),
    tags,
    validation.Language(language.Russian),
    it.HasMinCount(1).MinMessage(customMessage),
)

if violations, ok := validation.UnwrapViolationList(err); ok {
    violations.ForEach(func (i int, violation validation.Violation) error {
        fmt.Println(violation.Error())
        return nil
    })
}
// Output:
// violation: теги должны содержать 1 элемент и более
Creating custom constraints

Everything you need to create a custom constraint is to implement one of the interfaces:

  • BoolConstraint - for validating boolean values;
  • NumberConstraint - for validating numeric values;
  • StringConstraint - for validating string values;
  • ComparableConstraint - for validating generic comparable values;
  • ComparablesConstraint - for validating slice of generic comparable values;
  • CountableConstraint - for validating iterable values based only on the count of elements;
  • TimeConstraint - for validating date/time values.

Also, you can combine several types of constraints. See examples for more details:

Recommendations for storing violations in a database

If you have a need to store violations in persistent storage (database), then it is recommended to store only error code, property path, and template parameters. It is not recommended to store message templates because they can contain mistakes and can be changed more frequently than violation error codes. The better practice is to store messages in separate storage with translations and to load them by violation error codes. So make sure that violation errors codes are unique and have only one specific message template. To restore the violations from a storage load an error code, property path, template parameters, and find a message template by the violation error code. To make a violation error code unique it is recommended to use a namespaced value, for example app: product: empty tags.

Contributing

You may help this project by

  • reporting an issue;
  • making translations for error messages;
  • suggest an improvement or discuss the usability of the package.

If you'd like to contribute, see the contribution guide. Pull requests are welcome.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Documentation

Overview

Package validation provides tools for data validation. It is designed to create complex validation rules with abilities to hook into the validation process.

Index

Examples

Constants

View Source
const DefaultGroup = "default"

Variables

View Source
var (
	ErrInvalidDate       = NewError("invalid date", message.InvalidDate)
	ErrInvalidDateTime   = NewError("invalid datetime", message.InvalidDateTime)
	ErrInvalidEAN13      = NewError("invalid EAN-13", message.InvalidEAN13)
	ErrInvalidEAN8       = NewError("invalid EAN-8", message.InvalidEAN8)
	ErrInvalidEmail      = NewError("invalid email", message.InvalidEmail)
	ErrInvalidHostname   = NewError("invalid hostname", message.InvalidHostname)
	ErrInvalidIP         = NewError("invalid IP address", message.InvalidIP)
	ErrInvalidJSON       = NewError("invalid JSON", message.InvalidJSON)
	ErrInvalidTime       = NewError("invalid time", message.InvalidTime)
	ErrInvalidULID       = NewError("invalid ULID", message.InvalidULID)
	ErrInvalidUPCA       = NewError("invalid UPC-A", message.InvalidUPCA)
	ErrInvalidUPCE       = NewError("invalid UPC-E", message.InvalidUPCE)
	ErrInvalidURL        = NewError("invalid URL", message.InvalidURL)
	ErrInvalidUUID       = NewError("invalid UUID", message.InvalidUUID)
	ErrIsBlank           = NewError("is blank", message.IsBlank)
	ErrIsEqual           = NewError("is equal", message.IsEqual)
	ErrIsNil             = NewError("is nil", message.IsNil)
	ErrNoSuchChoice      = NewError("no such choice", message.NoSuchChoice)
	ErrNotBlank          = NewError("is not blank", message.NotBlank)
	ErrNotDivisible      = NewError("is not divisible", message.NotDivisible)
	ErrNotDivisibleCount = NewError("not divisible count", message.NotDivisibleCount)
	ErrNotEqual          = NewError("is not equal", message.NotEqual)
	ErrNotExactCount     = NewError("not exact count", message.NotExactCount)
	ErrNotExactLength    = NewError("not exact length", message.NotExactLength)
	ErrNotFalse          = NewError("is not false", message.NotFalse)
	ErrNotInRange        = NewError("is not in range", message.NotInRange)
	ErrNotInteger        = NewError("is not an integer", message.NotInteger)
	ErrNotNegative       = NewError("is not negative", message.NotNegative)
	ErrNotNegativeOrZero = NewError("is not negative or zero", message.NotNegativeOrZero)
	ErrNotNil            = NewError("is not nil", message.NotNil)
	ErrNotNumeric        = NewError("is not numeric", message.NotNumeric)
	ErrNotPositive       = NewError("is not positive", message.NotPositive)
	ErrNotPositiveOrZero = NewError("is not positive or zero", message.NotPositiveOrZero)
	ErrNotTrue           = NewError("is not true", message.NotTrue)
	ErrNotUnique         = NewError("is not unique", message.NotUnique)
	ErrNotValid          = NewError("is not valid", message.NotValid)
	ErrProhibitedIP      = NewError("is prohibited IP", message.ProhibitedIP)
	ErrProhibitedURL     = NewError("is prohibited URL", message.ProhibitedURL)
	ErrTooEarly          = NewError("is too early", message.TooEarly)
	ErrTooEarlyOrEqual   = NewError("is too early or equal", message.TooEarlyOrEqual)
	ErrTooFewElements    = NewError("too few elements", message.TooFewElements)
	ErrTooHigh           = NewError("is too high", message.TooHigh)
	ErrTooHighOrEqual    = NewError("is too high or equal", message.TooHighOrEqual)
	ErrTooLate           = NewError("is too late", message.TooLate)
	ErrTooLateOrEqual    = NewError("is too late or equal", message.TooLateOrEqual)
	ErrTooLong           = NewError("is too long", message.TooLong)
	ErrTooLow            = NewError("is too low", message.TooLow)
	ErrTooLowOrEqual     = NewError("is too low or equal", message.TooLowOrEqual)
	ErrTooManyElements   = NewError("too many elements", message.TooManyElements)
	ErrTooShort          = NewError("is too short", message.TooShort)
)

Functions

func Filter

func Filter(violations ...error) error

Filter is used for processing the list of errors to return a single ViolationList. If there is at least one non-violation error it will return it instead.

func IsViolation

func IsViolation(err error) bool

IsViolation can be used to verify that the error implements the Violation interface.

func IsViolationList

func IsViolationList(err error) bool

IsViolationList can be used to verify that the error implements the ViolationList.

Types

type AllArgument added in v0.12.0

type AllArgument struct {
	// contains filtered or unexported fields
}

AllArgument can be used to interrupt validation process when the first violation is raised.

func All added in v0.12.0

func All(arguments ...Argument) AllArgument

All runs validation for each argument. It works exactly as Validator.Validate method. It can be helpful to build complex validation process.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	book := struct {
		Name string
		Tags []string
	}{
		Name: "Very long book name",
		Tags: []string{"Fiction", "Thriller", "Science", "Fantasy"},
	}

	err := validator.Validate(
		context.Background(),
		validation.Sequentially(
			// this block passes
			validation.All(
				validation.StringProperty("name", book.Name, it.IsNotBlank()),
				validation.CountableProperty("tags", len(book.Tags), it.IsNotBlank()),
			),
			// this block fails
			validation.All(
				validation.StringProperty("name", book.Name, it.HasMaxLength(10)),
				validation.CountableProperty("tags", len(book.Tags), it.HasMaxCount(3)),
			),
		),
	)

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
}
Output:

violation at "name": "This value is too long. It should have 10 characters or less."
violation at "tags": "This collection should contain 3 elements or less."

func AtProperty added in v0.17.0

func AtProperty(propertyName string, arguments ...Argument) AllArgument

AtProperty can be used to group different kind of validations for a specific property. It creates an AllArgument option and executes validation on all the arguments.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	book := struct {
		Name string
		Tags []string
	}{
		Name: "Very long book name",
		Tags: []string{"Fiction", "Thriller", "", "Fiction"},
	}

	err := validator.Validate(
		context.Background(),
		validation.StringProperty("name", book.Name, it.IsNotBlank(), it.HasMaxLength(10)),
		// grouping property validation with different types of arguments
		validation.AtProperty(
			"tags",
			validation.Countable(len(book.Tags), it.HasMaxCount(3)),
			validation.Comparables[string](book.Tags, it.HasUniqueValues[string]()),
			validation.EachString(book.Tags, it.IsNotBlank()),
		),
	)

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
}
Output:

violation at "name": "This value is too long. It should have 10 characters or less."
violation at "tags": "This collection should contain 3 elements or less."
violation at "tags": "This collection should contain only unique elements."
violation at "tags[2]": "This value should not be blank."

func (AllArgument) At added in v0.13.0

At returns a copy of AllArgument with appended property path suffix.

func (AllArgument) When added in v0.12.0

func (arg AllArgument) When(condition bool) AllArgument

When enables conditional validation of this argument. If the expression evaluates to false, then the argument will be ignored.

type Argument

type Argument interface {
	// contains filtered or unexported methods
}

Argument used to set up the validation process. It is used to set up the current validation context and to pass arguments for validation values.

type ArrayIndex

type ArrayIndex int

ArrayIndex holds up array index value under PropertyPath.

func (ArrayIndex) IsIndex added in v0.13.0

func (a ArrayIndex) IsIndex() bool

IsIndex on ArrayIndex always returns true.

func (ArrayIndex) String added in v0.13.0

func (a ArrayIndex) String() string

String returns array index values converted into a string.

type AsyncArgument added in v0.12.0

type AsyncArgument struct {
	// contains filtered or unexported fields
}

AsyncArgument can be used to interrupt validation process when the first violation is raised.

func Async added in v0.12.0

func Async(arguments ...Argument) AsyncArgument

Async implements async/await pattern and runs validation for each argument in a separate goroutine.

func (AsyncArgument) At added in v0.13.0

At returns a copy of AsyncArgument with appended property path suffix.

func (AsyncArgument) When added in v0.12.0

func (arg AsyncArgument) When(condition bool) AsyncArgument

When enables conditional validation of this argument. If the expression evaluates to false, then the argument will be ignored.

type AtLeastOneOfArgument added in v0.9.0

type AtLeastOneOfArgument struct {
	// contains filtered or unexported fields
}

AtLeastOneOfArgument can be used to set up validation process to check that the value satisfies at least one of the given constraints. The validation stops as soon as one constraint is satisfied.

func AtLeastOneOf added in v0.3.0

func AtLeastOneOf(arguments ...Argument) AtLeastOneOfArgument

AtLeastOneOf can be used to set up validation process to check that the value satisfies at least one of the given constraints. The validation stops as soon as one constraint is satisfied.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	banners := []struct {
		Name      string
		Keywords  []string
		Companies []string
		Brands    []string
	}{
		{Name: "Acme banner", Companies: []string{"Acme"}},
		{Name: "Empty banner"},
	}

	for _, banner := range banners {
		err := validator.Validate(
			context.Background(),
			validation.AtLeastOneOf(
				validation.CountableProperty("keywords", len(banner.Keywords), it.IsNotBlank()),
				validation.CountableProperty("companies", len(banner.Companies), it.IsNotBlank()),
				validation.CountableProperty("brands", len(banner.Brands), it.IsNotBlank()),
			),
		)
		if violations, ok := validation.UnwrapViolationList(err); ok {
			fmt.Println("banner", banner.Name, "is not valid:")
			for violation := violations.First(); violation != nil; violation = violation.Next() {
				fmt.Println(violation)
			}
		}
	}

}
Output:

banner Empty banner is not valid:
violation at "keywords": "This value should not be blank."
violation at "companies": "This value should not be blank."
violation at "brands": "This value should not be blank."

func (AtLeastOneOfArgument) At added in v0.13.0

At returns a copy of AtLeastOneOfArgument with appended property path suffix.

func (AtLeastOneOfArgument) When added in v0.9.0

func (arg AtLeastOneOfArgument) When(condition bool) AtLeastOneOfArgument

When enables conditional validation of this argument. If the expression evaluates to false, then the argument will be ignored.

type BoolConstraint

type BoolConstraint interface {
	ValidateBool(ctx context.Context, validator *Validator, value *bool) error
}

BoolConstraint is used to build constraints for boolean values validation.

type BuiltinViolationFactory added in v0.15.0

type BuiltinViolationFactory struct {
	// contains filtered or unexported fields
}

BuiltinViolationFactory used as a default factory for creating a violations. It translates and renders message templates.

func NewViolationFactory added in v0.15.0

func NewViolationFactory(translator Translator) *BuiltinViolationFactory

NewViolationFactory creates a new BuiltinViolationFactory for creating a violations.

func (*BuiltinViolationFactory) CreateViolation added in v0.15.0

func (factory *BuiltinViolationFactory) CreateViolation(
	err error,
	messageTemplate string,
	pluralCount int,
	parameters []TemplateParameter,
	propertyPath *PropertyPath,
	lang language.Tag,
) Violation

CreateViolation creates a new instance of Violation.

type Checker added in v0.5.2

type Checker struct {
	// contains filtered or unexported fields
}

Checker is an argument that can be useful for quickly checking the result of some simple expression that returns a boolean value.

func Check added in v0.5.2

func Check(isValid bool) Checker

Check argument can be useful for quickly checking the result of some simple expression that returns a boolean value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 123
	err := validator.Validate(context.Background(), validation.Check(v > 321))
	fmt.Println(err)
}
Output:

violation: "This value is not valid."

func CheckProperty added in v0.5.2

func CheckProperty(name string, isValid bool) Checker

CheckProperty argument is an alias for Check that automatically adds property name to the current validation context. It is useful to apply a simple checks on structs.

Example
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

type Outlet struct {
	Type          string
	MainCommodity OutletCommodity
}

type OutletCommodity interface {
	Name() string
	Supports(outletType string) bool
}

type DigitalMovie struct {
	name string
}

func (m DigitalMovie) Name() string {
	return m.name
}

func (m DigitalMovie) Supports(outletType string) bool {
	return outletType == "digital"
}

var ErrUnsupportedCommodity = errors.New("unsupported commodity")

func main() {
	outlet := Outlet{
		Type:          "offline",
		MainCommodity: DigitalMovie{name: "Digital movie"},
	}

	err := validator.Validate(
		context.Background(),
		validation.
			CheckProperty("mainCommodity", outlet.MainCommodity.Supports(outlet.Type)).
			WithError(ErrUnsupportedCommodity).
			WithMessage(
				`Commodity "{{ value }}" cannot be sold at outlet.`,
				validation.TemplateParameter{Key: "{{ value }}", Value: outlet.MainCommodity.Name()},
			),
	)

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println("violation underlying error:", violation.Unwrap())
			fmt.Println(violation)
		}
	}
	fmt.Println("errors.Is(err, ErrUnsupportedCommodity) =", errors.Is(err, ErrUnsupportedCommodity))
}
Output:

violation underlying error: unsupported commodity
violation at "mainCommodity": "Commodity "Digital movie" cannot be sold at outlet."
errors.Is(err, ErrUnsupportedCommodity) = true

func (Checker) At added in v0.13.0

func (c Checker) At(path ...PropertyPathElement) Checker

At returns a copy of Checker with appended property path suffix.

func (Checker) When added in v0.5.3

func (c Checker) When(condition bool) Checker

When enables conditional validation of this constraint. If the expression evaluates to false, then the constraint will be ignored.

func (Checker) WhenGroups added in v0.8.0

func (c Checker) WhenGroups(groups ...string) Checker

WhenGroups enables conditional validation of the constraint by using the validation groups.

func (Checker) WithError added in v0.13.0

func (c Checker) WithError(err error) Checker

WithError overrides default code for produced violation.

func (Checker) WithMessage added in v0.13.0

func (c Checker) WithMessage(template string, parameters ...TemplateParameter) Checker

WithMessage sets the violation message template. You can set custom template parameters for injecting its values into the final message.

type ComparableConstraint added in v0.9.0

type ComparableConstraint[T comparable] interface {
	ValidateComparable(ctx context.Context, validator *Validator, value *T) error
}

ComparableConstraint is used to build constraints for generic comparable value validation.

type ComparablesConstraint added in v0.9.0

type ComparablesConstraint[T comparable] interface {
	ValidateComparables(ctx context.Context, validator *Validator, values []T) error
}

ComparablesConstraint is used to build constraints for generic comparable values validation.

type Constraint

type Constraint[T any] interface {
	Validate(ctx context.Context, validator *Validator, v T) error
}

Constraint is a generic interface for client-side typed constraints.

type ConstraintError added in v0.9.0

type ConstraintError struct {
	ConstraintName string
	Path           *PropertyPath
	Description    string
}

ConstraintError is used to return critical error from constraint that immediately stops the validation process. It is recommended to use Validator.CreateConstraintError method to initiate an error from current validation context.

func (*ConstraintError) Error added in v0.9.0

func (err *ConstraintError) Error() string

type ConstraintNotFoundError

type ConstraintNotFoundError struct {
	Key  string
	Type string
}

ConstraintNotFoundError is returned when trying to get a constraint from the validator store using a non-existent key.

func (*ConstraintNotFoundError) Error

func (err *ConstraintNotFoundError) Error() string

type CountableConstraint

type CountableConstraint interface {
	ValidateCountable(ctx context.Context, validator *Validator, count int) error
}

CountableConstraint is used to build constraints for simpler validation of iterable elements count.

type Error added in v0.13.0

type Error struct {
	// contains filtered or unexported fields
}

Error is a base type for static validation error used as an underlying error for Violation. It can be used to programmatically test for a specific violation. Error code values are protected by backward compatibility rules, message values are not protected.

func NewError added in v0.13.0

func NewError(code string, message string) *Error

NewError creates a static validation error. It should be used to create only package-level errors.

func (*Error) Error added in v0.13.0

func (err *Error) Error() string

Error returns error code. This code is protected by backward compatibility rules.

func (*Error) Message added in v0.13.0

func (err *Error) Message() string

Message returns message template that will be shown to the end user. Be aware. This message is not protected by backward compatibility rules and may be changed even in patch versions.

type NewViolationFunc

type NewViolationFunc func(
	err error,
	messageTemplate string,
	pluralCount int,
	parameters []TemplateParameter,
	propertyPath *PropertyPath,
	lang language.Tag,
) Violation

NewViolationFunc is an adapter that allows you to use ordinary functions as a ViolationFactory.

func (NewViolationFunc) CreateViolation

func (f NewViolationFunc) CreateViolation(
	err error,
	messageTemplate string,
	pluralCount int,
	parameters []TemplateParameter,
	propertyPath *PropertyPath,
	lang language.Tag,
) Violation

CreateViolation creates a new instance of a Violation.

type NilConstraint

type NilConstraint interface {
	ValidateNil(ctx context.Context, validator *Validator, isNil bool) error
}

NilConstraint is used for a special cases to check a value for nil.

type NumberConstraint

type NumberConstraint[T Numeric] interface {
	ValidateNumber(ctx context.Context, validator *Validator, value *T) error
}

NumberConstraint is used to build constraints for numeric values validation.

type Numeric added in v0.9.0

type Numeric interface {
	~float32 | ~float64 |
		~int | ~int8 | ~int16 | ~int32 | ~int64 |
		~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64
}

Numeric is used as a type parameter for numeric values.

type PropertyName

type PropertyName string

PropertyName holds up property name value under PropertyPath.

func (PropertyName) IsIndex added in v0.13.0

func (p PropertyName) IsIndex() bool

IsIndex on PropertyName always returns false.

func (PropertyName) String added in v0.13.0

func (p PropertyName) String() string

String returns property name as is.

type PropertyPath

type PropertyPath struct {
	// contains filtered or unexported fields
}

PropertyPath is generated by the validator and indicates how it reached the invalid value from the root element. Property path is denoted by dots, while array access is denoted by square brackets. For example, "book.keywords[0]" means that the violation occurred on the first element of array "keywords" in the "book" object.

Internally PropertyPath is a linked list. You can create a new path using PropertyPath.WithProperty or PropertyPath.WithIndex methods. PropertyPath should always be used as a pointer value. Nil value is a valid value that means that the property path is empty.

func NewPropertyPath added in v0.2.0

func NewPropertyPath(elements ...PropertyPathElement) *PropertyPath

NewPropertyPath creates a PropertyPath from the list of elements. If the list is empty nil will be returned. Nil value is a valid value that means that the property path is empty.

func (*PropertyPath) Elements added in v0.14.0

func (path *PropertyPath) Elements() []PropertyPathElement

Elements returns property path as a slice of PropertyPathElement. It returns nil if property path is nil (empty).

func (*PropertyPath) Len added in v0.14.0

func (path *PropertyPath) Len() int

Len returns count of property path elements.

func (*PropertyPath) MarshalText added in v0.14.0

func (path *PropertyPath) MarshalText() (text []byte, err error)

MarshalText will marshal property path value to a string.

func (*PropertyPath) String

func (path *PropertyPath) String() string

String is used to format property path to a string.

func (*PropertyPath) UnmarshalText added in v0.14.0

func (path *PropertyPath) UnmarshalText(text []byte) error

UnmarshalText unmarshal string representation of property path into PropertyPath.

func (*PropertyPath) With added in v0.11.0

func (path *PropertyPath) With(elements ...PropertyPathElement) *PropertyPath

With returns new PropertyPath with appended elements to the end of the list.

func (*PropertyPath) WithIndex added in v0.2.0

func (path *PropertyPath) WithIndex(index int) *PropertyPath

WithIndex returns new PropertyPath with appended ArrayIndex to the end of the list.

func (*PropertyPath) WithProperty added in v0.2.0

func (path *PropertyPath) WithProperty(name string) *PropertyPath

WithProperty returns new PropertyPath with appended PropertyName to the end of the list.

type PropertyPathElement

type PropertyPathElement interface {
	// IsIndex can be used to determine whether an element is a string (property name) or
	// an index array.
	IsIndex() bool
	fmt.Stringer
}

PropertyPathElement is a part of the PropertyPath.

type SequentialArgument added in v0.9.0

type SequentialArgument struct {
	// contains filtered or unexported fields
}

SequentialArgument can be used to interrupt validation process when the first violation is raised.

func Sequentially added in v0.2.0

func Sequentially(arguments ...Argument) SequentialArgument

Sequentially function used to run validation process step-by-step.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	title := "bar"

	err := validator.Validate(
		context.Background(),
		validation.Sequentially(
			validation.String(title, it.IsBlank()),       // validation will fail on first argument
			validation.String(title, it.HasMinLength(5)), // this argument will be ignored
		),
	)

	fmt.Println(err)
}
Output:

violation: "This value should be blank."

func (SequentialArgument) At added in v0.13.0

At returns a copy of SequentialArgument with appended property path suffix.

func (SequentialArgument) When added in v0.9.0

func (arg SequentialArgument) When(condition bool) SequentialArgument

When enables conditional validation of this argument. If the expression evaluates to false, then the argument will be ignored.

type StringConstraint

type StringConstraint interface {
	ValidateString(ctx context.Context, validator *Validator, value *string) error
}

StringConstraint is used to build constraints for string values validation.

type StringFuncConstraint added in v0.13.0

type StringFuncConstraint struct {
	// contains filtered or unexported fields
}

StringFuncConstraint can be used to create constraints for validating string values based on function with signature func(string) bool.

func OfStringBy added in v0.13.0

func OfStringBy(isValid func(string) bool) StringFuncConstraint

OfStringBy creates a new string constraint from a function with signature func(string) bool.

Example
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

func main() {
	validate := func(s string) bool {
		return s == "valid"
	}
	errExample := errors.New("exampleCode")

	constraint := validation.OfStringBy(validate).
		WithError(errExample).           // underlying static error
		WithMessage("Unexpected value.") // violation message template

	s := "foo"
	err := validator.Validate(context.Background(), validation.String(s, constraint))

	fmt.Println(err)
}
Output:

violation: "Unexpected value."

func (StringFuncConstraint) ValidateString added in v0.13.0

func (c StringFuncConstraint) ValidateString(ctx context.Context, validator *Validator, value *string) error

func (StringFuncConstraint) When added in v0.13.0

When enables conditional validation of this constraint. If the expression evaluates to false, then the constraint will be ignored.

func (StringFuncConstraint) WhenGroups added in v0.13.0

func (c StringFuncConstraint) WhenGroups(groups ...string) StringFuncConstraint

WhenGroups enables conditional validation of the constraint by using the validation groups.

func (StringFuncConstraint) WithError added in v0.13.0

WithError overrides default error for produced violation.

func (StringFuncConstraint) WithMessage added in v0.13.0

func (c StringFuncConstraint) WithMessage(template string, parameters ...TemplateParameter) StringFuncConstraint

WithMessage sets the violation message template. You can set custom template parameters for injecting its values into the final message. Also, you can use default parameters:

{{ value }} - the current (invalid) value.

type TemplateParameter

type TemplateParameter struct {
	// Key is the marker in the string that will be replaced by value.
	// In general, it is recommended to use double curly braces around the key name.
	// Example: {{ keyName }}
	Key string

	// Value is set by constraint when building violation.
	Value string

	// NeedsTranslation marks that the template value needs to be translated.
	NeedsTranslation bool
}

TemplateParameter is injected into the message while rendering the template.

type TemplateParameterList added in v0.3.0

type TemplateParameterList []TemplateParameter

TemplateParameterList is a list of template parameters that can be injection into violation message.

func (TemplateParameterList) Prepend added in v0.3.0

func (params TemplateParameterList) Prepend(parameters ...TemplateParameter) TemplateParameterList

Prepend returns TemplateParameterList prepended by given parameters.

type TimeConstraint

type TimeConstraint interface {
	ValidateTime(ctx context.Context, validator *Validator, value *time.Time) error
}

TimeConstraint is used to build constraints for date/time validation.

type Translator

type Translator interface {
	Translate(tag language.Tag, message string, pluralCount int) string
}

Translator is used to translate violation messages. By default, validator uses an implementation from github.com/muonsoft/validation/message/translations package based on golang.org/x/text package. You can set up your own implementation by using SetTranslator option.

type Validatable

type Validatable interface {
	Validate(ctx context.Context, validator *Validator) error
}

Validatable is interface for creating validatable types on the client side. By using it you can build complex validation rules on a set of objects used in other objects.

Example

type Book struct {
    Title    string
    Author   string
    Keywords []string
}

func (b Book) Validate(ctx context.Context, validator *validation.Validator) error {
    return validator.Validate(
        ctx,
        validation.StringProperty("title", &b.Title, it.IsNotBlank()),
        validation.StringProperty("author", &b.Author, it.IsNotBlank()),
        validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)),
        validation.EachStringProperty("keywords", b.Keywords, it.IsNotBlank()),
    )
}

type ValidatableFunc added in v0.15.0

type ValidatableFunc func(ctx context.Context, validator *Validator) error

ValidatableFunc is a functional adapter for the Validatable interface.

func (ValidatableFunc) Validate added in v0.15.0

func (f ValidatableFunc) Validate(ctx context.Context, validator *Validator) error

Validate runs validation process on function.

Example
package main

import (
	"context"
	"fmt"
	"net/url"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type Webstore struct {
	Name  string
	URL   string
	Items WebstoreItems
}

func (w Webstore) Validate(ctx context.Context, validator *validation.Validator) error {
	domain := ""
	u, err := url.Parse(w.URL)
	if err == nil {
		domain = u.Host
	}

	return validator.Validate(
		ctx,
		validation.StringProperty("name", w.Name, it.IsNotBlank(), it.HasMaxLength(100)),
		validation.StringProperty("url", w.URL, it.IsNotBlank(), it.IsURL()),
		validation.CountableProperty("items", len(w.Items), it.IsNotBlank(), it.HasMaxCount(20)),
		validation.ValidProperty(
			"items",
			validation.ValidatableFunc(func(ctx context.Context, validator *validation.Validator) error {
				// passing context value by callback function
				return w.Items.Validate(ctx, validator, domain)
			}),
		),
	)
}

type WebstoreItem struct {
	Name  string
	URL   string
	Price int
}

func (w WebstoreItem) Validate(ctx context.Context, validator *validation.Validator, webstoreDomain string) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", w.Name, it.IsNotBlank(), it.HasMaxLength(100)),
		validation.NumberProperty[int]("price", w.Price, it.IsNotBlankNumber[int](), it.IsLessThan[int](10000)),
		validation.StringProperty(
			"url", w.URL,
			it.IsNotBlank(),
			// using webstore domain passed as a function parameter
			it.IsURL().WithHosts(webstoreDomain).WithProhibitedMessage(
				`Webstore item URL domain must match "{{ webstoreDomain }}".`,
				validation.TemplateParameter{Key: "{{ webstoreDomain }}", Value: webstoreDomain},
			),
		),
	)
}

type WebstoreItems []WebstoreItem

func (items WebstoreItems) Validate(ctx context.Context, validator *validation.Validator, webstoreDomain string) error {
	violations := validation.NewViolationList()

	for i, item := range items {
		// passing context value to each item
		err := violations.AppendFromError(item.Validate(ctx, validator.AtIndex(i), webstoreDomain))
		if err != nil {
			return err
		}
	}

	return violations.AsError()
}

func main() {
	store := Webstore{
		Name: "Acme store",
		URL:  "https://acme.com/homepage",
		Items: []WebstoreItem{
			{
				Name:  "Book",
				URL:   "https://acme.com/items/the-book",
				Price: 100,
			},
			{
				Name:  "Notepad",
				URL:   "https://store.com/items/notepad",
				Price: 1000,
			},
		},
	}

	err := validator.Validate(context.Background(), validation.Valid(store))

	if violations, ok := validation.UnwrapViolationList(err); ok {
		violations.ForEach(func(i int, violation validation.Violation) error {
			fmt.Println(violation)
			return nil
		})
	}
}
Output:

violation at "items[1].url": "Webstore item URL domain must match "acme.com"."

type ValidateFunc added in v0.13.0

type ValidateFunc func(ctx context.Context, validator *Validator) (*ViolationList, error)

type Validator

type Validator struct {
	// contains filtered or unexported fields
}

Validator is the root validation service. It can be created by NewValidator constructor. Also, you can use singleton version from the package github.com/muonsoft/validation/validator.

func NewValidator

func NewValidator(options ...ValidatorOption) (*Validator, error)

NewValidator is a constructor for creating an instance of Validator. You can configure it by using the ValidatorOption.

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(
		validation.DefaultLanguage(language.English), // passing default language of translations
		validation.Translations(russian.Messages),    // setting up custom or built-in translations
		// validation.SetViolationFactory(userViolationFactory), // if you want to override creation of violations
	)
	// don't forget to check for errors
	if err != nil {
		log.Fatal(err)
	}

	err = validator.Validate(context.Background(), validation.String("", it.IsNotBlank()))
	fmt.Println(err)
}
Output:

violation: "This value should not be blank."

func (*Validator) At added in v0.13.0

func (validator *Validator) At(path ...PropertyPathElement) *Validator

At method creates a new context validator with appended property path.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	books := []struct {
		Title string
	}{
		{Title: ""},
	}

	err := validator.At(validation.PropertyName("books"), validation.ArrayIndex(0)).Validate(
		context.Background(),
		validation.StringProperty("title", books[0].Title, it.IsNotBlank()),
	)

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: books[0].title

func (*Validator) AtIndex

func (validator *Validator) AtIndex(index int) *Validator

AtIndex method creates a new context validator with appended array index to the property path.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	books := []struct {
		Title string
	}{
		{Title: ""},
	}

	err := validator.AtIndex(0).Validate(
		context.Background(),
		validation.StringProperty("title", books[0].Title, it.IsNotBlank()),
	)

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: [0].title

func (*Validator) AtProperty

func (validator *Validator) AtProperty(name string) *Validator

AtProperty method creates a new context validator with appended property name to the property path.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	book := struct {
		Title string
	}{
		Title: "",
	}

	err := validator.AtProperty("book").Validate(
		context.Background(),
		validation.StringProperty("title", book.Title, it.IsNotBlank()),
	)

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: book.title

func (*Validator) BuildViolation

func (validator *Validator) BuildViolation(ctx context.Context, err error, message string) *ViolationBuilder

BuildViolation can be used to build a custom violation on the client-side.

Example (BuildingViolation)
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/muonsoft/validation"
)

func main() {
	validator, err := validation.NewValidator()
	if err != nil {
		log.Fatal(err)
	}
	errClient := errors.New("client error")

	violation := validator.BuildViolation(context.Background(), errClient, "Client message with {{ parameter }}.").
		WithParameter("{{ parameter }}", "value").
		Create()

	fmt.Println(violation.Message())
}
Output:

Client message with value.
Example (TranslatableParameter)
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"golang.org/x/text/message/catalog"
)

func main() {
	validator, err := validation.NewValidator(
		validation.Translations(map[language.Tag]map[string]catalog.Message{
			language.Russian: {
				"The operation is only possible for the {{ role }}.": catalog.String("Операция возможна только для {{ role }}."),
				"administrator role": catalog.String("роли администратора"),
			},
		}),
	)
	if err != nil {
		log.Fatal(err)
	}
	errClient := errors.New("client error")

	violation := validator.WithLanguage(language.Russian).
		BuildViolation(context.Background(), errClient, "The operation is only possible for the {{ role }}.").
		WithParameters(validation.TemplateParameter{
			Key:              "{{ role }}",
			Value:            "administrator role",
			NeedsTranslation: true,
		}).
		Create()

	fmt.Println(violation.Message())
}
Output:

Операция возможна только для роли администратора.

func (*Validator) BuildViolationList added in v0.11.0

func (validator *Validator) BuildViolationList(ctx context.Context) *ViolationListBuilder

BuildViolationList can be used to build a custom violation list on the client-side.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/muonsoft/validation"
)

func main() {
	validator, err := validation.NewValidator()
	if err != nil {
		log.Fatal(err)
	}
	errFirst := errors.New("error 1")
	errSecond := errors.New("error 2")

	builder := validator.BuildViolationList(context.Background())
	builder.BuildViolation(errFirst, "Client message with {{ parameter 1 }}.").
		WithParameter("{{ parameter 1 }}", "value 1").
		AtProperty("properties").AtIndex(0).
		Add()
	builder.BuildViolation(errSecond, "Client message with {{ parameter 2 }}.").
		WithParameter("{{ parameter 2 }}", "value 2").
		AtProperty("properties").AtIndex(1).
		Add()
	violations := builder.Create()

	violations.ForEach(func(i int, violation validation.Violation) error {
		fmt.Println(violation.Error())
		return nil
	})
	fmt.Println("errors.Is(violations, errFirst) =", errors.Is(violations, errFirst))
	fmt.Println("errors.Is(violations, errSecond) =", errors.Is(violations, errSecond))
}
Output:

violation at "properties[0]": "Client message with value 1."
violation at "properties[1]": "Client message with value 2."
errors.Is(violations, errFirst) = true
errors.Is(violations, errSecond) = true

func (*Validator) CreateConstraintError added in v0.13.0

func (validator *Validator) CreateConstraintError(constraintName, description string) *ConstraintError

CreateConstraintError creates a new ConstraintError, which can be used to stop validation process if constraint is not properly configured.

func (*Validator) CreateViolation added in v0.11.0

func (validator *Validator) CreateViolation(ctx context.Context, err error, message string, path ...PropertyPathElement) Violation

CreateViolation can be used to quickly create a custom violation on the client-side.

Example
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/muonsoft/validation"
)

func main() {
	validator, err := validation.NewValidator()
	if err != nil {
		log.Fatal(err)
	}
	errClient := errors.New("client error")

	violation := validator.CreateViolation(
		context.Background(),
		errClient,
		"Client message.",
		validation.PropertyName("properties"),
		validation.ArrayIndex(1),
	)

	fmt.Println(violation.Error())
}
Output:

violation at "properties[1]": "Client message."

func (*Validator) IsAppliedForGroups added in v0.13.0

func (validator *Validator) IsAppliedForGroups(groups ...string) bool

IsAppliedForGroups compares current validation groups and constraint groups. If one of the validator groups intersects with the constraint groups, the validation process should be applied (returns true). Empty groups are treated as DefaultGroup. To create a new validator with the validation groups use the Validator.WithGroups method.

func (*Validator) IsIgnoredForGroups added in v0.13.0

func (validator *Validator) IsIgnoredForGroups(groups ...string) bool

IsIgnoredForGroups is the reverse condition for applying validation groups to the Validator.IsAppliedForGroups method. It is recommended to use this method in every validation method of the constraint.

func (*Validator) Validate

func (validator *Validator) Validate(ctx context.Context, arguments ...Argument) error

Validate is the main validation method. It accepts validation arguments that can be used to tune up the validation process or to pass values of a specific type.

Example (BasicStructValidation)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	document := struct {
		Title    string
		Keywords []string
	}{
		Title:    "",
		Keywords: []string{"", "book", "fantasy", "book"},
	}

	err := validator.Validate(
		context.Background(),
		validation.StringProperty("title", document.Title, it.IsNotBlank()),
		validation.CountableProperty("keywords", len(document.Keywords), it.HasCountBetween(5, 10)),
		validation.ComparablesProperty[string]("keywords", document.Keywords, it.HasUniqueValues[string]()),
		validation.EachStringProperty("keywords", document.Keywords, it.IsNotBlank()),
	)

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
}
Output:

violation at "title": "This value should not be blank."
violation at "keywords": "This collection should contain 5 elements or more."
violation at "keywords": "This collection should contain only unique elements."
violation at "keywords[0]": "This value should not be blank."
Example (BasicValidation)
package main

import (
	"context"
	"errors"
	"fmt"
	"log"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
)

func main() {
	validator, err := validation.NewValidator()
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	err = validator.Validate(context.Background(), validation.String(s, it.IsNotBlank()))

	fmt.Println(err)
	fmt.Println("errors.Is(err, validation.ErrIsBlank) =", errors.Is(err, validation.ErrIsBlank))
}
Output:

violation: "This value should not be blank."
errors.Is(err, validation.ErrIsBlank) = true
Example (ConditionalValidationOnConstraint)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	notes := []struct {
		Title    string
		IsPublic bool
		Text     string
	}{
		{Title: "published note", IsPublic: true, Text: "text of published note"},
		{Title: "draft note", IsPublic: true, Text: ""},
	}

	for i, note := range notes {
		err := validator.Validate(
			context.Background(),
			validation.StringProperty("name", note.Title, it.IsNotBlank()),
			validation.StringProperty("text", note.Text, it.IsNotBlank().When(note.IsPublic)),
		)
		if violations, ok := validation.UnwrapViolationList(err); ok {
			for violation := violations.First(); violation != nil; violation = violation.Next() {
				fmt.Printf("error on note %d: %s", i, violation)
			}
		}
	}

}
Output:

error on note 1: violation at "text": "This value should not be blank."
Example (CustomConstraint)
package main

import (
	"context"
	"errors"
	"fmt"
	"regexp"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

var ErrNotNumeric = errors.New("not numeric")

type NumericConstraint struct {
	matcher *regexp.Regexp
}

// it is recommended to use semantic constructors for constraints.
func IsNumeric() NumericConstraint {
	return NumericConstraint{matcher: regexp.MustCompile("^[0-9]+$")}
}

func (c NumericConstraint) ValidateString(ctx context.Context, validator *validation.Validator, value *string) error {
	// usually, you should ignore empty values
	// to check for an empty value you should use it.NotBlankConstraint
	if value == nil || *value == "" {
		return nil
	}

	if c.matcher.MatchString(*value) {
		return nil
	}

	// use the validator to build violation with translations
	return validator.CreateViolation(ctx, ErrNotNumeric, "This value should be numeric.")
}

func main() {
	s := "alpha"

	err := validator.Validate(
		context.Background(),
		validation.String(s, it.IsNotBlank(), IsNumeric()),
	)

	fmt.Println(err)
	fmt.Println("errors.Is(err, ErrNotNumeric) =", errors.Is(err, ErrNotNumeric))
}
Output:

violation: "This value should be numeric."
errors.Is(err, ErrNotNumeric) = true
Example (CustomizingErrorMessage)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := ""

	err := validator.Validate(
		context.Background(),
		validation.String(s, it.IsNotBlank().WithMessage("this value is required")),
	)

	fmt.Println(err)
}
Output:

violation: "this value is required"
Example (HttpHandler)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

type Book struct {
	Title    string   `json:"title"`
	Author   string   `json:"author"`
	Keywords []string `json:"keywords"`
}

func (b Book) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("title", b.Title, it.IsNotBlank()),
		validation.StringProperty("author", b.Author, it.IsNotBlank()),
		validation.CountableProperty("keywords", len(b.Keywords), it.HasCountBetween(1, 10)),
		validation.EachStringProperty("keywords", b.Keywords, it.IsNotBlank()),
	)
}

func HandleBooks(writer http.ResponseWriter, request *http.Request) {
	var book Book
	err := json.NewDecoder(request.Body).Decode(&book)
	if err != nil {
		http.Error(writer, "invalid request", http.StatusBadRequest)
		return
	}

	// setting up validator
	validator, err := validation.NewValidator(validation.Translations(russian.Messages))
	if err != nil {
		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	err = validator.Validate(request.Context(), validation.Valid(book))
	if err != nil {
		violations, ok := validation.UnwrapViolationList(err)
		if ok {
			response, err := json.Marshal(violations)
			if err != nil {
				log.Fatal(err)
			}
			writer.WriteHeader(http.StatusUnprocessableEntity)
			writer.Header().Set("Content-Type", "application/json")
			writer.Write(response)
			return
		}

		http.Error(writer, err.Error(), http.StatusInternalServerError)
		return
	}

	// handle valid book

	writer.WriteHeader(http.StatusCreated)
	writer.Write([]byte("ok"))
}

func main() {
	var handler http.Handler
	handler = http.HandlerFunc(HandleBooks)
	// middleware set up: we need to set supported languages
	// detected language will be passed via request context
	handler = language.NewMiddleware(handler, language.SupportedLanguages(language.English, language.Russian))

	// creating request with the language-specific header
	request := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(`{}`))
	request.Header.Set("Accept-Language", "ru")

	recorder := httptest.NewRecorder()
	handler.ServeHTTP(recorder, request)

	// recorded response should contain array of violations
	fmt.Println(recorder.Body.String())
}
Output:

[{"error":"is blank","message":"Значение не должно быть пустым.","propertyPath":"title"},{"error":"is blank","message":"Значение не должно быть пустым.","propertyPath":"author"},{"error":"too few elements","message":"Эта коллекция должна содержать 1 элемент или больше.","propertyPath":"keywords"}]
Example (PartialEntityValidation)
package main

import (
	"bytes"
	"context"
	"fmt"
	"path/filepath"
	"strings"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type File struct {
	Name string
	Data []byte
}

// This validation will always check that file is valid.
// Partial validation will be applied by AllowedFileExtensionConstraint
// and AllowedFileSizeConstraint.
func (f File) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", f.Name, it.HasLengthBetween(5, 50)),
	)
}

type FileUploadRequest struct {
	Section string
	File    *File
}

type FileConstraint interface {
	ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error
}

func ValidFile(file *File, constraints ...FileConstraint) validation.ValidatorArgument {
	return validation.NewArgument(func(ctx context.Context, validator *validation.Validator) (*validation.ViolationList, error) {
		violations := validation.NewViolationList()

		for _, constraint := range constraints {
			err := violations.AppendFromError(constraint.ValidateFile(ctx, validator, file))
			if err != nil {
				return nil, err
			}
		}

		return violations, nil
	})
}

// AllowedFileExtensionConstraint used to check that file has one of allowed extensions.
// This constraint can be used for partial validation.
type AllowedFileExtensionConstraint struct {
	extensions []string
}

func FileHasAllowedExtension(extensions ...string) AllowedFileExtensionConstraint {
	return AllowedFileExtensionConstraint{extensions: extensions}
}

func (c AllowedFileExtensionConstraint) ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error {
	if file == nil {
		return nil
	}

	extension := strings.ReplaceAll(filepath.Ext(file.Name), ".", "")

	return validator.AtProperty("name").Validate(
		ctx,
		validation.Comparable[string](
			extension,
			it.IsOneOf(c.extensions...).WithMessage("Not allowed extension. Must be one of: {{ choices }}."),
		),
	)
}

// AllowedFileSizeConstraint used to check that file has limited size.
// This constraint can be used for partial validation.
type AllowedFileSizeConstraint struct {
	minSize int
	maxSize int
}

func FileHasAllowedSize(min, max int) AllowedFileSizeConstraint {
	return AllowedFileSizeConstraint{minSize: min, maxSize: max}
}

func (c AllowedFileSizeConstraint) ValidateFile(ctx context.Context, validator *validation.Validator, file *File) error {
	if file == nil {
		return nil
	}

	size := len(file.Data)

	return validator.Validate(
		ctx,
		validation.Number[int](
			size,
			it.IsGreaterThan(c.minSize).WithMessage("File size is too small."),
			it.IsLessThan(c.maxSize).WithMessage("File size is too large."),
		),
	)
}

func main() {
	// this constraints will be applied to all files uploaded as avatars
	avatarConstraints := []FileConstraint{
		FileHasAllowedExtension("jpeg", "jpg", "gif"),
		FileHasAllowedSize(100, 1000),
	}
	// this constraints will be applied to all files uploaded as documents
	documentConstraints := []FileConstraint{
		FileHasAllowedExtension("doc", "pdf", "txt"),
		FileHasAllowedSize(1000, 100000),
	}

	requests := []FileUploadRequest{
		{
			Section: "avatars",
			File:    &File{Name: "avatar.png", Data: bytes.Repeat([]byte{0}, 99)},
		},
		{
			Section: "documents",
			File:    &File{Name: "sheet.xls", Data: bytes.Repeat([]byte{0}, 100001)},
		},
	}

	for _, request := range requests {
		switch request.Section {
		case "avatars":
			err := validator.Validate(
				context.Background(),
				// common validation of validatable
				validation.Valid(request.File),
				// specific validation for file storage section
				ValidFile(request.File, avatarConstraints...),
			)
			fmt.Println(err)
		case "documents":
			err := validator.Validate(
				context.Background(),
				// common validation of validatable
				validation.Valid(request.File),
				// specific validation for file storage section
				ValidFile(request.File, documentConstraints...),
			)
			fmt.Println(err)
		}
	}

}
Output:

violations: #0 at "name": "Not allowed extension. Must be one of: jpeg, jpg, gif."; #1: "File size is too small."
violations: #0 at "name": "Not allowed extension. Must be one of: doc, pdf, txt."; #1: "File size is too large."
Example (PassingPropertyPathViaOptions)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := ""

	err := validator.Validate(
		context.Background(),
		validation.String(s, it.IsNotBlank()).At(
			validation.PropertyName("properties"),
			validation.ArrayIndex(1),
			validation.PropertyName("tag"),
		),
	)

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: properties[1].tag
Example (PropertyPathBySpecialArgument)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := ""

	err := validator.Validate(
		context.Background(),
		// this is an alias for
		// validation.String(s, it.IsNotBlank()).At(validation.PropertyName("property")),
		validation.StringProperty("property", s, it.IsNotBlank()),
	)

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: property
Example (PropertyPathWithContextValidator)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := ""

	err := validator.
		AtProperty("properties").
		AtIndex(1).
		AtProperty("tag").
		Validate(context.Background(), validation.String(s, it.IsNotBlank()))

	violation := err.(*validation.ViolationList).First()
	fmt.Println("property path:", violation.PropertyPath().String())
}
Output:

property path: properties[1].tag
Example (SingletonValidator)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := ""

	err := validator.Validate(context.Background(), validation.String(s, it.IsNotBlank()))

	fmt.Println(err)
}
Output:

violation: "This value should not be blank."
Example (TranslationForCustomMessage)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"golang.org/x/text/feature/plural"
	"golang.org/x/text/message/catalog"
)

func main() {
	const customMessage = "tags should contain more than {{ limit }} element(s)"
	validator, err := validation.NewValidator(
		validation.Translations(map[language.Tag]map[string]catalog.Message{
			language.Russian: {
				customMessage: plural.Selectf(1, "",
					plural.One, "теги должны содержать {{ limit }} элемент и более",
					plural.Few, "теги должны содержать более {{ limit }} элемента",
					plural.Other, "теги должны содержать более {{ limit }} элементов"),
			},
		}),
	)
	if err != nil {
		log.Fatal(err)
	}

	var tags []string
	err = validator.WithLanguage(language.Russian).Validate(
		context.Background(),
		validation.Countable(len(tags), it.HasMinCount(1).WithMinMessage(customMessage)),
	)

	fmt.Println(err)
}
Output:

violation: "теги должны содержать 1 элемент и более"
Example (TranslationsByContextArgument)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(
		validation.Translations(russian.Messages),
	)
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	ctx := language.WithContext(context.Background(), language.Russian)
	err = validator.Validate(
		ctx,
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

violation: "Значение не должно быть пустым."
Example (TranslationsByContextualValidator)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(
		validation.Translations(russian.Messages),
	)
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	err = validator.WithLanguage(language.Russian).Validate(
		context.Background(),
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

violation: "Значение не должно быть пустым."
Example (TranslationsByDefaultLanguage)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(
		validation.Translations(russian.Messages),
		validation.DefaultLanguage(language.Russian),
	)
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	err = validator.Validate(context.Background(), validation.String(s, it.IsNotBlank()))

	fmt.Println(err)
}
Output:

violation: "Значение не должно быть пустым."
Example (UsingContextWithRecursion)
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

var ErrNestingLimitReached = errors.New("nesting limit reached")

// It is recommended to make a custom constraint to check for nesting limit.
type NestingLimitConstraint struct {
	limit int
}

func (c NestingLimitConstraint) ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error {
	level, ok := ctx.Value(nestingLevelKey).(int)
	if !ok {
		// Don't forget to handle missing value.
		return fmt.Errorf("nesting level not found in context")
	}

	if level >= c.limit {
		return validator.CreateViolation(ctx, ErrNestingLimitReached, "Maximum nesting level reached.")
	}

	return nil
}

func ItIsNotDeeperThan(limit int) NestingLimitConstraint {
	return NestingLimitConstraint{limit: limit}
}

// Properties can be nested.
type Property struct {
	Name       string
	Properties []Property
}

// You can declare you own constraint interface to create custom constraints.
type PropertyConstraint interface {
	ValidateProperty(ctx context.Context, validator *validation.Validator, property *Property) error
}

// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.NewArgument constructor.
func ValidProperty(property *Property, constraints ...PropertyConstraint) validation.ValidatorArgument {
	return validation.NewArgument(func(ctx context.Context, validator *validation.Validator) (*validation.ViolationList, error) {
		violations := validation.NewViolationList()

		for i := range constraints {
			err := violations.AppendFromError(constraints[i].ValidateProperty(ctx, validator, property))
			if err != nil {
				return nil, err
			}
		}

		return violations, nil
	})
}

type recursionKey string

const nestingLevelKey recursionKey = "nestingLevel"

func (p Property) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		// Incrementing nesting level in context with special function.
		contextWithNextNestingLevel(ctx),
		// Executing validation for maximum nesting level of properties.
		ValidProperty(&p, ItIsNotDeeperThan(3)),
		validation.StringProperty("name", p.Name, it.IsNotBlank()),
		// This should run recursive validation for properties.
		validation.ValidSliceProperty("properties", p.Properties),
	)
}

// This function increments current nesting level.
func contextWithNextNestingLevel(ctx context.Context) context.Context {
	level, ok := ctx.Value(nestingLevelKey).(int)
	if !ok {
		level = -1
	}

	return context.WithValue(ctx, nestingLevelKey, level+1)
}

func main() {
	properties := []Property{
		{
			Name: "top",
			Properties: []Property{
				{
					Name: "middle",
					Properties: []Property{
						{
							Name: "low",
							Properties: []Property{
								// This property should cause a violation.
								{Name: "limited"},
							},
						},
					},
				},
			},
		},
	}

	err := validator.Validate(context.Background(), validation.ValidSlice(properties))

	fmt.Println(err)
	fmt.Println("errors.Is(err, ErrNestingLimitReached) =", errors.Is(err, ErrNestingLimitReached))
}
Output:

violation at "[0].properties[0].properties[0].properties[0]": "Maximum nesting level reached."
errors.Is(err, ErrNestingLimitReached) = true

func (*Validator) ValidateBool

func (validator *Validator) ValidateBool(ctx context.Context, value bool, constraints ...BoolConstraint) error

ValidateBool is an alias for validating a single boolean value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := false
	err := validator.ValidateBool(context.Background(), v, it.IsTrue())
	fmt.Println(err)
}
Output:

violation: "This value should be true."

func (*Validator) ValidateCountable

func (validator *Validator) ValidateCountable(ctx context.Context, count int, constraints ...CountableConstraint) error

ValidateCountable is an alias for validating a single countable value (an array, slice, or map).

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := []string{"a", "b"}
	err := validator.ValidateCountable(context.Background(), len(s), it.HasMinCount(3))
	fmt.Println(err)
}
Output:

violation: "This collection should contain 3 elements or more."

func (*Validator) ValidateEachString

func (validator *Validator) ValidateEachString(ctx context.Context, values []string, constraints ...StringConstraint) error

ValidateEachString is an alias for validating each value of a strings slice.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []string{""}
	err := validator.ValidateEachString(context.Background(), v, it.IsNotBlank())
	fmt.Println(err)
}
Output:

violation at "[0]": "This value should not be blank."

func (*Validator) ValidateFloat added in v0.9.0

func (validator *Validator) ValidateFloat(ctx context.Context, value float64, constraints ...NumberConstraint[float64]) error

ValidateFloat is an alias for validating a single float value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5.5
	err := validator.ValidateFloat(context.Background(), v, it.IsGreaterThan(6.5))
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 6.5."

func (*Validator) ValidateInt added in v0.9.0

func (validator *Validator) ValidateInt(ctx context.Context, value int, constraints ...NumberConstraint[int]) error

ValidateInt is an alias for validating a single integer value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5
	err := validator.ValidateInt(context.Background(), v, it.IsGreaterThan(5))
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 5."

func (*Validator) ValidateIt added in v0.9.0

func (validator *Validator) ValidateIt(ctx context.Context, validatable Validatable) error

ValidateIt is an alias for validating value that implements the Validatable interface.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type Person struct {
	Name    string
	Surname string
	Age     int
}

func (p Person) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(ctx,
		validation.StringProperty("name", p.Name, it.IsNotBlank(), it.HasMaxLength(50)),
		validation.StringProperty("surname", p.Surname, it.IsNotBlank(), it.HasMaxLength(100)),
		validation.NumberProperty[int]("age", p.Age, it.IsBetween(18, 100)),
	)
}

func main() {
	persons := []Person{
		{
			Name:    "John",
			Surname: "Doe",
			Age:     23,
		},
		{
			Name:    "",
			Surname: "",
			Age:     0,
		},
	}

	for i, person := range persons {
		err := validator.ValidateIt(context.Background(), person)
		if violations, ok := validation.UnwrapViolationList(err); ok {
			fmt.Println("person", i, "is not valid:")
			for violation := violations.First(); violation != nil; violation = violation.Next() {
				fmt.Println(violation)
			}
		}
	}

}
Output:

person 1 is not valid:
violation at "name": "This value should not be blank."
violation at "surname": "This value should not be blank."
violation at "age": "This value should be between 18 and 100."
Example (ViolationWithPayload)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

var ErrModificationProhibited = validation.NewError(
	"modification is prohibited",
	"Modification of resource is prohibited.",
)

type AccessViolation struct {
	validation.Violation
	UserID     int
	Permission string
}

func (err *AccessViolation) Error() string {
	return err.Violation.Error()
}

type Blog struct {
	Name    string
	Entries BlogEntries
}

func (b Blog) Validate(ctx context.Context, validator *validation.Validator, userID int) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", b.Name, it.IsNotBlank(), it.HasMaxLength(50)),
		validation.ValidProperty(
			"entries",
			validation.ValidatableFunc(func(ctx context.Context, validator *validation.Validator) error {
				// passing user id further
				return b.Entries.Validate(ctx, validator, userID)
			}),
		),
	)
}

type BlogEntry struct {
	AuthorID int
	Title    string
	Text     string
}

func (e BlogEntry) Validate(ctx context.Context, validator *validation.Validator, userID int) error {
	// creating violation with domain payload
	if e.AuthorID != userID {
		return &AccessViolation{
			Violation:  validator.CreateViolation(ctx, ErrModificationProhibited, ErrModificationProhibited.Message()),
			UserID:     userID,
			Permission: "edit",
		}
	}

	return validator.Validate(
		ctx,
		validation.StringProperty("title", e.Title, it.IsNotBlank(), it.HasMaxLength(100)),
		validation.StringProperty("text", e.Text, it.IsNotBlank(), it.HasMaxLength(10000)),
	)
}

type BlogEntries []BlogEntry

func (entries BlogEntries) Validate(ctx context.Context, validator *validation.Validator, userID int) error {
	violations := validation.NewViolationList()

	for i, entry := range entries {
		err := violations.AppendFromError(entry.Validate(ctx, validator.AtIndex(i), userID))
		if err != nil {
			return err
		}
	}

	return violations.AsError()
}

func main() {
	blog := Blog{
		Name: "News blog",
		Entries: []BlogEntry{
			{
				AuthorID: 123,
				Title:    "Good weather",
				Text:     "Good weather is coming!",
			},
			{
				AuthorID: 321,
				Title:    "Secret entry",
				Text:     "This should not be edited!",
			},
		},
	}

	userID := 123 // user id from session
	err := validator.ValidateIt(
		context.Background(),
		validation.ValidatableFunc(func(ctx context.Context, validator *validation.Validator) error {
			return blog.Validate(ctx, validator, userID)
		}),
	)

	if violations, ok := validation.UnwrapViolationList(err); ok {
		violations.ForEach(func(i int, violation validation.Violation) error {
			fmt.Println(violation)
			// unwrap concrete violation from chain
			if accessError, ok := violation.(*AccessViolation); ok {
				fmt.Println("user id:", accessError.UserID)
				fmt.Println("permission:", accessError.Permission)
			}
			return nil
		})
	}
}
Output:

violation at "entries[1]": "Modification of resource is prohibited."
user id: 123
permission: edit

func (*Validator) ValidateString

func (validator *Validator) ValidateString(ctx context.Context, value string, constraints ...StringConstraint) error

ValidateString is an alias for validating a single string value.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	err := validator.ValidateString(context.Background(), "", it.IsNotBlank())

	fmt.Println(err)
}
Output:

violation: "This value should not be blank."

func (*Validator) ValidateStrings added in v0.4.0

func (validator *Validator) ValidateStrings(ctx context.Context, values []string, constraints ...ComparablesConstraint[string]) error

ValidateStrings is an alias for validating slice of strings.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []string{"foo", "bar", "baz", "foo"}
	err := validator.ValidateStrings(context.Background(), v, it.HasUniqueValues[string]())
	fmt.Println(err)
}
Output:

violation: "This collection should contain only unique elements."

func (*Validator) ValidateTime

func (validator *Validator) ValidateTime(ctx context.Context, value time.Time, constraints ...TimeConstraint) error

ValidateTime is an alias for validating a single time value.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	t := time.Now()
	compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z")
	err := validator.ValidateTime(context.Background(), t, it.IsEarlierThan(compared))
	fmt.Println(err)
}
Output:

violation: "This value should be earlier than 2006-01-02T15:00:00Z."

func (*Validator) WithGroups added in v0.8.0

func (validator *Validator) WithGroups(groups ...string) *Validator

WithGroups is used to execute conditional validation based on validation groups. It creates a new context validator with a given set of groups.

By default, when validating an object all constraints of it will be checked whether or not they pass. In some cases, however, you will need to validate an object against only some specific group of constraints. To do this, you can organize each constraint into one or more validation groups and then apply validation against one group of constraints.

Validation groups are working together only with validation groups passed to a constraint by WhenGroups() method. This method is implemented in all built-in constraints. If you want to use validation groups for your own constraints do not forget to implement this method in your constraint.

Be careful, empty groups are considered as the default group. Its value is equal to the DefaultGroup ("default").

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type User struct {
	Email    string
	Password string
	City     string
}

func (u User) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty(
			"email",
			u.Email,
			it.IsNotBlank().WhenGroups("registration"),
			it.IsEmail().WhenGroups("registration"),
		),
		validation.StringProperty(
			"password",
			u.Password,
			it.IsNotBlank().WhenGroups("registration"),
			it.HasMinLength(7).WhenGroups("registration"),
		),
		validation.StringProperty(
			"city",
			u.City,
			it.HasMinLength(2), // this constraint belongs to the default group
		),
	)
}

func main() {
	user := User{
		Email:    "invalid email",
		Password: "1234",
		City:     "Z",
	}

	err1 := validator.WithGroups("registration").Validate(context.Background(), validation.Valid(user))
	err2 := validator.Validate(context.Background(), validation.Valid(user))

	if violations, ok := validation.UnwrapViolationList(err1); ok {
		fmt.Println("violations for registration group:")
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
	if violations, ok := validation.UnwrapViolationList(err2); ok {
		fmt.Println("violations for default group:")
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}

}
Output:

violations for registration group:
violation at "email": "This value is not a valid email address."
violation at "password": "This value is too short. It should have 7 characters or more."
violations for default group:
violation at "city": "This value is too short. It should have 2 characters or more."

func (*Validator) WithLanguage

func (validator *Validator) WithLanguage(tag language.Tag) *Validator

WithLanguage method creates a new context validator with a given language tag. All created violations will be translated into this language.

The priority of language selection methods:

  • Validator.WithLanguage has the highest priority and will override any other options;
  • if the validator language is not specified, the validator will try to get the language from the context;
  • in all other cases, the default language specified in the translator will be used.
Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(validation.Translations(russian.Messages))
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	err = validator.WithLanguage(language.Russian).Validate(
		context.Background(),
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

violation: "Значение не должно быть пустым."

type ValidatorArgument added in v0.9.0

type ValidatorArgument struct {
	// contains filtered or unexported fields
}

ValidatorArgument is common implementation of Argument that is used to run validation process on given argument.

func Bool

func Bool(value bool, constraints ...BoolConstraint) ValidatorArgument

Bool argument is used to validate boolean values.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := false
	err := validator.Validate(context.Background(), validation.Bool(v, it.IsTrue()))
	fmt.Println(err)
}
Output:

violation: "This value should be true."

func BoolProperty

func BoolProperty(name string, value bool, constraints ...BoolConstraint) ValidatorArgument

BoolProperty argument is an alias for Bool that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		IsPublished bool
	}{
		IsPublished: false,
	}
	err := validator.Validate(
		context.Background(),
		validation.BoolProperty("isPublished", v.IsPublished, it.IsTrue()),
	)
	fmt.Println(err)
}
Output:

violation at "isPublished": "This value should be true."

func CheckNoViolations added in v0.7.0

func CheckNoViolations(err error) ValidatorArgument

CheckNoViolations is a special argument that checks err for violations. If err contains Violation or ViolationList then these violations will be appended into returned violation list from the validator. If err contains an error that does not implement an error interface, then the validation process will be terminated and this error will be returned.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type Vehicle struct {
	Model    string
	MaxSpeed int
}

func (v Vehicle) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(ctx,
		validation.StringProperty("model", v.Model, it.IsNotBlank(), it.HasMaxLength(100)),
		validation.NumberProperty[int]("maxSpeed", v.MaxSpeed, it.IsBetween(50, 200)),
	)
}

type Car struct {
	Vehicle
	PassengerSeats int
}

func (c Car) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(ctx,
		validation.CheckNoViolations(c.Vehicle.Validate(ctx, validator)),
		validation.NumberProperty[int]("passengerSeats", c.PassengerSeats, it.IsBetween(2, 6)),
	)
}

type Truck struct {
	Vehicle
	LoadCapacity float64
}

func (t Truck) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(ctx,
		validation.CheckNoViolations(t.Vehicle.Validate(ctx, validator)),
		validation.NumberProperty[float64]("loadCapacity", t.LoadCapacity, it.IsBetween(10.0, 200.0)),
	)
}

func main() {
	vehicles := []validation.Validatable{
		Car{
			Vehicle: Vehicle{
				Model:    "Audi",
				MaxSpeed: 10,
			},
			PassengerSeats: 1,
		},
		Truck{
			Vehicle: Vehicle{
				Model:    "Benz",
				MaxSpeed: 20,
			},
			LoadCapacity: 5,
		},
	}

	for i, vehicle := range vehicles {
		err := validator.ValidateIt(context.Background(), vehicle)
		if violations, ok := validation.UnwrapViolationList(err); ok {
			fmt.Println("vehicle", i, "is not valid:")
			for violation := violations.First(); violation != nil; violation = violation.Next() {
				fmt.Println(violation)
			}
		}
	}

}
Output:

vehicle 0 is not valid:
violation at "maxSpeed": "This value should be between 50 and 200."
violation at "passengerSeats": "This value should be between 2 and 6."
vehicle 1 is not valid:
violation at "maxSpeed": "This value should be between 50 and 200."
violation at "loadCapacity": "This value should be between 10 and 200."

func Comparable added in v0.9.0

func Comparable[T comparable](value T, constraints ...ComparableConstraint[T]) ValidatorArgument

Comparable argument is used to validate generic comparable value.

Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 4
	err := validator.Validate(
		context.Background(),
		validation.Comparable[int](v, it.IsOneOf(1, 2, 3, 5)),
	)
	fmt.Println(err)
}
Output:

violation: "The value you selected is not a valid choice."
Example (String)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := "unknown"
	err := validator.Validate(
		context.Background(),
		validation.Comparable[string](v, it.IsOneOf("foo", "bar", "baz")),
	)
	fmt.Println(err)
}
Output:

violation: "The value you selected is not a valid choice."

func ComparableProperty added in v0.9.0

func ComparableProperty[T comparable](name string, value T, constraints ...ComparableConstraint[T]) ValidatorArgument

ComparableProperty argument is an alias for Comparable that automatically adds property name to the current validation context.

Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := struct {
		Metric int
	}{
		Metric: 4,
	}
	err := validator.Validate(
		context.Background(),
		validation.ComparableProperty[int]("metric", s.Metric, it.IsOneOf(1, 2, 3, 5)),
	)
	fmt.Println(err)
}
Output:

violation at "metric": "The value you selected is not a valid choice."
Example (String)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := struct {
		Enum string
	}{
		Enum: "unknown",
	}
	err := validator.Validate(
		context.Background(),
		validation.ComparableProperty[string]("enum", s.Enum, it.IsOneOf("foo", "bar", "baz")),
	)
	fmt.Println(err)
}
Output:

violation at "enum": "The value you selected is not a valid choice."

func Comparables added in v0.9.0

func Comparables[T comparable](values []T, constraints ...ComparablesConstraint[T]) ValidatorArgument

Comparables argument is used to validate generic comparable types.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []string{"foo", "bar", "baz", "foo"}
	err := validator.Validate(
		context.Background(),
		validation.Comparables[string](v, it.HasUniqueValues[string]()),
	)
	fmt.Println(err)
}
Output:

violation: "This collection should contain only unique elements."

func ComparablesProperty added in v0.9.0

func ComparablesProperty[T comparable](name string, values []T, constraints ...ComparablesConstraint[T]) ValidatorArgument

ComparablesProperty argument is an alias for Comparables that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Keywords []string
	}{
		Keywords: []string{"foo", "bar", "baz", "foo"},
	}
	err := validator.Validate(
		context.Background(),
		validation.ComparablesProperty[string]("keywords", v.Keywords, it.HasUniqueValues[string]()),
	)
	fmt.Println(err)
}
Output:

violation at "keywords": "This collection should contain only unique elements."

func Countable

func Countable(count int, constraints ...CountableConstraint) ValidatorArgument

Countable argument can be used to validate size of an array, slice, or map. You can pass result of len() function as an argument.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := []string{"a", "b"}
	err := validator.Validate(
		context.Background(),
		validation.Countable(len(s), it.HasMinCount(3)),
	)
	fmt.Println(err)
}
Output:

violation: "This collection should contain 3 elements or more."

func CountableProperty

func CountableProperty(name string, count int, constraints ...CountableConstraint) ValidatorArgument

CountableProperty argument is an alias for Countable that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Tags []string
	}{
		Tags: []string{"a", "b"},
	}
	err := validator.Validate(
		context.Background(),
		validation.CountableProperty("tags", len(v.Tags), it.HasMinCount(3)),
	)
	fmt.Println(err)
}
Output:

violation at "tags": "This collection should contain 3 elements or more."

func EachComparable added in v0.9.0

func EachComparable[T comparable](values []T, constraints ...ComparableConstraint[T]) ValidatorArgument

EachComparable is used to validate a slice of generic comparables.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []string{"foo", "bar", "baz"}
	err := validator.Validate(
		context.Background(),
		validation.EachComparable[string](v, it.IsOneOf("foo", "bar", "buz")),
	)
	fmt.Println(err)
}
Output:

violation at "[2]": "The value you selected is not a valid choice."

func EachComparableProperty added in v0.9.0

func EachComparableProperty[T comparable](name string, values []T, constraints ...ComparableConstraint[T]) ValidatorArgument

EachComparableProperty argument is an alias for EachComparable that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Labels []string
	}{
		Labels: []string{"foo", "bar", "baz"},
	}
	err := validator.Validate(
		context.Background(),
		validation.EachComparableProperty[string]("labels", v.Labels, it.IsOneOf("foo", "bar", "buz")),
	)
	fmt.Println(err)
}
Output:

violation at "labels[2]": "The value you selected is not a valid choice."

func EachNumber added in v0.9.0

func EachNumber[T Numeric](values []T, constraints ...NumberConstraint[T]) ValidatorArgument

EachNumber is used to validate a slice of numbers.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []int{-1, 0, 1}
	err := validator.Validate(
		context.Background(),
		validation.EachNumber[int](v, it.IsPositiveOrZero[int]()),
	)
	fmt.Println(err)
}
Output:

violation at "[0]": "This value should be either positive or zero."

func EachNumberProperty added in v0.9.0

func EachNumberProperty[T Numeric](name string, values []T, constraints ...NumberConstraint[T]) ValidatorArgument

EachNumberProperty argument is an alias for EachNumber that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Metrics []int
	}{
		Metrics: []int{-1, 0, 1},
	}
	err := validator.Validate(
		context.Background(),
		validation.EachNumberProperty[int]("metrics", v.Metrics, it.IsPositiveOrZero[int]()),
	)
	fmt.Println(err)
}
Output:

violation at "metrics[0]": "This value should be either positive or zero."

func EachString

func EachString(values []string, constraints ...StringConstraint) ValidatorArgument

EachString is used to validate a slice of strings.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := []string{""}
	err := validator.Validate(
		context.Background(),
		validation.EachString(v, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation at "[0]": "This value should not be blank."

func EachStringProperty

func EachStringProperty(name string, values []string, constraints ...StringConstraint) ValidatorArgument

EachStringProperty argument is an alias for EachString that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Tags []string
	}{
		Tags: []string{""},
	}
	err := validator.Validate(
		context.Background(),
		validation.EachStringProperty("tags", v.Tags, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation at "tags[0]": "This value should not be blank."

func NewArgument

func NewArgument(validate ValidateFunc) ValidatorArgument

NewArgument can be used to implement validation functional arguments for the specific types.

func Nil added in v0.9.0

func Nil(isNil bool, constraints ...NilConstraint) ValidatorArgument

Nil argument is used to validate nil values of any nillable types.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	var v []string
	err := validator.Validate(context.Background(), validation.Nil(v == nil, it.IsNotNil()))
	fmt.Println(err)
}
Output:

violation: "This value should not be nil."

func NilBool added in v0.5.0

func NilBool(value *bool, constraints ...BoolConstraint) ValidatorArgument

NilBool argument is used to validate nillable boolean values.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := false
	err := validator.Validate(context.Background(), validation.NilBool(&v, it.IsTrue()))
	fmt.Println(err)
}
Output:

violation: "This value should be true."

func NilBoolProperty added in v0.5.0

func NilBoolProperty(name string, value *bool, constraints ...BoolConstraint) ValidatorArgument

NilBoolProperty argument is an alias for NilBool that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		IsPublished bool
	}{
		IsPublished: false,
	}
	err := validator.Validate(
		context.Background(),
		validation.NilBoolProperty("isPublished", &v.IsPublished, it.IsTrue()),
	)
	fmt.Println(err)
}
Output:

violation at "isPublished": "This value should be true."

func NilComparable added in v0.9.0

func NilComparable[T comparable](value *T, constraints ...ComparableConstraint[T]) ValidatorArgument

NilComparable argument is used to validate nillable generic comparable value.

Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 4
	err := validator.Validate(
		context.Background(),
		validation.NilComparable[int](&v, it.IsOneOf(1, 2, 3, 5)),
	)
	fmt.Println(err)
}
Output:

violation: "The value you selected is not a valid choice."
Example (String)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := "unknown"
	err := validator.Validate(
		context.Background(),
		validation.NilComparable[string](&v, it.IsOneOf("foo", "bar", "baz")),
	)
	fmt.Println(err)
}
Output:

violation: "The value you selected is not a valid choice."

func NilComparableProperty added in v0.9.0

func NilComparableProperty[T comparable](name string, value *T, constraints ...ComparableConstraint[T]) ValidatorArgument

NilComparableProperty argument is an alias for NilComparable that automatically adds property name to the current validation context.

Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := struct {
		Metric int
	}{
		Metric: 4,
	}
	err := validator.Validate(
		context.Background(),
		validation.NilComparableProperty[int]("metric", &s.Metric, it.IsOneOf(1, 2, 3, 5)),
	)
	fmt.Println(err)
}
Output:

violation at "metric": "The value you selected is not a valid choice."
Example (String)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	s := struct {
		Enum string
	}{
		Enum: "unknown",
	}
	err := validator.Validate(
		context.Background(),
		validation.NilComparableProperty[string]("enum", &s.Enum, it.IsOneOf("foo", "bar", "baz")),
	)
	fmt.Println(err)
}
Output:

violation at "enum": "The value you selected is not a valid choice."

func NilNumber added in v0.9.0

func NilNumber[T Numeric](value *T, constraints ...NumberConstraint[T]) ValidatorArgument

NilNumber argument is used to validate nillable numbers.

Example (Float)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5.5
	err := validator.Validate(
		context.Background(),
		validation.NilNumber[float64](&v, it.IsGreaterThan(6.5)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 6.5."
Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5
	err := validator.Validate(
		context.Background(),
		validation.NilNumber[int](&v, it.IsGreaterThan(5)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 5."

func NilNumberProperty added in v0.9.0

func NilNumberProperty[T Numeric](name string, value *T, constraints ...NumberConstraint[T]) ValidatorArgument

NilNumberProperty argument is an alias for NilNumber that automatically adds property name to the current validation context.

Example (Float)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Amount float64
	}{
		Amount: 5.5,
	}
	err := validator.Validate(
		context.Background(),
		validation.NilNumberProperty[float64]("amount", &v.Amount, it.IsGreaterThan(6.5)),
	)
	fmt.Println(err)
}
Output:

violation at "amount": "This value should be greater than 6.5."
Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Count int
	}{
		Count: 5,
	}
	err := validator.Validate(
		context.Background(),
		validation.NilNumberProperty[int]("count", &v.Count, it.IsGreaterThan(5)),
	)
	fmt.Println(err)
}
Output:

violation at "count": "This value should be greater than 5."

func NilProperty added in v0.9.0

func NilProperty(name string, isNil bool, constraints ...NilConstraint) ValidatorArgument

NilProperty argument is an alias for Nil that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Tags []string
	}{}
	err := validator.Validate(context.Background(), validation.NilProperty("tags", v.Tags == nil, it.IsNotNil()))
	fmt.Println(err)
}
Output:

violation at "tags": "This value should not be nil."

func NilString added in v0.5.0

func NilString(value *string, constraints ...StringConstraint) ValidatorArgument

NilString argument is used to validate nillable strings.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := ""
	err := validator.Validate(
		context.Background(),
		validation.NilString(&v, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation: "This value should not be blank."

func NilStringProperty added in v0.5.0

func NilStringProperty(name string, value *string, constraints ...StringConstraint) ValidatorArgument

NilStringProperty argument is an alias for NilString that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Title string
	}{
		Title: "",
	}
	err := validator.Validate(
		context.Background(),
		validation.NilStringProperty("title", &v.Title, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation at "title": "This value should not be blank."

func NilTime added in v0.5.0

func NilTime(value *time.Time, constraints ...TimeConstraint) ValidatorArgument

NilTime argument is used to validate nillable time.Time value.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	t := time.Now()
	compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z")
	err := validator.Validate(
		context.Background(),
		validation.NilTime(&t, it.IsEarlierThan(compared)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be earlier than 2006-01-02T15:00:00Z."

func NilTimeProperty added in v0.5.0

func NilTimeProperty(name string, value *time.Time, constraints ...TimeConstraint) ValidatorArgument

NilTimeProperty argument is an alias for NilTime that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		CreatedAt time.Time
	}{
		CreatedAt: time.Now(),
	}
	compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z")
	err := validator.Validate(
		context.Background(),
		validation.NilTimeProperty("createdAt", &v.CreatedAt, it.IsEarlierThan(compared)),
	)
	fmt.Println(err)
}
Output:

violation at "createdAt": "This value should be earlier than 2006-01-02T15:00:00Z."

func Number

func Number[T Numeric](value T, constraints ...NumberConstraint[T]) ValidatorArgument

Number argument is used to validate numbers.

Example (Float)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5.5
	err := validator.Validate(
		context.Background(),
		validation.Number[float64](v, it.IsGreaterThan(6.5)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 6.5."
Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := 5
	err := validator.Validate(
		context.Background(),
		validation.Number[int](v, it.IsGreaterThan(5)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be greater than 5."

func NumberProperty

func NumberProperty[T Numeric](name string, value T, constraints ...NumberConstraint[T]) ValidatorArgument

NumberProperty argument is an alias for Number that automatically adds property name to the current validation context.

Example (Float)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Amount float64
	}{
		Amount: 5.5,
	}
	err := validator.Validate(
		context.Background(),
		validation.NumberProperty[float64]("amount", v.Amount, it.IsGreaterThan(6.5)),
	)
	fmt.Println(err)
}
Output:

violation at "amount": "This value should be greater than 6.5."
Example (Int)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Count int
	}{
		Count: 5,
	}
	err := validator.Validate(
		context.Background(),
		validation.NumberProperty[int]("count", v.Count, it.IsGreaterThan(5)),
	)
	fmt.Println(err)
}
Output:

violation at "count": "This value should be greater than 5."

func String

func String(value string, constraints ...StringConstraint) ValidatorArgument

String argument is used to validate strings.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := ""
	err := validator.Validate(
		context.Background(),
		validation.String(v, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation: "This value should not be blank."

func StringProperty

func StringProperty(name string, value string, constraints ...StringConstraint) ValidatorArgument

StringProperty argument is an alias for String that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		Title string
	}{
		Title: "",
	}
	err := validator.Validate(
		context.Background(),
		validation.StringProperty("title", v.Title, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

violation at "title": "This value should not be blank."

func This added in v0.13.0

func This[T any](v T, constraints ...Constraint[T]) ValidatorArgument

This creates a generic validation argument that can help implement the validation argument for client-side types.

Example (CustomArgumentConstraintValidator)
package main

import (
	"context"
	"errors"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

type Brand struct {
	Name string
}

type BrandRepository struct {
	brands []Brand
}

func (repository *BrandRepository) FindByName(ctx context.Context, name string) ([]Brand, error) {
	found := make([]Brand, 0)

	for _, brand := range repository.brands {
		if brand.Name == name {
			found = append(found, brand)
		}
	}

	return found, nil
}

// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.This constructor.
func ValidBrand(brand *Brand, constraints ...validation.Constraint[*Brand]) validation.ValidatorArgument {
	return validation.This[*Brand](brand, constraints...)
}

var ErrNotUniqueBrand = errors.New("not unique brand")

// UniqueBrandConstraint implements BrandConstraint.
type UniqueBrandConstraint struct {
	brands *BrandRepository
}

func (c *UniqueBrandConstraint) Validate(ctx context.Context, validator *validation.Validator, brand *Brand) error {
	// usually, you should ignore empty values
	// to check for an empty value you should use it.NotBlankConstraint
	if brand == nil {
		return nil
	}

	brands, err := c.brands.FindByName(ctx, brand.Name)
	// here you can return a service error so that the validation process
	// is stopped immediately
	if err != nil {
		return err
	}
	if len(brands) == 0 {
		return nil
	}

	// use the validator to build violation with translations
	return validator.
		BuildViolation(ctx, ErrNotUniqueBrand, `Brand with name "{{ name }}" already exists.`).
		// you can inject parameter value to the message here
		WithParameter("{{ name }}", brand.Name).
		Create()
}

func main() {
	repository := &BrandRepository{brands: []Brand{{"Apple"}, {"Orange"}}}
	isUnique := &UniqueBrandConstraint{brands: repository}

	brand := Brand{Name: "Apple"}

	err := validator.Validate(
		// you can pass here the context value to the validation context
		context.WithValue(context.Background(), "key", "value"),
		ValidBrand(&brand, isUnique),
		// it is full equivalent of
		// validation.This[*Brand](&brand, isUnique),
	)

	fmt.Println(err)
	fmt.Println("errors.Is(err, ErrNotUniqueBrand) =", errors.Is(err, ErrNotUniqueBrand))
}
Output:

violation: "Brand with name "Apple" already exists."
errors.Is(err, ErrNotUniqueBrand) = true

func Time

func Time(value time.Time, constraints ...TimeConstraint) ValidatorArgument

Time argument is used to validate time.Time value.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	t := time.Now()
	compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z")
	err := validator.Validate(
		context.Background(),
		validation.Time(t, it.IsEarlierThan(compared)),
	)
	fmt.Println(err)
}
Output:

violation: "This value should be earlier than 2006-01-02T15:00:00Z."

func TimeProperty

func TimeProperty(name string, value time.Time, constraints ...TimeConstraint) ValidatorArgument

TimeProperty argument is an alias for Time that automatically adds property name to the current validation context.

Example
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := struct {
		CreatedAt time.Time
	}{
		CreatedAt: time.Now(),
	}
	compared, _ := time.Parse(time.RFC3339, "2006-01-02T15:00:00Z")
	err := validator.Validate(
		context.Background(),
		validation.TimeProperty("createdAt", v.CreatedAt, it.IsEarlierThan(compared)),
	)
	fmt.Println(err)
}
Output:

violation at "createdAt": "This value should be earlier than 2006-01-02T15:00:00Z."

func Valid

func Valid(value Validatable) ValidatorArgument

Valid is used to run validation on the Validatable type. This method is recommended to build a complex validation process.

Example (ValidatableSlice)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type Company struct {
	Name    string
	Address string
}

type Companies []Company

func (companies Companies) Validate(ctx context.Context, validator *validation.Validator) error {
	violations := validation.ViolationList{}

	for i, company := range companies {
		err := validator.AtIndex(i).Validate(
			ctx,
			validation.StringProperty("name", company.Name, it.IsNotBlank()),
			validation.StringProperty("address", company.Address, it.IsNotBlank(), it.HasMinLength(3)),
		)
		// appending violations from err
		err = violations.AppendFromError(err)
		// if append returns a non-nil error we should stop validation because an internal error occurred
		if err != nil {
			return err
		}
	}

	// we should always convert ViolationList into error by calling the AsError method
	// otherwise empty violations list will be interpreted as an error
	return violations.AsError()
}

func main() {
	companies := Companies{
		{"MuonSoft", "London"},
		{"", "x"},
	}

	err := validator.Validate(context.Background(), validation.Valid(companies))

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
}
Output:

violation at "[1].name": "This value should not be blank."
violation at "[1].address": "This value is too short. It should have 3 characters or more."
Example (ValidatableStruct)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

type Product struct {
	Name       string
	Tags       []string
	Components []Component
}

func (p Product) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", p.Name, it.IsNotBlank()),
		validation.AtProperty(
			"tags",
			validation.Countable(len(p.Tags), it.HasMinCount(5)),
			validation.Comparables[string](p.Tags, it.HasUniqueValues[string]()),
			validation.EachString(p.Tags, it.IsNotBlank()),
		),
		validation.AtProperty(
			"components",
			validation.Countable(len(p.Components), it.HasMinCount(1)),
			// this runs validation on each of the components
			validation.ValidSlice(p.Components),
		),
	)
}

type Component struct {
	ID   int
	Name string
	Tags []string
}

func (c Component) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", c.Name, it.IsNotBlank()),
		validation.CountableProperty("tags", len(c.Tags), it.HasMinCount(1)),
	)
}

func main() {
	p := Product{
		Name: "",
		Tags: []string{"device", "", "phone", "device"},
		Components: []Component{
			{
				ID:   1,
				Name: "",
			},
		},
	}

	err := validator.Validate(context.Background(), validation.Valid(p))

	if violations, ok := validation.UnwrapViolationList(err); ok {
		for violation := violations.First(); violation != nil; violation = violation.Next() {
			fmt.Println(violation)
		}
	}
}
Output:

violation at "name": "This value should not be blank."
violation at "tags": "This collection should contain 5 elements or more."
violation at "tags": "This collection should contain only unique elements."
violation at "tags[1]": "This value should not be blank."
violation at "components[0].name": "This value should not be blank."
violation at "components[0].tags": "This collection should contain 1 element or more."

func ValidMap added in v0.9.0

func ValidMap[T Validatable](values map[string]T) ValidatorArgument

ValidMap is a generic argument used to run validation on the map of Validatable types. This method is recommended to build a complex validation process.

func ValidMapProperty added in v0.9.0

func ValidMapProperty[T Validatable](name string, values map[string]T) ValidatorArgument

ValidMapProperty argument is an alias for ValidSlice that automatically adds property name to the current validation context.

func ValidProperty

func ValidProperty(name string, value Validatable) ValidatorArgument

ValidProperty argument is an alias for Valid that automatically adds property name to the current validation context.

func ValidSlice added in v0.9.0

func ValidSlice[T Validatable](values []T) ValidatorArgument

ValidSlice is a generic argument used to run validation on the slice of Validatable types. This method is recommended to build a complex validation process.

func ValidSliceProperty added in v0.9.0

func ValidSliceProperty[T Validatable](name string, values []T) ValidatorArgument

ValidSliceProperty argument is an alias for ValidSlice that automatically adds property name to the current validation context.

func (ValidatorArgument) At added in v0.13.0

At returns a copy of ValidatorArgument with appended property path suffix.

func (ValidatorArgument) When added in v0.9.0

func (arg ValidatorArgument) When(condition bool) ValidatorArgument

When enables conditional validation of this argument. If the expression evaluates to false, then the argument will be ignored.

type ValidatorOption

type ValidatorOption func(options *ValidatorOptions) error

ValidatorOption is a base type for configuration options used to create a new instance of Validator.

func DefaultLanguage

func DefaultLanguage(tag language.Tag) ValidatorOption

DefaultLanguage option is used to set up the default language for translation of violation messages.

func SetTranslator added in v0.6.0

func SetTranslator(translator Translator) ValidatorOption

SetTranslator option is used to set up the custom implementation of message violation translator.

func SetViolationFactory

func SetViolationFactory(factory ViolationFactory) ValidatorOption

SetViolationFactory option can be used to override the mechanism of violation creation.

Example
package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"strings"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations"
	"golang.org/x/text/language"
)

// DomainError is the container that will pass the ID to DomainViolation.
type DomainError struct {
	ID      string // this ID will be passed into DomainViolation by DomainViolationFactory
	Message string
}

func (err *DomainError) Error() string {
	return err.Message
}

var ErrIsEmpty = &DomainError{ID: "IsEmpty", Message: "Value is empty."}

// DomainViolation is custom implementation of validation.Violation interface with domain
// data and custom marshaling to JSON.
type DomainViolation struct {
	id string // id passed from DomainError

	// required fields for implementing validation.Violation
	err             error
	message         string
	messageTemplate string
	parameters      []validation.TemplateParameter
	propertyPath    *validation.PropertyPath
}

func (v *DomainViolation) Unwrap() error                              { return v.err }
func (v *DomainViolation) Is(target error) bool                       { return errors.Is(v.err, target) }
func (v *DomainViolation) Error() string                              { return v.err.Error() }
func (v *DomainViolation) Message() string                            { return v.message }
func (v *DomainViolation) MessageTemplate() string                    { return v.messageTemplate }
func (v *DomainViolation) Parameters() []validation.TemplateParameter { return v.parameters }
func (v *DomainViolation) PropertyPath() *validation.PropertyPath     { return v.propertyPath }

// pathAsJSONPointer formats property path according to a JSON Pointer Syntax https://tools.ietf.org/html/rfc6901
func (v *DomainViolation) pathAsJSONPointer() string {
	var s strings.Builder
	for _, element := range v.propertyPath.Elements() {
		s.WriteRune('/')
		s.WriteString(element.String())
	}
	return s.String()
}

// MarshalJSON marshals violation data with id, message and path fields. Path is formatted
// according to JSON Pointer Syntax.
func (v *DomainViolation) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		ID           string `json:"id"`
		Message      string `json:"message"`
		PropertyPath string `json:"path"`
	}{
		ID:           v.id,
		Message:      v.message,
		PropertyPath: v.pathAsJSONPointer(),
	})
}

// DomainViolationFactory is custom implementation for validation.ViolationFactory.
type DomainViolationFactory struct {
	// reuse translations and templating from BuiltinViolationFactory
	factory *validation.BuiltinViolationFactory
}

func NewDomainViolationFactory() (*DomainViolationFactory, error) {
	translator, err := translations.NewTranslator()
	if err != nil {
		return nil, err
	}

	return &DomainViolationFactory{factory: validation.NewViolationFactory(translator)}, nil
}

func (factory *DomainViolationFactory) CreateViolation(err error, messageTemplate string, pluralCount int, parameters []validation.TemplateParameter, propertyPath *validation.PropertyPath, lang language.Tag) validation.Violation {
	// extracting error ID from err if it implements DomainError
	id := ""
	var domainErr *DomainError
	if errors.As(err, &domainErr) {
		id = domainErr.ID
	}

	violation := factory.factory.CreateViolation(err, messageTemplate, pluralCount, parameters, propertyPath, lang)

	return &DomainViolation{
		id:              id,
		err:             err,
		message:         violation.Message(),
		messageTemplate: violation.MessageTemplate(),
		parameters:      violation.Parameters(),
		propertyPath:    violation.PropertyPath(),
	}
}

func main() {
	violationFactory, err := NewDomainViolationFactory()
	if err != nil {
		log.Fatalln(err)
	}
	validator, err := validation.NewValidator(validation.SetViolationFactory(violationFactory))
	if err != nil {
		log.Fatalln(err)
	}

	err = validator.At(
		// property path will be formatted according to JSON Pointer Syntax
		validation.PropertyName("properties"),
		validation.ArrayIndex(1),
		validation.PropertyName("key"),
	).ValidateString(
		context.Background(),
		"",
		// passing DomainError implementation via a constraint method
		it.IsNotBlank().WithError(ErrIsEmpty).WithMessage(ErrIsEmpty.Message),
	)

	marshaled, err := json.MarshalIndent(err, "", "\t")
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(string(marshaled))
}
Output:

[
	{
		"id": "IsEmpty",
		"message": "Value is empty.",
		"path": "/properties/1/key"
	}
]

func Translations

func Translations(messages map[language.Tag]map[string]catalog.Message) ValidatorOption

Translations option is used to load translation messages into the validator.

By default, all violation messages are generated in the English language with pluralization capabilities. To use a custom language you have to load translations on validator initialization. Built-in translations are available in the sub-packages of the package github.com/muonsoft/message/translations. The translation mechanism is provided by the golang.org/x/text package (be aware, it has no stable version yet).

Example
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/language"
	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/message/translations/russian"
)

func main() {
	validator, err := validation.NewValidator(
		validation.Translations(russian.Messages),
	)
	if err != nil {
		log.Fatal(err)
	}

	s := ""
	ctx := language.WithContext(context.Background(), language.Russian)
	err = validator.Validate(
		ctx,
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

violation: "Значение не должно быть пустым."

type ValidatorOptions added in v0.6.0

type ValidatorOptions struct {
	// contains filtered or unexported fields
}

ValidatorOptions is a temporary structure for collecting functional options ValidatorOption.

type Violation

type Violation interface {
	error
	// Unwrap returns underlying static error. This error can be used as a unique, short, and semantic code
	// that can be used to test for specific violation by [errors.Is] from standard library.
	Unwrap() error

	// Is can be used to check that the violation contains one of the specific static errors.
	Is(target error) bool

	// Message is a translated message with injected values from constraint. It can be used to show
	// a description of a violation to the end-user. Possible values for build-in constraints
	// are defined in the [github.com/muonsoft/validation/message] package and can be changed at any time,
	// even in patch versions.
	Message() string

	// MessageTemplate is a template for rendering message. Alongside parameters it can be used to
	// render the message on the client-side of the library.
	MessageTemplate() string

	// Parameters is the map of the template variables and their values provided by the specific constraint.
	Parameters() []TemplateParameter

	// PropertyPath is a path that points to the violated property.
	// See [PropertyPath] type description for more info.
	PropertyPath() *PropertyPath
}

Violation is the abstraction for validator errors. You can use your own implementations on the application side to use it for your needs. In order for the validator to generate application violations, it is necessary to implement the ViolationFactory interface and inject it into the validator. You can do this by using the SetViolationFactory option in the NewValidator constructor.

func UnwrapViolation

func UnwrapViolation(err error) (Violation, bool)

UnwrapViolation is a short function to unwrap Violation from the error.

type ViolationBuilder

type ViolationBuilder struct {
	// contains filtered or unexported fields
}

ViolationBuilder used to build an instance of a Violation.

func NewViolationBuilder

func NewViolationBuilder(factory ViolationFactory) *ViolationBuilder

NewViolationBuilder creates a new ViolationBuilder.

func (*ViolationBuilder) At added in v0.11.0

At appends a property path of violated attribute.

func (*ViolationBuilder) AtIndex added in v0.11.0

func (b *ViolationBuilder) AtIndex(index int) *ViolationBuilder

AtIndex adds an array index to property path of violated attribute.

func (*ViolationBuilder) AtProperty added in v0.11.0

func (b *ViolationBuilder) AtProperty(propertyName string) *ViolationBuilder

AtProperty adds a property name to property path of violated attribute.

func (*ViolationBuilder) BuildViolation

func (b *ViolationBuilder) BuildViolation(err error, message string) *ViolationBuilder

BuildViolation creates a new ViolationBuilder for composing Violation object fluently.

func (*ViolationBuilder) Create added in v0.11.0

func (b *ViolationBuilder) Create() Violation

Create creates a new violation with given parameters and returns it. Violation is created by calling the [ViolationFactory.CreateViolation].

func (*ViolationBuilder) SetPropertyPath

func (b *ViolationBuilder) SetPropertyPath(path *PropertyPath) *ViolationBuilder

SetPropertyPath resets a base property path of violated attributes.

func (*ViolationBuilder) WithLanguage added in v0.11.0

func (b *ViolationBuilder) WithLanguage(tag language.Tag) *ViolationBuilder

WithLanguage sets language that will be used to translate the violation message.

func (*ViolationBuilder) WithParameter added in v0.11.0

func (b *ViolationBuilder) WithParameter(name, value string) *ViolationBuilder

WithParameter adds one parameter into a slice of parameters.

func (*ViolationBuilder) WithParameters added in v0.11.0

func (b *ViolationBuilder) WithParameters(parameters ...TemplateParameter) *ViolationBuilder

WithParameters sets template parameters that can be injected into the violation message.

func (*ViolationBuilder) WithPluralCount added in v0.11.0

func (b *ViolationBuilder) WithPluralCount(pluralCount int) *ViolationBuilder

WithPluralCount sets a plural number that will be used for message pluralization during translations.

type ViolationFactory

type ViolationFactory interface {
	// CreateViolation creates a new instance of [Violation].
	CreateViolation(
		err error,
		messageTemplate string,
		pluralCount int,
		parameters []TemplateParameter,
		propertyPath *PropertyPath,
		lang language.Tag,
	) Violation
}

ViolationFactory is the abstraction that can be used to create custom violations on the application side. Use the SetViolationFactory option on the NewValidator constructor to inject your own factory into the validator.

type ViolationList

type ViolationList struct {
	// contains filtered or unexported fields
}

ViolationList is a linked list of violations. It is the usual type of error that is returned from a validator.

func NewViolationList added in v0.3.0

func NewViolationList(violations ...Violation) *ViolationList

NewViolationList creates a new ViolationList, that can be immediately populated with variadic arguments of violations.

func UnwrapViolationList

func UnwrapViolationList(err error) (*ViolationList, bool)

UnwrapViolationList is a short function to unwrap ViolationList from the error.

func (*ViolationList) Append added in v0.3.0

func (list *ViolationList) Append(violations ...Violation)

Append appends violations to the end of the linked list.

func (*ViolationList) AppendFromError

func (list *ViolationList) AppendFromError(err error) error

AppendFromError appends a single violation or a slice of violations into the end of a given slice. If an error does not implement the Violation or ViolationList interface, it will return an error itself. Otherwise nil will be returned.

Example (AddingError)
package main

import (
	"errors"
	"fmt"

	"github.com/muonsoft/validation"
)

func main() {
	violations := validation.NewViolationList()
	err := errors.New("error")

	appendErr := violations.AppendFromError(err)

	fmt.Println("append error:", appendErr)
	fmt.Println("violations length:", violations.Len())
}
Output:

append error: error
violations length: 0
Example (AddingViolation)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

func main() {
	violations := validation.NewViolationList()
	err := validator.BuildViolation(context.Background(), validation.ErrNotValid, "foo").Create()

	appendErr := violations.AppendFromError(err)

	fmt.Println("append error:", appendErr)
	fmt.Println("violations:", violations)
}
Output:

append error: <nil>
violations: violation: "foo"
Example (AddingViolationList)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

func main() {
	violations := validation.NewViolationList()
	err := validation.NewViolationList(
		validator.BuildViolation(context.Background(), validation.ErrNotValid, "foo").Create(),
		validator.BuildViolation(context.Background(), validation.ErrNotValid, "bar").Create(),
	)

	appendErr := violations.AppendFromError(err)

	fmt.Println("append error:", appendErr)
	fmt.Println("violations:", violations)
}
Output:

append error: <nil>
violations: violations: #0: "foo"; #1: "bar"

func (*ViolationList) AsError

func (list *ViolationList) AsError() error

AsError converts the list of violations to an error. This method correctly handles cases where the list of violations is empty. It returns nil on an empty list, indicating that the validation was successful.

func (*ViolationList) AsSlice added in v0.3.0

func (list *ViolationList) AsSlice() []Violation

AsSlice converts underlying linked list into slice of Violation.

func (*ViolationList) Error

func (list *ViolationList) Error() string

Error returns a formatted list of violations as a string.

func (*ViolationList) Filter

func (list *ViolationList) Filter(errs ...error) *ViolationList

Filter returns a new list of violations with violations of given codes.

func (*ViolationList) First added in v0.3.0

func (list *ViolationList) First() *ViolationListElement

First returns the first element of the linked list.

Example
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/validator"
)

func main() {
	violations := validation.NewViolationList(
		validator.BuildViolation(context.Background(), validation.ErrNotValid, "foo").Create(),
		validator.BuildViolation(context.Background(), validation.ErrNotValid, "bar").Create(),
	)

	for violation := violations.First(); violation != nil; violation = violation.Next() {
		fmt.Println(violation)
	}
}
Output:

violation: "foo"
violation: "bar"

func (*ViolationList) ForEach added in v0.13.0

func (list *ViolationList) ForEach(f func(i int, violation Violation) error) error

ForEach can be used to iterate over ViolationList by a callback function. If callback returns any error, then it will be returned as a result of ForEach function.

func (*ViolationList) Format added in v0.13.1

func (list *ViolationList) Format(f fmt.State, verb rune)

Format formats the list of violations according to the fmt.Formatter interface. Verbs '%v', '%s', '%q' formats violation list into a single line string delimited by space. Verb with flag '%+v' formats violation list into a multi-line string.

func (*ViolationList) Is added in v0.13.0

func (list *ViolationList) Is(target error) bool

Is used to check that at least one of the violations contains the specific static error.

func (*ViolationList) Join added in v0.3.0

func (list *ViolationList) Join(violations *ViolationList)

Join is used to append the given violation list to the end of the current list.

func (*ViolationList) Last added in v0.3.0

func (list *ViolationList) Last() *ViolationListElement

Last returns the last element of the linked list.

func (*ViolationList) Len added in v0.3.0

func (list *ViolationList) Len() int

Len returns length of the linked list.

func (*ViolationList) MarshalJSON added in v0.3.0

func (list *ViolationList) MarshalJSON() ([]byte, error)

MarshalJSON marshals the linked list into JSON. Usually, you should use json.Marshal function for marshaling purposes.

func (*ViolationList) String added in v0.13.1

func (list *ViolationList) String() string

String converts list of violations into a string.

type ViolationListBuilder added in v0.11.0

type ViolationListBuilder struct {
	// contains filtered or unexported fields
}

ViolationListBuilder is used to build a ViolationList by fluent interface.

func NewViolationListBuilder added in v0.11.0

func NewViolationListBuilder(factory ViolationFactory) *ViolationListBuilder

NewViolationListBuilder creates a new ViolationListBuilder.

func (*ViolationListBuilder) AddViolation added in v0.11.0

func (b *ViolationListBuilder) AddViolation(err error, message string, path ...PropertyPathElement) *ViolationListBuilder

AddViolation can be used to quickly add a new violation using only code, message and optional property path elements.

func (*ViolationListBuilder) At added in v0.11.0

At appends a property path of violated attribute.

func (*ViolationListBuilder) AtIndex added in v0.11.0

func (b *ViolationListBuilder) AtIndex(index int) *ViolationListBuilder

AtIndex adds an array index to the base property path of violated attributes.

func (*ViolationListBuilder) AtProperty added in v0.11.0

func (b *ViolationListBuilder) AtProperty(propertyName string) *ViolationListBuilder

AtProperty adds a property name to the base property path of violated attributes.

func (*ViolationListBuilder) BuildViolation added in v0.11.0

func (b *ViolationListBuilder) BuildViolation(err error, message string) *ViolationListElementBuilder

BuildViolation initiates a builder for violation that will be added into ViolationList.

func (*ViolationListBuilder) Create added in v0.11.0

func (b *ViolationListBuilder) Create() *ViolationList

Create returns a ViolationList with built violations.

func (*ViolationListBuilder) SetPropertyPath added in v0.11.0

func (b *ViolationListBuilder) SetPropertyPath(path *PropertyPath) *ViolationListBuilder

SetPropertyPath resets a base property path of violated attributes.

func (*ViolationListBuilder) WithLanguage added in v0.11.0

func (b *ViolationListBuilder) WithLanguage(tag language.Tag) *ViolationListBuilder

WithLanguage sets language that will be used to translate the violation message.

type ViolationListElement added in v0.3.0

type ViolationListElement struct {
	// contains filtered or unexported fields
}

ViolationListElement points to violation build by validator. It also implements Violation and can be used as a proxy to underlying violation.

func (*ViolationListElement) Error added in v0.3.0

func (element *ViolationListElement) Error() string

func (*ViolationListElement) Is added in v0.3.0

func (element *ViolationListElement) Is(target error) bool

Is can be used to check that the violation contains one of the specific static errors.

func (*ViolationListElement) Message added in v0.3.0

func (element *ViolationListElement) Message() string

func (*ViolationListElement) MessageTemplate added in v0.3.0

func (element *ViolationListElement) MessageTemplate() string

func (*ViolationListElement) Next added in v0.3.0

func (element *ViolationListElement) Next() *ViolationListElement

Next returns the next element of the linked list.

func (*ViolationListElement) Parameters added in v0.3.0

func (element *ViolationListElement) Parameters() []TemplateParameter

func (*ViolationListElement) PropertyPath added in v0.3.0

func (element *ViolationListElement) PropertyPath() *PropertyPath

func (*ViolationListElement) Unwrap added in v0.13.0

func (element *ViolationListElement) Unwrap() error

Unwrap returns underlying static error. This error can be used as a unique, short, and semantic code that can be used to test for specific violation by errors.Is from standard library.

func (*ViolationListElement) Violation added in v0.3.0

func (element *ViolationListElement) Violation() Violation

Violation returns underlying violation value.

type ViolationListElementBuilder added in v0.11.0

type ViolationListElementBuilder struct {
	// contains filtered or unexported fields
}

ViolationListElementBuilder is used to build Violation that will be added into ViolationList of the ViolationListBuilder.

func (*ViolationListElementBuilder) Add added in v0.11.0

Add creates a Violation and appends it into the end of the ViolationList. It returns a ViolationListBuilder to continue process of creating a ViolationList.

func (*ViolationListElementBuilder) At added in v0.11.0

At appends a property path of violated attribute.

func (*ViolationListElementBuilder) AtIndex added in v0.11.0

AtIndex adds an array index to property path of violated attribute.

func (*ViolationListElementBuilder) AtProperty added in v0.11.0

func (b *ViolationListElementBuilder) AtProperty(propertyName string) *ViolationListElementBuilder

AtProperty adds a property name to property path of violated attribute.

func (*ViolationListElementBuilder) WithParameter added in v0.11.0

func (b *ViolationListElementBuilder) WithParameter(name, value string) *ViolationListElementBuilder

WithParameter adds one parameter into a slice of parameters.

func (*ViolationListElementBuilder) WithParameters added in v0.11.0

WithParameters sets template parameters that can be injected into the violation message.

func (*ViolationListElementBuilder) WithPluralCount added in v0.11.0

func (b *ViolationListElementBuilder) WithPluralCount(pluralCount int) *ViolationListElementBuilder

WithPluralCount sets a plural number that will be used for message pluralization during translations.

type WhenArgument added in v0.9.0

type WhenArgument struct {
	// contains filtered or unexported fields
}

WhenArgument is used to build conditional validation. Use the When function to initiate a conditional check. If the condition is true, then the arguments passed through the WhenArgument.Then function will be processed. Otherwise, the arguments passed through the WhenArgument.Else function will be processed.

func When added in v0.2.0

func When(isTrue bool) WhenArgument

When function is used to initiate conditional validation. If the condition is true, then the arguments passed through the WhenArgument.Then function will be processed. Otherwise, the arguments passed through the WhenArgument.Else function will be processed.

Example
package main

import (
	"context"
	"fmt"
	"regexp"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	visaRegex := regexp.MustCompile("^4[0-9]{12}(?:[0-9]{3})?$")
	masterCardRegex := regexp.MustCompile("^(5[1-5][0-9]{14}|2(22[1-9][0-9]{12}|2[3-9][0-9]{13}|[3-6][0-9]{14}|7[0-1][0-9]{13}|720[0-9]{12}))$")

	payment := struct {
		CardType   string
		CardNumber string
		Amount     int
	}{
		CardType:   "Visa",
		CardNumber: "4111",
		Amount:     1000,
	}

	err := validator.Validate(
		context.Background(),
		validation.ComparableProperty[string](
			"cardType",
			payment.CardType,
			it.IsOneOf("Visa", "MasterCard"),
		),
		validation.When(payment.CardType == "Visa").
			At(validation.PropertyName("cardNumber")).
			Then(validation.String(payment.CardNumber, it.Matches(visaRegex))).
			Else(validation.String(payment.CardNumber, it.Matches(masterCardRegex))),
	)

	fmt.Println(err)
}
Output:

violation at "cardNumber": "This value is not valid."

func (WhenArgument) At added in v0.13.0

At returns a copy of WhenArgument with appended property path suffix.

func (WhenArgument) Else added in v0.9.0

func (arg WhenArgument) Else(arguments ...Argument) WhenArgument

Else function is used to set a sequence of arguments to be processed if a condition is false.

Example
package main

import (
	"context"
	"fmt"
	"regexp"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := "123"
	err := validator.Validate(
		context.Background(),
		validation.When(true).
			Then(validation.String(v, it.Matches(regexp.MustCompile(`^\w+$`)))).
			Else(validation.String(v, it.Matches(regexp.MustCompile(`^\d+$`)))),
	)
	fmt.Println(err)
}
Output:

<nil>

func (WhenArgument) Then added in v0.9.0

func (arg WhenArgument) Then(arguments ...Argument) WhenArgument

Then function is used to set a sequence of arguments to be processed if the condition is true.

Example
package main

import (
	"context"
	"fmt"
	"regexp"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"github.com/muonsoft/validation/validator"
)

func main() {
	v := "foo"
	err := validator.Validate(
		context.Background(),
		validation.When(true).Then(
			validation.String(v, it.Matches(regexp.MustCompile(`^\w+$`))),
		),
	)
	fmt.Println(err)
}
Output:

<nil>

type WhenGroupsArgument added in v0.9.0

type WhenGroupsArgument struct {
	// contains filtered or unexported fields
}

WhenGroupsArgument is used to build conditional validation based on groups. Use the WhenGroups function to initiate a conditional check. If validation group matches to the validator one, then the arguments passed through the WhenGroupsArgument.Then function will be processed. Otherwise, the arguments passed through the WhenGroupsArgument.Else function will be processed.

func WhenGroups added in v0.9.0

func WhenGroups(groups ...string) WhenGroupsArgument

WhenGroups is used to build conditional validation based on groups. If validation group matches to the validator one, then the arguments passed through the WhenGroupsArgument.Then function will be processed. Otherwise, the arguments passed through the WhenGroupsArgument.Else function will be processed.

func (WhenGroupsArgument) At added in v0.13.0

At returns a copy of WhenGroupsArgument with appended property path suffix.

func (WhenGroupsArgument) Else added in v0.9.0

func (arg WhenGroupsArgument) Else(arguments ...Argument) WhenGroupsArgument

Else function is used to set a sequence of arguments to be processed if the validation group is active.

func (WhenGroupsArgument) Then added in v0.9.0

func (arg WhenGroupsArgument) Then(arguments ...Argument) WhenGroupsArgument

Then function is used to set a sequence of arguments to be processed if the validation group is active.

Directories

Path Synopsis
internal
uuid
Package uuid is a port of https://github.com/gofrs/uuid with functions only for parsing UUID from string.
Package uuid is a port of https://github.com/gofrs/uuid with functions only for parsing UUID from string.
Package is contains standalone functions that can be used for custom validation process.
Package is contains standalone functions that can be used for custom validation process.
Package it contains validation constraints that are used to validate specific types of values.
Package it contains validation constraints that are used to validate specific types of values.
Package message contains violation message templates.
Package message contains violation message templates.
translations/english
Package english contains violation message texts translated into English language.
Package english contains violation message texts translated into English language.
translations/russian
Package russian contains violation message texts translated into Russian language.
Package russian contains violation message texts translated into Russian language.
Package test contains tests for validation
Package test contains tests for validation
Package validate contains standalone functions that can be used for custom validation process.
Package validate contains standalone functions that can be used for custom validation process.
Package validationtest contains helper functions for testing purposes.
Package validationtest contains helper functions for testing purposes.
Package validator contains Validator service singleton.
Package validator contains Validator service singleton.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL