This commit updates the configuration to be yaml-based and updates the configuration to read in a yaml file.
88 lines
2.6 KiB
Go
88 lines
2.6 KiB
Go
package validator
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
)
|
|
|
|
const (
|
|
validateTag = "validate"
|
|
recurseTag = "recurse"
|
|
dfltTag = "default"
|
|
)
|
|
|
|
// Validate validates an object of type T, setting defaults where appropriate.
|
|
//
|
|
// T must be a struct type, when this is not the case, returns ErrNotAStruct.
|
|
// validators should contain a set of validators.
|
|
//
|
|
// Validate iterates over the fields and tags of those fields as follows:
|
|
// - If the 'validate' tag is not the empty string, read the appropriate validator from the map, and call the function.
|
|
// If the element in the validators map does not exist, returns an error that unwraps to type UnknownValidator.
|
|
// If the element in the validators map is not a validator, returns an error that unwraps to type NotAValidator.
|
|
// If the type of validator function does not match the field type, returns an error that unwraps to type IncompatibleValidator.
|
|
// - If the 'recurse' tag is not the empty string, recurse into the struct type by calling Validate on it.
|
|
// If the annotated field is not a struct, return an error.
|
|
//
|
|
// Any error is wrapped in a FieldError, indicating the field they occured in.
|
|
// Recursive validate calls may result in FieldError wraps.
|
|
// For a description of struct tags, see [reflect.StructTag].
|
|
func Validate[T any](data *T, validators map[string]any) error {
|
|
return validate(reflect.ValueOf(data).Elem(), validators)
|
|
}
|
|
|
|
// FieldError wraps an error to indicate which field it occured in.
|
|
type FieldError struct {
|
|
Field string
|
|
Err error
|
|
}
|
|
|
|
func (fe FieldError) Error() string {
|
|
return fmt.Sprintf("field %q: %s", fe.Field, fe.Err)
|
|
}
|
|
|
|
func (fe FieldError) Unwrap() error {
|
|
return fe.Err
|
|
}
|
|
|
|
var ErrNotAStruct = errors.New("validate called on non-struct type")
|
|
|
|
func validate(datum reflect.Value, validators Collection) error {
|
|
// make sure that we have a struct type
|
|
typ := datum.Type()
|
|
if typ.Kind() != reflect.Struct {
|
|
return ErrNotAStruct
|
|
}
|
|
|
|
fieldC := typ.NumField()
|
|
for i := 0; i < fieldC; i++ {
|
|
field := typ.Field(i)
|
|
|
|
// if the recurse tag is set, do the recursion!
|
|
if field.Tag.Get(recurseTag) != "" {
|
|
if err := validate(datum.FieldByName(field.Name), validators); err != nil {
|
|
return FieldError{Field: field.Name, Err: err}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// check if there is a validator associated with this tag
|
|
// and if not, skip it!
|
|
validator := field.Tag.Get(validateTag)
|
|
if validator == "" {
|
|
continue
|
|
}
|
|
|
|
// call the actual validator
|
|
if err := validators.Call(
|
|
validator,
|
|
datum.FieldByName(field.Name),
|
|
field.Tag.Get(dfltTag),
|
|
); err != nil {
|
|
return FieldError{Field: field.Name, Err: err}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|