validator

package module
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Jul 3, 2023 License: MIT Imports: 10 Imported by: 0

README

go-struct-validator

GitHub tag (latest SemVer) golangci-lint Coverage Status Go Report Card GoDoc

Golang Struct validator.

Features
  • Nested struct validation
  • Activation triggers: Allows selective validation and struct re-use.
Getting Started

A guide on how to quickly get started.

  1. Customize validation options (optional)
  2. Add custom filter/validation functions (optional)
  3. Validate away!
go get github.com/SharkFourSix/go-strutct-validator
package main
import "github.com/SharkFourSix/go-strutct-validator"

type Person {
    Id int `validator:"min(1000)" trigger:"update,delete"` // evaluate only when 'update' or 'delete' triggers have been specified
    Age int `validator:"min(18)|max(65)"` // n >= 18 and n <= 65
    Name string `validator:"max(20)" filter:"upper"` // len(s) <= 20
}

func main(){

    // 1. Customize validation options
    validator.SetupOptions(func(opts *validator.ValidationOptions){
        // override options here. See available options
    })

    // 2.Add custom validation and filter functions
    validator.AddFilter("upper", func(ctx *validator.ValidationContext) reflect.Value{
        ctx.ValueMustBeOfKind(reflect.String)

        if !ctx.IsNull {
            stringValue := strings.ToUpper(ctx.GetValue().String())
            if ctx.IsPointer {
                ctx.GetValue().Set(&stringValue)
            }else{
                ctx.GetValue().SetString(stringValue)
            }
        }

        return ctx.GetValue()
    })

    person := Person{Age: 20, Name: "Bames Jond"}

    // validate
    result := validator.Validate(&person)
    if result.IsValid() {
        fmt.Println("validation passed")
    }else{
        fmt.Println(result.Error)
        fmt.Println(result.FieldErrors)
    }
}
Design Philosophy

This librabry provides validation functionality for structs only. It does not support data binding.

Each validation rule correspods to a function, which accepts a validator.ValidationContext, containing the value to validate as well as any arguments that the function may require.

Validating required vs optional values

The contract for validating pointer values is to first inspect whether the pointer is null (validationContext.IsNull), returning true if that is the case, implying an optional value, or, continuing with the validation in case the pointer is not null.

The contract for validating literal values is to inspect the values and perform validation logic accordingly.

Validation functions and filters

Both validation and filter functions accept the same input parameter validator.ValidationContext.

Validation functions return true or false (bool) to indicate whether the validation test passed or failed. If the validation function wishes to provide an error message, it may do so through validator.ValidationContext.ErrorMessage.

The goal of filter functions is to allow transforming data into desired and suitable formats.

NOTE: Because this is not a data binding library, filters may not change the data type since the type of the input value cannot be changed.

Filters return reflect.Value, which may be a newly allocated value or simply the same value found stored in validator.ValidationContext.value.

To access the input value within a filter or validator, call ValidationContext.GetValue(), which will return the underlying value (reflect.Value), resolving pointers (1 level deep) if necessary.

To check the type of the input value, you can use ValidationContext.IsValueOfKind(...reflect.Kind) or ValidationContext.IsValueOfType(inteface{}).

Sample validator

func MyValidator(ctx *validator.ValidationContext) bool {
    // First always check if the value is (a pointer and) null.
    if ctx.IsNull {
        // treat this as an optional field. if the caller decides otherwise, the first validtor in the list will be "requried"
        return true
    }

    // ..check supported type (will panic)
    ctx.MustBeOfKind(reflect.String)

    // or only check for supported types only without needing to panic
    if ctx.IsValueOfKind(reflect.String) {
        myString := ctx.GetValue().String()

        // apply validation logic
        if !strings.HasSuffix(myString, ".com") {

            // provide an optional error message. The validation orchestrator will set one for you if you do not specify one
            ctx.ErrorMessage = "only .com domains are allowed"

            return false
        }
    }else{
        // panic because this is not a validation error, rather a type/low level error that needs to be fixed
        panic("unsupported type " + ctx.ValueKind.String())
    }

    return true
}

Sample filter

func InPlaceMutationFilter(ctx *validator.ValidationContext) reflect.Value {

    // Check supported type (will panic)
    ctx.MustBeOfKind(reflect.Int)

    // always check if the value is (a pointer and) null.
    if !ctx.IsNull {
        myNumber := ctx.GetValue().Int()
        myNumber = myNumber * myNumber

        // update the value in place
        if ctx.IsPointer {
            ctx.GetValue().Set(&myNumber)
        }else{
            ctx.GetValue().Set(myNumber)
        }
    }

    return ctx.GetValue()
}
func NewValueFilter(ctx *validator.ValidationContext) reflect.Value {

    // Check supported type (will panic)
    ctx.MustBeOfKind(reflect.Int)

    value := ctx.GetValue()

    // always check if the value is (a pointer and) null.
    if !ctx.IsNull {
        myNumber := value.Int()
        myNumber = myNumber * myNumber

        // return a new value
        if ctx.IsPointer {
            value = reflect.ValueOf(&myNumber)
        }else{
            value = reflect.ValueOf(myNumber)
        }
    }

    return value
}
Validation flags

Validation flags control the validation behavior per input value.

type MyStruct struct {
    Foo *string `validate:"min(5)|max(50)" flags:"allow_zero"`
}

myStruct := MyStruct {}
result := validator.Validate(&myStruct)
fmt.Println(result.IsValid()) // --> true

Refer to Packaged Flags

Execution order and activation

Selective Validation

Sometimes you may wish to use the same struct but only work with specific fields in specific cases. Instead of creating a struct for each use case, you can use activation triggers to selectively evaluate those specific fields.

To specify an activation trigger, include the name of the trigger in the trigger tag.

NOTE Trigger names can be anything.

A special activation trigger 'all' exists which causes a field to be evaluated in all use cases. Omitting the trigger tag is equivalent to explicitly specifying ONLY this special value.

type ResourceRequest struct {
    ResourceId string `validator:"uuidv4" trigger:"update,delete"`
    ResourceName string `validator:"min(3)|max(50)"`
}

myResource := ResourceRequest{}

// get from some http request
httpRequestDataBinder.BindData(&myResource)

// making the following call validates .ResourceName
validator.Validate(&myResource, "create")

// ... later on when updating the resource name, both .ResourceId and .ResourceName
// will get evaludated
validator.Validate(&myResource, "update")

Execution Order

Validators are evaluated first and filters last.

Accessing validation errors

validator.ValidationResults.IsValid() indicates whether validation succeeded or not. If validation did not exceed, you are guaranteed to have at least one validation error in validator.ValidationResults.FieldErrors.

Each field error contains the label take from the field name or label tag, and error message returned by the failing validation function, or taken from the message tag or default generic error message if none of the former options were specified.

func main(){
    type Person {
        Age int `validator:"min(18)|max(65)" message:"You're too young or too old for this"`
        Name string `validator:"min(20)" filter:"upper" message:"Your name is too long" label:"Candidate name"`
    }

    person := Person{Age: 16, Name: "Bames Jond"}

    // validate
    result := validator.Validate(&person)
    if result.IsValid() {
        fmt.Println("validation passed")
    }else{
        fmt.Println(result.Error)
        fmt.Println(result.FieldErrors)
    }
}
Packaged validators
Name Function Parameters
required IsRequired
alphanum IsAlphaNumeric
uuid1 IsUuid1
uuid2 IsUuid2
uuid3 IsUuid3
uuid4 IsUuid4
min IsMin (number)
max IsMax (number)
enum IsEnum (...string)
email IsEmail
at_least_today IsOrBeforeToday (dateLayout) - optional
at_most_today IsOrAfterToday (dateLayout) - optional
today IsToday (dateLayout) - optional
before_today IsBeforeToday (dateLayout) - optional
after_today IsAfterToday (dateLayout) - optional
Packaged filters
Name Function Parameters Description
trim Trim Trim string space
Packaged flags
Name Description
allow_zero skips validation of values that match zero values
Validation options

Refer to validator.ValidationOptions to see list of options in validator.go

Documentation

https://pkg.go.dev/github.com/SharkFourSix/go-struct-validator#section-documentation

Contribution

Contributions are welcome

Inspiration taken from https://github.com/gookit/validate

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddFilter

func AddFilter(name string, v FilterFunction)

AddFilter adds the given filter function to the list of filters

The backed storage containing the list of filters is not thread safe and so this function must be called once during package or application initialization.

You cannot replace filter functions that have already been added to the list, so the function will panic if the name already exists.

func AddValidator

func AddValidator(name string, v ValidationFunction)

AddValidator adds the given validator function to the list of validators

The backed storage containing the list of validators is not thread safe and so this function must be called once during package or application initialization.

You cannot replace validator functions that have already been added to the list, so the function will panic if the name already exists.

func CopyOptions

func CopyOptions(opts *ValidationOptions)

CopyOptions CopyOptions Copies the default global options into the specified destination. Useful when you want to have localized validation options

func IsAfterToday added in v0.0.2

func IsAfterToday(ctx *ValidationContext) bool

IsAfterToday tests whether the given date is after today.

If the time layout is not specified, '2006-01-02' will be used

func IsAlphaNumeric

func IsAlphaNumeric(ctx *ValidationContext) bool

IsAlphaNumeric verifies that the given string is alphanumeric

func IsBeforeToday added in v0.0.2

func IsBeforeToday(ctx *ValidationContext) bool

IsBeforeToday tests whether the given date is before today.

If the time layout is not specified, '2006-01-02' will be used

func IsEmail

func IsEmail(ctx *ValidationContext) bool

IsEmail tests if the input value matches an email format.

The validation rules used here do not conform to RFC and only allow only a few latin character set values. Therefore this function could be considered as very strict.

func IsEnum

func IsEnum(ctx *ValidationContext) bool

IsEnum tests if the input value matches any of the values passed in the arguments

func IsMax

func IsMax(ctx *ValidationContext) bool

IsMax tests if the given input (string, integer, list) contains at least the given number of elements

func IsMin

func IsMin(ctx *ValidationContext) bool

IsMin tests if the given input (string, integer, list) contains at least the given number of elements

func IsNotToday added in v0.0.2

func IsNotToday(ctx *ValidationContext) bool

IsNotToday tests whether the given date is not today.

If the time layout is not specified, '2006-01-02' will be used

func IsOrAfterToday added in v0.0.2

func IsOrAfterToday(ctx *ValidationContext) bool

IsOrAfterToday tests whether the given date is today or after today.

If the time layout is not specified, '2006-01-02' will be used

func IsOrBeforeToday added in v0.0.2

func IsOrBeforeToday(ctx *ValidationContext) bool

IsBeforeToday tests whether the given date is today or before today.

If the time layout is not specified, '2006-01-02' will be used

func IsRequired

func IsRequired(ctx *ValidationContext) bool

IsRequired check if the required field has values.

For literal values, the function always returns true because the values are present and can subsequnetly be validated appropriately.

For pointer types, the function will return false if the pointer is null or true if the pointer is not null

func IsToday added in v0.0.2

func IsToday(ctx *ValidationContext) bool

IsToday tests whether the given date is today.

If the time layout is not specified, '2006-01-02' will be used

func IsUuid1

func IsUuid1(ctx *ValidationContext) bool

func IsUuid2

func IsUuid2(ctx *ValidationContext) bool

func IsUuid3

func IsUuid3(ctx *ValidationContext) bool

func IsUuid4

func IsUuid4(ctx *ValidationContext) bool

func NullIfEmpty added in v0.0.4

func NullIfEmpty(ctx *ValidationContext) reflect.Value

NullIfEmpty Sets the given string pointer's value to null if the string is empty

func SetupOptions

func SetupOptions(configCallback func(*ValidationOptions))

SetupOptions SetupOptions allows you to configure the global validation options.

func Trim

func Trim(ctx *ValidationContext) reflect.Value

Types

type Comparator added in v0.0.2

type Comparator string
const (
	EQUALS                Comparator = "="
	NOT_EQUAL             Comparator = "!="
	LESS_THAN             Comparator = "<"
	GREATER_THAN          Comparator = ">"
	LESS_THAN_OR_EQUAL    Comparator = "<="
	GREATER_THAN_OR_EQUAL Comparator = ">="
)

func (Comparator) NumericDescription added in v0.0.2

func (c Comparator) NumericDescription() string

func (Comparator) TemporalDescription added in v0.0.2

func (c Comparator) TemporalDescription() string

type ComparatorDescription added in v0.0.2

type ComparatorDescription byte
const (
	NUMERICAL ComparatorDescription = 0
	TEMPORAL  ComparatorDescription = 1
)

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

func (FieldError) Error

func (e FieldError) Error() string

type FilterFunction

type FilterFunction func(ctx *ValidationContext) reflect.Value

FilterFunction FilterFunction is used to manipulate input values. This function may manipulate the value in place or return a completely new value.

However, the contract is that they must always return a value depending on the input value and logic contained therein.

type Stack

type Stack []any

func (*Stack) IsEmpty

func (s *Stack) IsEmpty() bool

func (*Stack) Peek

func (s *Stack) Peek() any

func (*Stack) Pop

func (s *Stack) Pop() any

func (*Stack) Push

func (s *Stack) Push(item any)

type ValidationContext

type ValidationContext struct {

	// The resolved type of the input value
	ValueType reflect.Type

	// If the input value is a pointer
	IsPointer bool

	// If the input value is a pointer and the point is null
	IsNull bool

	// Validation options
	Options *ValidationOptions

	// Arguments passed to the validation or filter function
	Args []string

	// Containst the validation error message
	ErrorMessage string

	// An error that may have occurred during validation
	AdditionalError error
	// contains filtered or unexported fields
}

func (ValidationContext) ArgCount

func (vc ValidationContext) ArgCount() int

func (ValidationContext) GetValue

func (vc ValidationContext) GetValue() reflect.Value

GetValue GetValue Returns the underlying value, resolving pointers if necessary

func (ValidationContext) IsValueOfKind

func (vc ValidationContext) IsValueOfKind(kind ...reflect.Kind) bool

func (*ValidationContext) IsValueOfType added in v0.0.2

func (vc *ValidationContext) IsValueOfType(i interface{}) bool

func (*ValidationContext) MustGetIntArg

func (vc *ValidationContext) MustGetIntArg(position int) int64

func (*ValidationContext) MustGetUintArg

func (vc *ValidationContext) MustGetUintArg(position int) uint64

func (*ValidationContext) ValueMustBeOfKind

func (vc *ValidationContext) ValueMustBeOfKind(kind ...reflect.Kind)

ValueMustBeOfKind ValueMustBeOfKind tests if the resolved kind of the input value matches any of the given kinds.

If there is no match, the function panics.

type ValidationError

type ValidationError struct {
	ErrorDelegate error
	Message       string
}

func (ValidationError) Error

func (e ValidationError) Error() string

type ValidationFlag added in v0.0.4

type ValidationFlag string

Flags used by the validation engine before calling validations and filters.

Flags will alter behavior of the validator towards each field being evaluated.

const (
	// If a value contains zero value, allow the value to pass through by skipping
	// validation since there's nothing to validate or filter.
	AllowZero ValidationFlag = "allow_zero"
)

type ValidationFunction

type ValidationFunction func(ctx *ValidationContext) bool

ValidationFunction ValidationFunction is used to validate input. Validator functions return a boolean indicating whether the input is valid or not.

type ValidationOptions

type ValidationOptions struct {
	// FilterTagName specifies the tag to use when looking up filter functions
	//
	// default: 'filter'
	FilterTagName string

	// TriggerTagName specifies the tag to use when looking up activation triggers.
	//
	// Activation triggers allow evaluating fields selectively. The default activation trigger is 'all'.
	//
	// The following example shows a struct that will have the '.Age' field evaluated everytime the struct is validate
	// and '.Id' only when updating.
	//
	//	type UserRequest struct {
	//		Id  int `validator:"min(100)" trigger:"update"`
	//		Age int `validator:"min(10)" trigger:"all"`
	//	}
	//
	// default: 'trigger'
	TriggerTagName string

	// ValidatorTagName specifies the tag to use when looking up validation functions
	//
	// default: 'validator'
	ValidatorTagName string

	// MessageTagName specifies the tag to use when looking up error message template
	//
	// default: 'message'
	MessageTagName string

	// LabelTagName specifies the tag to use when looking up the fields label
	//
	// default: 'label'
	LabelTagName string

	// StringAutoTrim specifies whether to automatically trim all strings
	//
	// default: false
	StringAutoTrim bool

	// StopOnFirstError specifies whether to stop validation upon encountering the first validation error
	//
	// default: false
	StopOnFirstError bool

	// ExposeValidatorNames specifies whether to expose validator function names in default error messages
	// when neither a validator nor a struct tag has specified an error message.
	//
	// Exposing validator names can provide technically meaningful error messages but may not be suitable for
	// client side presentation.
	//
	// default: false
	ExposeValidatorNames bool

	// NoPanicOnFunctionConflict specifies whether or not to panic upon encountering an existing validation or filter function
	// when adding custom validators and filters.
	//
	// default: false
	NoPanicOnFunctionConflict bool

	// ExposeEnumValues specifies whether to list all enum values in the default error message.
	//
	// default: false
	ExposeEnumValues bool

	// FlagTagName specifies the name of tag to use when looking up flags
	//
	// default: 'flags'
	FlagTagName string
}

type ValidationResult

type ValidationResult struct {

	// Error the top level error summarizing what the hell happened. May not necessarily come from validating the passed struct
	//
	Error *ValidationError
	//
	// FieldErrors FieldErrors struct field validation errors
	FieldErrors []FieldError
	// contains filtered or unexported fields
}

ValidationResult contains validation status, a general error, and field errors

func Validate

func Validate(structPtr interface{}, trigger ...string) (res *ValidationResult)

Validate validates the given struct

Parameters

structPtr : Pointer to a struct

trigger : Activation trigger - Specifies a unique value that will trigger activation of fields that have been taggeed with the same value.

func (ValidationResult) IsValid

func (r ValidationResult) IsValid() bool

Jump to

Keyboard shortcuts

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