validation

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Dec 18, 2021 License: MIT Imports: 16 Imported by: 0

README

Golang validation framework

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

The package provides tools for data validation. It is 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
  • Nice and readable way to describe 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.

First stable version aimed to be released after generics implementation in Golang.

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

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// Output:
// violation: This value should not be blank.

List of common validation arguments

  • validation.Value() - passes any value. It uses reflection to detect the type of the argument and pass it to a specific validation method.
  • validation.Bool() - passes boolean value.
  • validation.NilBool() - passes nillable boolean value.
  • validation.Number() - passes any numeric value. At the moment it uses reflection for executing validation process.
  • validation.String() - passes string value.
  • validation.NilString() - passes nillable string value.
  • validation.Strings() - passes slice of strings value.
  • validation.Iterable() - passes array, slice or a map. At the moment it uses reflection for executing validation process.
  • validation.Countable() - you can pass result of len() to use easy way of iterable validation based only on count of the elements.
  • validation.Time() - passes time.Time value.
  • validation.NilTime() - passes nillable time.Time value.
  • validation.Each() - passes array, slice or a map. Used to validate each value of iterable. It uses reflection.
  • validation.EachString() - passes slice of strings. This is more performant version than Each.
  • validation.Valid() - passes Validatable value to run embedded validation.

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

  • validator.ValidateValue()
  • validator.ValidateBool()
  • validator.ValidateNumber()
  • validator.ValidateString()
  • validator.ValidateStrings()
  • validator.ValidateIterable()
  • validator.ValidateCountable()
  • validator.ValidateTime()
  • validator.ValidateEach()
  • validator.ValidateEachString()
  • validator.ValidateValidatable()

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.SetOptions(
    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 name or an array index via validation.PropertyName() and validation.ArrayIndex() options.

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

violation := err.(validation.ViolationList)[0]
fmt.Println("property path:", violation.GetPropertyPath().Format())
// Output:
// property path: properties[1].tag

Also, you can create scoped validator by using valdiator.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()))

violation := err.(validation.ViolationList)[0]
fmt.Println("property path:", violation.GetPropertyPath().Format())
// 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.PropertyValue()
  • validation.BoolProperty()
  • validation.NilBoolProperty()
  • validation.NumberProperty()
  • validation.StringProperty()
  • validation.NilStringProperty()
  • validation.StringsProperty()
  • validation.IterableProperty()
  • validation.CountableProperty()
  • validation.TimeProperty()
  • validation.NilTimeProperty()
  • validation.EachProperty()
  • validation.EachStringProperty()
  • validation.ValidProperty()
err := validator.Validate(
    context.Background(),
    validation.StringProperty("property", "", it.IsNotBlank()),
)

violation := err.(validation.ViolationList)[0]
fmt.Println("property path:", violation.GetPropertyPath().Format())
// 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.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()),
    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.

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.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)),
        validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()),
        validation.EachStringProperty("tags", p.Tags, it.IsNotBlank()),
        // this also runs validation on each of the components
        validation.IterableProperty("components", p.Components, it.HasMinCount(1)),
    )
}

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.ValidateValidatable(context.Background(), p)
    
    violations := err.(validation.ViolationList)
    for _, violation := range violations {
        fmt.Println(violation.Error())
    }
    // 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)),
)

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// Output:
// violation at 'text': This value should not be blank.
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 */)

var violations ViolationList
if err != nil {
    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.

  • code - unique, short, and semantic violation code that can be used to programmatically test for specific violation. All code values are defined in the github.com/muonsoft/validation/code package and are protected by backward compatibility rules.
  • 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.

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

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// Output:
// violation: Значение не должно быть пустым.

The second way is to use the validation.Language() argument. Be aware that this method works only on a specific scope. Also, you can use the validator.WithLanguage() method to create scoped validator and use it in different places.

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

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

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// 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())

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// Output:
// violation: Значение не должно быть пустым.

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

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

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// 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),
)

violations := err.(validation.ViolationList)
for _, violation := range violations {
    fmt.Println(violation.Error())
}
// 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;
  • StringsConstraint - for validating slice of strings;
  • IterableConstraint - for validating iterable values: arrays, slices, or maps;
  • 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 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 codes. The better practice is to store messages in separate storage with translations and to load them by violation codes. So make sure that violation codes are unique and have only one specific message template. To restore the violations from a storage load a code, property path, template parameters, and find a message template by the violation code. To make a violation code unique it is recommended to use a namespaced value, for example app.product.emptyTags.

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

This section is empty.

Variables

This section is empty.

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 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 scope and to pass arguments for validation values.

func Bool

func Bool(value bool, options ...Option) Argument

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, options ...Option) Argument

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

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

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.

func Countable

func Countable(count int, options ...Option) Argument

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, options ...Option) Argument

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

Example
v := Product{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 Each

func Each(value interface{}, options ...Option) Argument

Each is used to validate each value of iterable (array, slice, or map). At the moment it uses reflection to iterate over values. So you can expect a performance hit using this method. For better performance it is recommended to make a custom type that implements the Validatable interface. Also, you can use EachString argument to validate slice of strings.

Warning! This argument is subject to change in the final versions of the library.

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.Each(v, it.IsNotBlank()),
	)
	fmt.Println(err)
}
Output:

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

func EachProperty

func EachProperty(name string, value interface{}, options ...Option) Argument

EachProperty argument is an alias for Each that automatically adds property name to the current scope.

Example
v := Product{Tags: []string{""}}
err := validator.Validate(
	context.Background(),
	validation.EachProperty("tags", v.Tags, it.IsNotBlank()),
)
fmt.Println(err)
Output:

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

func EachString

func EachString(values []string, options ...Option) Argument

EachString is used to validate a slice of strings. This is a more performant version of Each argument.

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, options ...Option) Argument

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

Example
v := Product{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 Iterable

func Iterable(value interface{}, options ...Option) Argument

Iterable argument is used to validate arrays, slices, or maps. At the moment it uses reflection to iterate over values. So you can expect a performance hit using this method. For better performance it is recommended to make a custom type that implements the Validatable interface.

Warning! This argument is subject to change in the final versions of the library.

Example
package main

import (
	"context"
	"fmt"

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

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

violation: This value should not be blank.

func IterableProperty

func IterableProperty(name string, value interface{}, options ...Option) Argument

IterableProperty argument is an alias for Iterable that automatically adds property name to the current scope.

Example
v := Product{Tags: []string{}}
err := validator.Validate(
	context.Background(),
	validation.IterableProperty("tags", v.Tags, it.IsNotBlank()),
)
fmt.Println(err)
Output:

violation at 'tags': This value should not be blank.

func Language

func Language(tag language.Tag) Argument

Language argument sets the current language for translation of a violation message.

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.Validate(
		context.Background(),
		validation.Language(language.Russian),
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

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

func NewArgument

func NewArgument(options []Option, validate ValidateByConstraintFunc) Argument

NewArgument can be used to implement your own validation arguments for the specific types. See example for more details.

Example (CustomArgumentConstraintValidator)
package main

import (
	"context"
	"fmt"

	"github.com/muonsoft/validation"
	"github.com/muonsoft/validation/it"
	"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
}

// You can declare you own constraint interface to create custom constraints.
type BrandConstraint interface {
	validation.Constraint
	ValidateBrand(brand *Brand, scope validation.Scope) error
}

// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.NewArgument constructor.
func BrandArgument(brand *Brand, options ...validation.Option) validation.Argument {
	return validation.NewArgument(options, func(constraint validation.Constraint, scope validation.Scope) error {
		if c, ok := constraint.(BrandConstraint); ok {
			return c.ValidateBrand(brand, scope)
		}
		// If you want to use built-in constraints for checking for nil or empty values
		// such as it.IsNil() or it.IsBlank().
		if c, ok := constraint.(validation.NilConstraint); ok {
			if brand == nil {
				return c.ValidateNil(scope)
			}
			return nil
		}

		return validation.NewInapplicableConstraintError(constraint, "Brand")
	})
}

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

func (c *UniqueBrandConstraint) SetUp() error {
	return nil
}

func (c *UniqueBrandConstraint) Name() string {
	return "UniqueBrandConstraint"
}

func (c *UniqueBrandConstraint) ValidateBrand(brand *Brand, scope validation.Scope) error {
	// usually, you should ignore empty values
	// to check for an empty value you should use it.NotBlankConstraint
	if brand == nil {
		return nil
	}

	// you can pass the context value from the scope
	brands, err := c.brands.FindByName(scope.Context(), 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 scope to build violation with translations
	return scope.
		BuildViolation("notUniqueBrand", `Brand with name "{{ name }}" already exists.`).
		// you can inject parameter value to the message here
		AddParameter("{{ name }}", brand.Name).
		CreateViolation()
}

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

	brand := Brand{Name: "Apple"}

	err := validator.Validate(
		// you can pass here the context value to the validation scope
		context.WithValue(context.Background(), exampleKey, "value"),
		BrandArgument(&brand, it.IsNotBlank(), isEntityUnique),
	)

	fmt.Println(err)
}
Output:

violation: Brand with name "Apple" already exists.

func NilBool added in v0.5.0

func NilBool(value *bool, options ...Option) Argument

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, options ...Option) Argument

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

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 NilString added in v0.5.0

func NilString(value *string, options ...Option) Argument

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, options ...Option) Argument

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

Example
v := Book{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, options ...Option) Argument

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, options ...Option) Argument

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

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(value interface{}, options ...Option) Argument

Number argument is used to validate numbers (any types of integers or floats). At the moment it uses reflection to detect numeric value. Given value is internally converted into int64 or float64 to make comparisons.

Warning! This method will be changed after generics implementation in Go.

Example
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(&v, it.IsGreaterThanInteger(5)),
	)
	fmt.Println(err)
}
Output:

violation: This value should be greater than 5.

func NumberProperty

func NumberProperty(name string, value interface{}, options ...Option) Argument

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

Example
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("count", &v.Count, it.IsGreaterThanInteger(5)),
	)
	fmt.Println(err)
}
Output:

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

func PropertyValue

func PropertyValue(name string, value interface{}, options ...Option) Argument

PropertyValue argument is an alias for Value that automatically adds property name to the current scope.

Example
v := Book{Title: ""}
err := validator.Validate(
	context.Background(),
	validation.PropertyValue("title", v.Title, it.IsNotBlank()),
)
fmt.Println(err)
Output:

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

func String

func String(value string, options ...Option) Argument

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, options ...Option) Argument

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

Example
v := Book{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 Strings added in v0.4.0

func Strings(values []string, options ...Option) Argument

Strings argument is used to validate 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{"foo", "bar", "baz", "foo"}
	err := validator.Validate(
		context.Background(),
		validation.Strings(v, it.HasUniqueValues()),
	)
	fmt.Println(err)
}
Output:

violation: This collection should contain only unique elements.

func StringsProperty added in v0.4.0

func StringsProperty(name string, values []string, options ...Option) Argument

StringsProperty argument is an alias for Strings that automatically adds property name to the current scope.

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

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

func Time

func Time(value time.Time, options ...Option) Argument

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, options ...Option) Argument

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

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, options ...Option) Argument

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

func ValidProperty

func ValidProperty(name string, value Validatable, options ...Option) Argument

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

func Value

func Value(value interface{}, options ...Option) Argument

Value argument is used to validate any supported value. It uses reflection to detect the type of the argument and pass it to a specific validation method.

If the validator cannot determine the value or it is not supported, then NotValidatableError will be returned when calling the validator.Validate method.

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.Value(v, it.IsNotBlank()))
	fmt.Println(err)
}
Output:

violation: This value should not be blank.

type Arguments

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

type ArrayIndexElement

type ArrayIndexElement int

ArrayIndexElement holds up array index value under PropertyPath.

func (ArrayIndexElement) IsIndex

func (a ArrayIndexElement) IsIndex() bool

IsIndex on ArrayIndexElement always returns true.

func (ArrayIndexElement) String

func (a ArrayIndexElement) String() string

String returns array index values converted into a string.

type AtLeastOneOfConstraint added in v0.3.0

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

AtLeastOneOfConstraint is used to set constraints allowing checks 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(constraints ...Constraint) AtLeastOneOfConstraint

AtLeastOneOf function used to set of constraints that the value satisfies at least one of the given constraints. If the list is empty error will be returned.

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.String(
			title,
			validation.AtLeastOneOf(
				it.IsBlank(),
				it.HasMinLength(5),
			),
		),
	)

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

violation: This value should be blank.
violation: This value is too short. It should have 5 characters or more.

func (AtLeastOneOfConstraint) Name added in v0.3.0

func (c AtLeastOneOfConstraint) Name() string

Name is the constraint name.

func (AtLeastOneOfConstraint) SetUp added in v0.3.0

func (c AtLeastOneOfConstraint) SetUp() error

SetUp will return an error if the list of constraints is empty.

func (AtLeastOneOfConstraint) When added in v0.5.3

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

type BoolConstraint

type BoolConstraint interface {
	Constraint
	ValidateBool(value *bool, scope Scope) error
}

BoolConstraint is used to build constraints for boolean values validation.

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 scope. It is useful to apply a simple checks on structs.

Example
package main

import (
	"context"
	"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"
}

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

	err := validator.Validate(
		context.Background(),
		validation.
			CheckProperty("mainCommodity", outlet.MainCommodity.Supports(outlet.Type)).
			Code("unsupportedCommodity").
			Message(
				`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 code:", violation.Code())
			fmt.Println(violation)
		}
	}
}
Output:

violation code: unsupportedCommodity
violation at 'mainCommodity': Commodity "Digital movie" cannot be sold at outlet.

func (Checker) Code added in v0.5.2

func (c Checker) Code(code string) Checker

Code overrides default code for produced violation.

func (Checker) Message added in v0.5.2

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

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

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.

type CompoundConstraint added in v0.3.0

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

CompoundConstraint is used to create your own set of reusable constraints, representing rules to use consistently.

func Compound added in v0.3.0

func Compound(constraints ...Constraint) CompoundConstraint

Compound function used to create set of reusable constraints. If the list is empty error will be returned.

Example
package main

import (
	"context"
	"fmt"

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

func main() {
	title := "bar"
	isEmail := validation.Compound(it.IsEmail(), it.HasLengthBetween(5, 200))

	err := validator.Validate(
		context.Background(),
		validation.String(title, isEmail),
	)

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

violation: This value is not a valid email address.
violation: This value is too short. It should have 5 characters or more.

func (CompoundConstraint) Name added in v0.3.0

func (c CompoundConstraint) Name() string

Name is the constraint name.

func (CompoundConstraint) SetUp added in v0.3.0

func (c CompoundConstraint) SetUp() error

SetUp will return an error if the list of constraints is empty.

func (CompoundConstraint) When added in v0.5.3

func (c CompoundConstraint) When(condition bool) CompoundConstraint

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

type ConditionalConstraint added in v0.2.0

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

ConditionalConstraint is used for conditional validation. Use the When function to initiate a conditional check. If the condition is true, then the constraints passed through the Then function will be applied. Otherwise, the constraints passed through the Else function will apply.

func When added in v0.2.0

func When(condition bool) ConditionalConstraint

When function is used to initiate conditional validation. If the condition is true, then the constraints passed through the Then function will be applied. Otherwise, the constraints passed through the Else function will apply.

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.StringProperty(
			"cardType",
			payment.CardType,
			it.IsOneOfStrings("Visa", "MasterCard"),
		),
		validation.StringProperty(
			"cardNumber",
			payment.CardNumber,
			validation.
				When(payment.CardType == "Visa").
				Then(it.Matches(visaRegex)).
				Else(it.Matches(masterCardRegex)),
		),
	)

	fmt.Println(err)
}
Output:

violation at 'cardNumber': This value is not valid.

func (ConditionalConstraint) Else added in v0.2.0

Else function is used to set a sequence of constraints to be applied 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.String(
			v,
			validation.When(false).
				Then(it.Matches(regexp.MustCompile(`^\w+$`))).
				Else(it.Matches(regexp.MustCompile(`^\d+$`))),
		),
	)
	fmt.Println(err)
}
Output:

<nil>

func (ConditionalConstraint) Name added in v0.2.0

func (c ConditionalConstraint) Name() string

Name is the constraint name.

func (ConditionalConstraint) SetUp added in v0.2.0

func (c ConditionalConstraint) SetUp() error

SetUp will return an error if Then function did not set any constraints.

func (ConditionalConstraint) Then added in v0.2.0

Then function is used to set a sequence of constraints to be applied if the condition is true. If the list is empty error will be returned.

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.String(
			v,
			validation.When(true).Then(it.Matches(regexp.MustCompile(`^\w+$`))),
		),
	)
	fmt.Println(err)
}
Output:

<nil>

type Constraint

type Constraint interface {
	Option
	// Name is a constraint name that can be used in internal errors.
	Name() string
}

Constraint is the base interface to build validation constraints.

type ConstraintAlreadyStoredError

type ConstraintAlreadyStoredError struct {
	Key string
}

ConstraintAlreadyStoredError is returned when trying to put a constraint in the validator store using an existing key.

func (ConstraintAlreadyStoredError) Error

type ConstraintNotFoundError

type ConstraintNotFoundError struct {
	Key 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 {
	Constraint
	ValidateCountable(count int, scope Scope) error
}

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

type CustomStringConstraint added in v0.2.0

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

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

func NewCustomStringConstraint added in v0.2.0

func NewCustomStringConstraint(isValid func(string) bool, parameters ...string) CustomStringConstraint

NewCustomStringConstraint creates a new string constraint from a function with signature func(string) bool. Optional parameters can be used to set up constraint name (first parameter), violation code (second), message template (third). All other parameters are ignored.

Example
package main

import (
	"context"
	"fmt"

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

func main() {
	validate := func(s string) bool {
		return s == "valid"
	}
	constraint := validation.NewCustomStringConstraint(
		validate,
		"ExampleConstraint", // constraint name
		"exampleCode",       // violation code
		"Unexpected value.", // violation message template
	)

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

	fmt.Println(err)
}
Output:

violation: Unexpected value.

func (CustomStringConstraint) Code added in v0.3.0

Code overrides default code for produced violation.

func (CustomStringConstraint) Message added in v0.2.0

func (c CustomStringConstraint) Message(template string, parameters ...TemplateParameter) CustomStringConstraint

Message 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.

func (CustomStringConstraint) Name added in v0.2.0

func (c CustomStringConstraint) Name() string

Name is the constraint name. It can be set via first parameter of function NewCustomStringConstraint.

func (CustomStringConstraint) SetUp added in v0.2.0

func (c CustomStringConstraint) SetUp() error

SetUp always returns no error.

func (CustomStringConstraint) ValidateString added in v0.2.0

func (c CustomStringConstraint) ValidateString(value *string, scope Scope) error

func (CustomStringConstraint) When added in v0.2.0

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

type InapplicableConstraintError

type InapplicableConstraintError struct {
	Constraint Constraint
	ValueType  string
}

InapplicableConstraintError occurs when trying to use constraint on not applicable values. For example, if you are trying to compare slice with a number.

func NewInapplicableConstraintError

func NewInapplicableConstraintError(constraint Constraint, valueType string) InapplicableConstraintError

NewInapplicableConstraintError helps to create a error on trying to use constraint on not applicable values.

func (InapplicableConstraintError) Error

func (err InapplicableConstraintError) Error() string

type IterableConstraint

type IterableConstraint interface {
	Constraint
	ValidateIterable(value generic.Iterable, scope Scope) error
}

IterableConstraint is used to build constraints for validation of iterables (arrays, slices, or maps).

At this moment working with numbers is based on reflection. Be aware. This constraint is subject to be changed after generics implementation in Go.

type NewViolationFunc

type NewViolationFunc func(
	code,
	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(
	code,
	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 {
	Constraint
	ValidateNil(scope Scope) error
}

NilConstraint is used for constraints that need to check value for nil. In common case you do not need to implement it in your constraints because nil values should be ignored.

type NotValidatableError

type NotValidatableError struct {
	Value reflect.Value
}

NotValidatableError occurs when validator cannot determine type by reflection or it is not supported by validator.

func (NotValidatableError) Error

func (err NotValidatableError) Error() string

type NumberConstraint

type NumberConstraint interface {
	Constraint
	ValidateNumber(value generic.Number, scope Scope) error
}

NumberConstraint is used to build constraints for numeric values validation.

At this moment working with numbers is based on reflection. Be aware. This constraint is subject to be changed after generics implementation in Go.

type Option

type Option interface {
	// SetUp is called when the validation process is initialized
	// and can be used to gracefully handle errors when initializing constraints.
	SetUp() error
}

Option is used to set up validation process of a value.

func ArrayIndex

func ArrayIndex(index int) Option

ArrayIndex option adds index of the given array to current validation scope.

func PropertyName

func PropertyName(propertyName string) Option

PropertyName option adds name of the given property to current validation scope.

type PropertyNameElement

type PropertyNameElement string

PropertyNameElement holds up property name value under PropertyPath.

func (PropertyNameElement) IsIndex

func (p PropertyNameElement) IsIndex() bool

IsIndex on PropertyNameElement always returns false.

func (PropertyNameElement) String

func (p PropertyNameElement) 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 WithProperty or 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) MarshalJSON

func (path *PropertyPath) MarshalJSON() ([]byte, error)

MarshalJSON will marshal property path value to a JSON string.

func (*PropertyPath) String

func (path *PropertyPath) String() string

String is used to format property path to a string.

func (*PropertyPath) WithIndex added in v0.2.0

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

WithIndex returns new PropertyPath with appended ArrayIndexElement 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 PropertyNameElement 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 Scope

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

Scope holds the current state of validation. On the client-side of the package, it can be used to build violations.

func (Scope) AtIndex added in v0.4.1

func (s Scope) AtIndex(index int) Scope

AtIndex returns a copy of the scope with property path appended by the given array index.

func (Scope) AtProperty added in v0.4.1

func (s Scope) AtProperty(name string) Scope

AtProperty returns a copy of the scope with property path appended by the given property name.

func (Scope) BuildViolation

func (s Scope) BuildViolation(code, message string) *ViolationBuilder

BuildViolation is used to create violations in validation methods of constraints. This method automatically injects the property path and language of the current validation scope.

func (Scope) Context

func (s Scope) Context() context.Context

Context returns context value that was passed to the validator by Context argument or by creating scoped validator with the validator.WithContext method.

func (Scope) Validator added in v0.3.0

func (s Scope) Validator() *Validator

Validator creates a new validator for the given scope. This validator can be used to perform complex validation on a custom constraint using existing constraints.

Example
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 {
	validation.Constraint
	ValidateFile(file *File, scope validation.Scope) error
}

func FileArgument(file *File, options ...validation.Option) validation.Argument {
	return validation.NewArgument(options, func(constraint validation.Constraint, scope validation.Scope) error {
		if c, ok := constraint.(FileConstraint); ok {
			return c.ValidateFile(file, scope)
		}
		// If you want to use built-in constraints for checking for nil or empty values
		// such as it.IsNil() or it.IsBlank().
		if c, ok := constraint.(validation.NilConstraint); ok {
			if file == nil {
				return c.ValidateNil(scope)
			}
			return nil
		}

		return validation.NewInapplicableConstraintError(constraint, "File")
	})
}

// 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) SetUp() error {
	return nil
}

func (c AllowedFileExtensionConstraint) Name() string {
	return "AllowedFileExtensionConstraint"
}

func (c AllowedFileExtensionConstraint) ValidateFile(file *File, scope validation.Scope) error {
	if file == nil {
		return nil
	}

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

	return scope.Validator().AtProperty("name").Validate(
		scope.Context(),
		validation.String(
			extension,
			it.IsOneOfStrings(c.extensions...).Message("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 int64
	maxSize int64
}

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

func (c AllowedFileSizeConstraint) SetUp() error {
	return nil
}

func (c AllowedFileSizeConstraint) Name() string {
	return "AllowedFileSizeConstraint"
}

func (c AllowedFileSizeConstraint) ValidateFile(file *File, scope validation.Scope) error {
	if file == nil {
		return nil
	}

	size := len(file.Data)

	return scope.Validator().Validate(
		scope.Context(),
		validation.Number(
			size,
			it.IsGreaterThanInteger(c.minSize).Message("File size is too small."),
			it.IsLessThanInteger(c.maxSize).Message("File size is too large."),
		),
	)
}

func main() {
	// this constraints will be applied to all files uploaded as avatars
	avatarConstraints := []validation.Option{
		FileHasAllowedExtension("jpeg", "jpg", "gif"),
		FileHasAllowedSize(100, 1000),
	}
	// this constraints will be applied to all files uploaded as documents
	documentConstraints := []validation.Option{
		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
				FileArgument(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
				FileArgument(request.File, documentConstraints...),
			)
			fmt.Println(err)
		}
	}

}
Output:

violation at 'name': Not allowed extension. Must be one of: jpeg, jpg, gif.; violation: File size is too small.
violation at 'name': Not allowed extension. Must be one of: doc, pdf, txt.; violation: File size is too large.

type SequentialConstraint added in v0.3.0

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

SequentialConstraint is used to set constraints allowing to interrupt the validation once the first violation is raised.

func Sequentially added in v0.2.0

func Sequentially(constraints ...Constraint) SequentialConstraint

Sequentially function used to set of constraints that should be validated step-by-step. If the list is empty error will be returned.

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.String(
			title,
			validation.Sequentially(
				it.IsBlank(),       // validation will fail on first constraint
				it.HasMinLength(5), // this constraint will be ignored
			),
		),
	)

	fmt.Println(err)
}
Output:

violation: This value should be blank.

func (SequentialConstraint) Name added in v0.3.0

func (c SequentialConstraint) Name() string

Name is the constraint name.

func (SequentialConstraint) SetUp added in v0.3.0

func (c SequentialConstraint) SetUp() error

SetUp will return an error if the list of constraints is empty.

func (SequentialConstraint) When added in v0.5.3

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

type StringConstraint

type StringConstraint interface {
	Constraint
	ValidateString(value *string, scope Scope) error
}

StringConstraint is used to build constraints for string values validation.

type StringsConstraint added in v0.4.0

type StringsConstraint interface {
	Constraint
	ValidateStrings(values []string, scope Scope) error
}

StringsConstraint is used to build constraints to validate an array or a slice of strings.

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 {
	Constraint
	ValidateTime(value *time.Time, scope Scope) 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 ValidateByConstraintFunc

type ValidateByConstraintFunc func(constraint Constraint, scope Scope) error

ValidateByConstraintFunc is used for building validation functions for the values of specific types.

type Validator

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

Validator is the main 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 validator options.

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

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

AtIndex method creates a new scoped validator with injected array index element to scope property path.

Example
books := []Book{{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 scoped validator with injected property name element to scope property path.

Example
book := &Book{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, code, message string) *ViolationBuilder

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

Example (BuildingViolation)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/muonsoft/validation"
)

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

	violation := validator.BuildViolation(context.Background(), "clientCode", "Client message with {{ parameter }}.").
		AddParameter("{{ parameter }}", "value").
		CreateViolation()

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

Client message with value.
Example (TranslatableParameter)
package main

import (
	"context"
	"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)
	}

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

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

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

func (*Validator) Validate

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

Validate is the main validation method. It accepts validation arguments. Arguments 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.StringsProperty("keywords", document.Keywords, it.HasUniqueValues()),
		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"
	"fmt"
	"log"

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

func main() {
	s := ""

	validator, err := validation.NewValidator()
	if err != nil {
		log.Fatal(err)
	}
	err = validator.Validate(context.Background(), validation.String(s, it.IsNotBlank()))

	fmt.Println(err)
}
Output:

violation: This value should not be blank.
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"
	"fmt"
	"regexp"

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

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) SetUp() error {
	// you may return errors here on the constraint initialization process
	return nil
}

func (c NumericConstraint) Name() string {
	return "NumericConstraint"
}

func (c NumericConstraint) ValidateString(value *string, scope validation.Scope) 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 scope to build violation with translations
	return scope.BuildViolation("notNumeric", "This value should be numeric.").CreateViolation()
}

func main() {
	s := "alpha"

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

	fmt.Println(err)
}
Output:

violation: This value should be numeric.
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().Message("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:

[{"code":"notBlank","message":"Значение не должно быть пустым.","propertyPath":"title"},{"code":"notBlank","message":"Значение не должно быть пустым.","propertyPath":"author"},{"code":"countTooFew","message":"Эта коллекция должна содержать 1 элемент или больше.","propertyPath":"keywords"}]
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,
			validation.PropertyName("properties"),
			validation.ArrayIndex(1),
			validation.PropertyName("tag"),
			it.IsNotBlank(),
		),
	)

	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, validation.PropertyName("property"), it.IsNotBlank()),
		validation.StringProperty("property", s, it.IsNotBlank()),
	)

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

property path: property
Example (PropertyPathWithScopedValidator)
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.Validate(
		context.Background(),
		validation.Language(language.Russian),
		validation.Iterable(tags, it.HasMinCount(1).MinMessage(customMessage)),
	)

	fmt.Println(err)
}
Output:

violation: теги должны содержать 1 элемент и более
Example (TranslationsByArgument)
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.Validate(
		context.Background(),
		validation.Language(language.Russian),
		validation.String(s, it.IsNotBlank()),
	)

	fmt.Println(err)
}
Output:

violation: Значение не должно быть пустым.
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 (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"
	"fmt"

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

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

func (c NestingLimitConstraint) SetUp() error {
	return nil
}

func (c NestingLimitConstraint) Name() string {
	return "NestingLimitConstraint"
}

func (c NestingLimitConstraint) ValidateProperty(property *Property, scope validation.Scope) error {
	// You can read any passed context value from scope.
	level, ok := scope.Context().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 scope.
			BuildViolation("nestingLimitReached", "Maximum nesting level reached.").
			CreateViolation()
	}

	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 {
	validation.Constraint
	ValidateProperty(property *Property, scope validation.Scope) error
}

// To create your own functional argument for validation simply create a function with
// a typed value and use the validation.NewArgument constructor.
func PropertyArgument(property *Property, options ...validation.Option) validation.Argument {
	return validation.NewArgument(options, func(constraint validation.Constraint, scope validation.Scope) error {
		if c, ok := constraint.(PropertyConstraint); ok {
			return c.ValidateProperty(property, scope)
		}
		// If you want to use built-in constraints for checking for nil or empty values
		// such as it.IsNil() or it.IsBlank().
		if c, ok := constraint.(validation.NilConstraint); ok {
			if property == nil {
				return c.ValidateNil(scope)
			}
			return nil
		}

		return validation.NewInapplicableConstraintError(constraint, "Property")
	})
}

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.
		PropertyArgument(&p, ItIsNotDeeperThan(3)),
		validation.StringProperty("name", p.Name, it.IsNotBlank()),
		// This should run recursive validation for properties.
		validation.IterableProperty("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.Iterable(properties))

	fmt.Println(err)
}
Output:

violation at '[0].properties[0].properties[0].properties[0]': Maximum nesting level reached.

func (*Validator) ValidateBool

func (validator *Validator) ValidateBool(ctx context.Context, value bool, options ...Option) error

ValidateBool is an alias for validating a single boolean value.

func (*Validator) ValidateBy

func (validator *Validator) ValidateBy(constraintKey string) Constraint

ValidateBy is used to get the constraint from the internal validator store. If the constraint does not exist, then the validator will return a ConstraintNotFoundError during the validation process. For storing a constraint you should use the StoredConstraint option.

Example (CustomServiceConstraint)
package main

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

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

type contextKey string

const exampleKey contextKey = "exampleKey"

type TagStorage struct {
	// this might be stored in the database
	tags []string
}

func (storage *TagStorage) FindByName(ctx context.Context, name string) ([]string, error) {
	contextValue, ok := ctx.Value(exampleKey).(string)
	if !ok {
		return nil, errors.New("context value missing")
	}
	if contextValue != "value" {
		return nil, errors.New("invalid context value")
	}

	found := make([]string, 0)

	for _, tag := range storage.tags {
		if tag == name {
			found = append(found, tag)
		}
	}

	return found, nil
}

type ExistingTagConstraint struct {
	storage *TagStorage
}

func (c *ExistingTagConstraint) SetUp() error {
	return nil
}

func (c *ExistingTagConstraint) Name() string {
	return "ExistingTagConstraint"
}

func (c *ExistingTagConstraint) ValidateString(value *string, scope validation.Scope) error {
	// usually, you should ignore empty values
	// to check for an empty value you should use it.NotBlankConstraint
	if value == nil || *value == "" {
		return nil
	}

	// you can pass the context value from the scope
	entities, err := c.storage.FindByName(scope.Context(), *value)
	// here you can return a service error so that the validation process
	// is stopped immediately
	if err != nil {
		return err
	}
	if len(entities) > 0 {
		return nil
	}

	// use the scope to build violation with translations
	return scope.
		BuildViolation("unknownTag", `Tag "{{ value }}" does not exist.`).
		// you can inject parameter value to the message here
		AddParameter("{{ value }}", *value).
		CreateViolation()
}

type StockItem struct {
	Name string
	Tags []string
}

func (s StockItem) Validate(ctx context.Context, validator *validation.Validator) error {
	return validator.Validate(
		ctx,
		validation.StringProperty("name", s.Name, it.IsNotBlank(), it.HasMaxLength(20)),
		validation.EachStringProperty("tags", s.Tags, validator.ValidateBy("isTagExists")),
	)
}

func main() {
	storage := &TagStorage{tags: []string{"movie", "book"}}
	isTagExists := &ExistingTagConstraint{storage: storage}

	// custom constraint can be stored in the validator's internal store
	// and can be used later by calling the validator.ValidateBy method
	validator, err := validation.NewValidator(
		validation.StoredConstraint("isTagExists", isTagExists),
	)
	if err != nil {
		log.Fatal(err)
	}

	item := StockItem{
		Name: "War and peace",
		Tags: []string{"book", "camera"},
	}

	err = validator.Validate(
		// you can pass here the context value to the validation scope
		context.WithValue(context.Background(), exampleKey, "value"),
		validation.Valid(item),
	)

	fmt.Println(err)
}
Output:

violation at 'tags[1]': Tag "camera" does not exist.

func (*Validator) ValidateCountable

func (validator *Validator) ValidateCountable(ctx context.Context, count int, options ...Option) error

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

func (*Validator) ValidateEach

func (validator *Validator) ValidateEach(ctx context.Context, value interface{}, options ...Option) error

ValidateEach is an alias for validating each value of an iterable (an array, slice, or map).

func (*Validator) ValidateEachString

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

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

func (*Validator) ValidateIterable

func (validator *Validator) ValidateIterable(ctx context.Context, value interface{}, options ...Option) error

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

func (*Validator) ValidateNumber

func (validator *Validator) ValidateNumber(ctx context.Context, value interface{}, options ...Option) error

ValidateNumber is an alias for validating a single numeric value (integer or float).

func (*Validator) ValidateString

func (validator *Validator) ValidateString(ctx context.Context, value string, options ...Option) error

ValidateString is an alias for validating a single string value.

Example (ShorthandAlias)
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, options ...Option) error

ValidateStrings is an alias for validating slice of strings.

func (*Validator) ValidateTime

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

ValidateTime is an alias for validating a single time value.

func (*Validator) ValidateValidatable

func (validator *Validator) ValidateValidatable(ctx context.Context, validatable Validatable, options ...Option) error

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

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.CountableProperty("tags", len(p.Tags), it.HasMinCount(5)),
		validation.StringsProperty("tags", p.Tags, it.HasUniqueValues()),
		validation.EachStringProperty("tags", p.Tags, it.IsNotBlank()),
		// this also runs validation on each of the components
		validation.IterableProperty("components", p.Components, it.HasMinCount(1)),
	)
}

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 (*Validator) ValidateValue

func (validator *Validator) ValidateValue(ctx context.Context, value interface{}, options ...Option) error

ValidateValue is an alias for validating a single value of any supported type.

func (*Validator) WithLanguage

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

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

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

func StoredConstraint added in v0.2.0

func StoredConstraint(key string, constraint Constraint) ValidatorOption

StoredConstraint option can be used to store a constraint in an internal validator store. It can later be used by the validator.ValidateBy method. This can be useful for passing custom or prepared constraints to Validatable.

If the constraint already exists, a ConstraintAlreadyStoredError will be returned.

Example
package main

import (
	"context"
	"fmt"
	"log"

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

func main() {
	validator, err := validation.NewValidator(
		validation.StoredConstraint("notEmpty", it.IsNotBlank().Message("value should not be empty")),
	)
	if err != nil {
		log.Fatal(err)
	}

	err = validator.ValidateString(context.Background(), "", validator.ValidateBy("notEmpty"))
	fmt.Println(err)
}
Output:

violation: value should not be empty

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

	// Code is unique, short, and semantic string that can be used to programmatically
	// test for specific violation. All "code" values are defined in the "github.com/muonsoft/validation/code" package
	// and are protected by backward compatibility rules.
	Code() string

	// Is can be used to check that the violation contains one of the specific codes.
	// For an empty list, it should always returns false.
	Is(codes ...string) 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) AddParameter

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

AddParameter adds one parameter into a slice of parameters.

func (*ViolationBuilder) BuildViolation

func (b *ViolationBuilder) BuildViolation(code, message string) *ViolationBuilder

BuildViolation creates a new ViolationBuilder for composing Violation object fluently.

func (*ViolationBuilder) CreateViolation

func (b *ViolationBuilder) CreateViolation() Violation

CreateViolation creates a new violation with given parameters and returns it. Violation is created by calling the CreateViolation method of the ViolationFactory.

func (*ViolationBuilder) SetLanguage

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

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

func (*ViolationBuilder) SetParameters

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

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

func (*ViolationBuilder) SetPluralCount

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

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

func (*ViolationBuilder) SetPropertyPath

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

SetPropertyPath sets a property path of violated attribute.

type ViolationFactory

type ViolationFactory interface {
	// CreateViolation creates a new instance of Violation.
	CreateViolation(
		code,
		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(), "", "foo").CreateViolation()

	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(), "", "foo").CreateViolation(),
		validator.BuildViolation(context.Background(), "", "bar").CreateViolation(),
	)

	appendErr := violations.AppendFromError(err)

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

append error: <nil>
violations: violation: foo; violation: 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) Each added in v0.7.0

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

Each 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 Each function.

func (*ViolationList) Error

func (list *ViolationList) Error() string

Error returns a formatted list of errors as a string.

func (*ViolationList) Filter

func (list *ViolationList) Filter(codes ...string) *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(), "", "foo").CreateViolation(),
		validator.BuildViolation(context.Background(), "", "bar").CreateViolation(),
	)

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

violation: foo
violation: bar

func (*ViolationList) Has

func (list *ViolationList) Has(codes ...string) bool

Has can be used to check that at least one of the violations contains one of the specific codes. For an empty list of codes, it should always returns false.

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.

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) Code added in v0.3.0

func (element *ViolationListElement) Code() string

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(codes ...string) bool

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 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) Violation added in v0.3.0

func (element *ViolationListElement) Violation() Violation

Violation returns underlying violation value.

Directories

Path Synopsis
Package code contains a list of unique, short, and semantic violation codes.
Package code contains a list of unique, short, and semantic violation codes.
Package generic contains structures for processing generic values such as numbers and iterables.
Package generic contains structures for processing generic values such as numbers and iterables.
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