validate

package
v0.2.1 Latest Latest
Warning

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

Go to latest
Published: Feb 27, 2024 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package validate contains field validations for Go structs, appropriated to use with [*lit.Request].

There are two ways a struct can be validated:

  • Explicitly, by calling Fields and passing the validations required;
  • Implicitly, by making the struct implement Validatable with a pointer receiver and using the binding functions.

When a validation fails, Fields use the Message and Fields attributes of the Field struct to build the validation error.

Custom validations

A validation is simply an instance of the struct Field. In order to create a new validation, is enough to just create your own instance, passing the arguments. You could also change only the Message field, if that meets your use case. Check the package-level examples.

Example (CustomMessage)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/render"
	"github.com/jvcoutinho/lit/validate"
)

type DivideRequest struct {
	A int `query:"a"`
	B int `query:"b"`
}

func (r *DivideRequest) Validate() []validate.Field {
	notZeroValidation := validate.NotEqual(&r.B, 0)
	notZeroValidation.Message = "{0} should not be zero: invalid division"

	return []validate.Field{
		notZeroValidation,
	}
}

// Divide returns the division of two integers, given the second is not zero.
func Divide(r *lit.Request) lit.Response {
	req, err := bind.Query[DivideRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	return render.OK(req.A / req.B)
}

func main() {
	r := lit.NewRouter()
	r.GET("/div", Divide)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/div?a=4&b=2", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

	res = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/div?a=2&b=0", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

}
Output:

2
{"message":"b should not be zero: invalid division"}
Example (CustomValidations)
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/render"
	"github.com/jvcoutinho/lit/validate"
)

// Even validates if target is an even number.
func Even(target *int) validate.Field {
	return validate.Field{
		Valid:   target != nil && *target%2 == 0,
		Message: "{0} should be an odd number",
		Fields:  []any{target},
	}
}

// MultipleField validates if target is multiple of field.
func MultipleField(target *int, field *int) validate.Field {
	return validate.Field{
		Valid:   target != nil && field != nil && *target%*field == 0,
		Message: "{1} should be divisible by {0}",
		Fields:  []any{target, field},
	}
}

type EvenNumbersBetweenRequest struct {
	A int `query:"a"`
	B int `query:"b"`
}

func (r *EvenNumbersBetweenRequest) Validate() []validate.Field {
	return []validate.Field{
		Even(&r.A),
		MultipleField(&r.B, &r.A),
	}
}

// EvenNumbersBetween compute the even numbers between two numbers, given the first is even and
// the second is multiple of the first.
func EvenNumbersBetween(r *lit.Request) lit.Response {
	req, err := bind.Query[EvenNumbersBetweenRequest](r)
	if err != nil {
		return render.BadRequest(err)
	}

	evenNumbersBetween := make([]int, 0)
	for i := req.A; i <= req.B; i += 2 {
		evenNumbersBetween = append(evenNumbersBetween, i)
	}

	return render.OK(evenNumbersBetween)
}

func main() {
	r := lit.NewRouter()
	r.GET("/", EvenNumbersBetween)

	res := httptest.NewRecorder()
	req := httptest.NewRequest(http.MethodGet, "/?a=4&b=20", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

	res = httptest.NewRecorder()
	req = httptest.NewRequest(http.MethodGet, "/?a=2&b=3", nil)
	r.ServeHTTP(res, req)
	fmt.Println(res.Body)

}
Output:

[4,6,8,10,12,14,16,18,20]
{"message":"a should be divisible by b"}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fields

func Fields[T any](target *T, validations ...Field) error

Fields validates the fields of a struct of type T.

It uses the tags of the binding functions (such as "uri", "query" or "json") from the fields to build a message for the user in case the validation fails. If none of these tags are set or are set as the empty string, Fields uses the field's name instead.

If T is not a struct type, Fields panics.

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/jvcoutinho/lit"
	"github.com/jvcoutinho/lit/bind"
	"github.com/jvcoutinho/lit/validate"
)

func main() {
	req := httptest.NewRequest(http.MethodPost, "/books", strings.NewReader(`
		{"name": "Percy Jackson", "publishYear": 2007}
	`))

	r := lit.NewRequest(req)

	type RequestBody struct {
		Name        string `json:"name"`
		PublishYear int    `json:"publishYear"`
	}

	body, _ := bind.Body[RequestBody](r)

	err := validate.Fields(&body,
		validate.Greater(&body.PublishYear, 2009),
		validate.Less(&body.PublishYear, 2020),
		validate.HasPrefix(&body.Name, "A"),
	)

	if err != nil {
		fmt.Println(err)
	}

}
Output:

publishYear should be greater than 2009; name should start with "A"

Types

type Error

type Error struct {
	// Validations that have failed.
	Violations []Field
}

Error occurs when at least one Field validation fails in validation steps.

func (Error) Error

func (e Error) Error() string

type Field

type Field struct {
	// Determines if this validation has succeeded.
	Valid bool

	// A user-friendly message that can be displayed if the validation fails. In this case,
	// "{i}" placeholders, where i is the index of the replacement in Fields, are replaced by the field's tag values.
	Message string

	// Pointers to the fields involved in this validation. It is a slice because the validation
	// can have multiple fields as arguments.
	Fields []any
}

Field represents a field validation.

func After

func After(target *time.Time, value time.Time) Field

After validates if target is after value.

func Before

func Before(target *time.Time, value time.Time) Field

Before validates if target is before value.

func Between

func Between[T constraints.Ordered](target *T, min, max T) Field

Between validates if target is greater than min and less than max.

func BetweenLength

func BetweenLength[T any](target *T, min, max int) Field

BetweenLength validates if target has a length greater or equal than min and less or equal than max.

If target is not a pointer to a slice, array, string or map, BetweenLength panics.

func BetweenTime

func BetweenTime(target *time.Time, min, max time.Time) Field

BetweenTime validates if target is after min and before max.

func DateTime

func DateTime(target *string, layout string) Field

DateTime validates if target is a valid date-time string.

Note that binding functions bind strings to fields with type time.Time if the layout is time.RFC3339, validating them in the process.

func Email

func Email(target *string) Field

Email validates if target is a valid e-mail.

func Empty

func Empty[T any](target *T) Field

Empty validates if target is empty.

If target is not a pointer to a slice, array, string or map, Empty panics.

func Equal

func Equal[T comparable](target *T, value T) Field

Equal validates if target is equal to value.

func EqualField

func EqualField[T comparable](target *T, field *T) Field

EqualField validates if target is equal to the value of field.

func Greater

func Greater[T constraints.Ordered](target *T, value T) Field

Greater validates if target is greater than value.

func GreaterField

func GreaterField[T constraints.Ordered](target *T, field *T) Field

GreaterField validates if target is greater than the value of field.

func GreaterOrEqual

func GreaterOrEqual[T constraints.Ordered](target *T, value T) Field

GreaterOrEqual validates if target is greater or equal than value.

func GreaterOrEqualField

func GreaterOrEqualField[T constraints.Ordered](target *T, field *T) Field

GreaterOrEqualField validates if target is greater or equal than the value of field.

func HasPrefix

func HasPrefix(target *string, prefix string) Field

HasPrefix validates if target starts with prefix.

func HasSuffix

func HasSuffix(target *string, suffix string) Field

HasSuffix validates if target ends with suffix.

func IPAddress

func IPAddress(target *string) Field

IPAddress validates if target is a valid IPv4 or IPv6 address.

func IPv4Address

func IPv4Address(target *string) Field

IPv4Address validates if target is a valid IPv4 address.

func IPv6Address

func IPv6Address(target *string) Field

IPv6Address validates if target is a valid IPv6 address.

func Length

func Length[T any](target *T, value int) Field

Length validates if target has a length of value.

If target is not a pointer to a slice, array, string or map, Length panics.

func Less

func Less[T constraints.Ordered](target *T, value T) Field

Less validates if target is less than value.

func LessField

func LessField[T constraints.Ordered](target *T, field *T) Field

LessField validates if target is less than the value of field.

func LessOrEqual

func LessOrEqual[T constraints.Ordered](target *T, value T) Field

LessOrEqual validates if target is less or equal than value.

func LessOrEqualField

func LessOrEqualField[T constraints.Ordered](target *T, field *T) Field

LessOrEqualField validates if target is less or equal than the value of field.

func Lowercase

func Lowercase(target *string) Field

Lowercase validates if target contains only lowercase characters.

func MaxLength

func MaxLength[T any](target *T, value int) Field

MaxLength validates if target has a length less or equal than value.

If target is not a pointer to a slice, array, string or map, MaxLength panics.

func MinLength

func MinLength[T any](target *T, value int) Field

MinLength validates if target has a length greater or equal than value.

If target is not a pointer to a slice, array, string or map, MinLength panics.

func NotEmpty

func NotEmpty[T any](target *T) Field

NotEmpty validates if target is not empty.

If target is not a pointer to a slice, array, string or map, NotEmpty panics.

func NotEqual

func NotEqual[T comparable](target *T, value T) Field

NotEqual validates if target is not equal to value.

func NotEqualField

func NotEqualField[T comparable](target *T, field *T) Field

NotEqualField validates if target is not equal to the value of field.

func OneOf

func OneOf[T comparable](target *T, values ...T) Field

OneOf validates if target is one of values.

func Required

func Required[T any](target *T) Field

Required validates that target is not nil. Suited for when target is a pointer field.

func Substring

func Substring(target *string, substring string) Field

Substring validates if target contains the given substring.

func UUID

func UUID(target *string) Field

UUID validates if target is a valid UUID of any version.

func Uppercase

func Uppercase(target *string) Field

Uppercase validates if target contains only uppercase characters.

type Validatable

type Validatable interface {
	// Validate returns a list of field validations.
	Validate() []Field
}

Validatable structs can be validated.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL