This commit updates the configuration to be yaml-based and updates the configuration to read in a yaml file.
131 lines
4 KiB
Go
131 lines
4 KiB
Go
package validator
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/tkw1536/goprogram/lib/reflectx"
|
|
)
|
|
|
|
// Collection represents a set of validators.
|
|
// The zero value is not ready to use; it should be created using make().
|
|
//
|
|
// A validator is a non-nil function with signature func(value *F, dflt string) error.
|
|
// Here F is the type of a value of a field.
|
|
// The value is the initialized value to be validated.
|
|
// The validator may perform abitrary normalization on the value.
|
|
// dflt is the default value (read from the 'default' tag).
|
|
// error should be an appropriate error that occured.
|
|
//
|
|
// A validator function is applied by calling it.
|
|
type Collection map[string]any
|
|
|
|
// Add adds a Validator to the provided collection of validators.
|
|
// Any previously validator of the same name is overwritten.
|
|
func Add[F any](coll Collection, name string, validator func(value *F, dflt string) error) {
|
|
coll[name] = validator
|
|
}
|
|
|
|
// AddSlice adds a Validator to the provided collection of validators that validates a slice of the given type. The default is seperated by seperator.
|
|
func AddSlice[F any](coll Collection, name string, sep string, validator func(value *F, dflt string) error) {
|
|
Add(coll, name, func(value *[]F, dflt string) error {
|
|
// some value is set, so we do not need to set the default!
|
|
if *value != nil {
|
|
for i := range *value {
|
|
if err := validator(&(*value)[i], ""); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// no default provided => set if to an empty slice
|
|
if dflt == "" {
|
|
*value = make([]F, 0)
|
|
return nil
|
|
}
|
|
|
|
// some default provided => iterate over the underlying validator
|
|
dflts := strings.Split(dflt, sep)
|
|
*value = make([]F, len(dflts))
|
|
for i := range *value {
|
|
if err := validator(&(*value)[i], dflts[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
var (
|
|
errTyp = reflectx.TypeOf[error]()
|
|
strTyp = reflectx.TypeOf[string]()
|
|
)
|
|
|
|
// UnknownValidator is an error returned from Validate if a validator does not exist
|
|
type UnknownValidator string
|
|
|
|
func (uv UnknownValidator) Error() string {
|
|
return fmt.Sprintf("unknown validator %q", string(uv))
|
|
}
|
|
|
|
// NotAValidator is an error returned from Validate if an entry in the validators map is not a validator
|
|
type NotAValidator string
|
|
|
|
func (nv NotAValidator) Error() string {
|
|
return fmt.Sprintf("entry %q in validators is not a valiator", string(nv))
|
|
}
|
|
|
|
// IncompatibleValidator is returned when a validator in the validators map is incompatible
|
|
type IncompatibleValidator struct {
|
|
Validator string
|
|
GotType reflect.Type
|
|
ExpectedType reflect.Type
|
|
}
|
|
|
|
func (iv IncompatibleValidator) Error() string {
|
|
return fmt.Sprintf("validator %q: got type %s, expected type %s", iv.Validator, iv.GotType, iv.ExpectedType)
|
|
}
|
|
|
|
// Call calls the validator with the given name, on the given value, and with the provided default.
|
|
// See documentation of [Validate] for details.
|
|
func (coll Collection) Call(name string, field reflect.Value, dflt string) error {
|
|
validator, ok := coll[name]
|
|
if !ok {
|
|
return UnknownValidator(name)
|
|
}
|
|
|
|
// get the type of the validator
|
|
vFunc := reflect.ValueOf(validator)
|
|
vTyp := vFunc.Type()
|
|
|
|
// ensure that vTyp is of type func(*F,string) error
|
|
// where T is the type of the field
|
|
//
|
|
// - the first if assumes checks for some type F
|
|
// - the second if checks if the F is the right one
|
|
if validator == nil || vTyp.Kind() != reflect.Func || // func
|
|
vTyp.NumIn() != 2 || vTyp.In(0).Kind() != reflect.Pointer || vTyp.In(1) != strTyp || // (*F,string)
|
|
vTyp.NumOut() != 1 || vTyp.Out(0) != errTyp { // error
|
|
return NotAValidator(name)
|
|
}
|
|
if vTyp.In(0).Elem() != field.Type() { // the correct *F
|
|
return IncompatibleValidator{
|
|
Validator: name,
|
|
GotType: vTyp.In(0).Elem(),
|
|
ExpectedType: field.Type(),
|
|
}
|
|
}
|
|
|
|
// call the validator function, and return an error
|
|
results := vFunc.Call([]reflect.Value{field.Addr(), reflect.ValueOf(dflt)})
|
|
|
|
// turn the result into an error
|
|
// NOTE: We can't just .(error) here because that panic()s on err == nil
|
|
err := results[0].Interface()
|
|
if err, ok := err.(error); ok {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|