authelia/internal/configuration/schema/validator.go

164 lines
3.7 KiB
Go

package schema
import (
"fmt"
"reflect"
"github.com/Workiva/go-datastructures/queue"
)
// ErrorContainer represents a container where we can add errors and retrieve them.
type ErrorContainer interface {
Push(err error)
PushWarning(err error)
HasErrors() bool
HasWarnings() bool
Errors() []error
Warnings() []error
}
// Validator represents the validator interface.
type Validator struct {
errors map[string][]error
}
// NewValidator create a validator.
func NewValidator() *Validator {
validator := new(Validator)
validator.errors = make(map[string][]error)
return validator
}
// QueueItem an item representing a struct field and its path.
type QueueItem struct {
value reflect.Value
path string
}
func (v *Validator) validateOne(item QueueItem, q *queue.Queue) error { //nolint:unparam
if item.value.Type().Kind() == reflect.Ptr {
if item.value.IsNil() {
return nil
}
elem := item.value.Elem()
q.Put(QueueItem{ //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
value: elem,
path: item.path,
})
} else if item.value.Kind() == reflect.Struct {
numFields := item.value.Type().NumField()
validateFn := item.value.Addr().MethodByName("Validate")
if validateFn.IsValid() {
structValidator := NewStructValidator()
validateFn.Call([]reflect.Value{reflect.ValueOf(structValidator)})
v.errors[item.path] = structValidator.Errors()
}
for i := 0; i < numFields; i++ {
field := item.value.Type().Field(i)
value := item.value.Field(i)
q.Put(QueueItem{ //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
value: value,
path: item.path + "." + field.Name,
})
}
}
return nil
}
// Validate validate a struct.
func (v *Validator) Validate(s interface{}) error {
q := queue.New(40)
q.Put(QueueItem{value: reflect.ValueOf(s), path: "root"}) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
for !q.Empty() {
val, err := q.Get(1)
if err != nil {
return err
}
item, ok := val[0].(QueueItem)
if !ok {
return fmt.Errorf("Cannot convert item into QueueItem")
}
v.validateOne(item, q) //nolint:errcheck // TODO: Legacy code, consider refactoring time permitting.
}
return nil
}
// PrintErrors display the errors thrown during validation.
func (v *Validator) PrintErrors() {
for path, errs := range v.errors {
fmt.Printf("Errors at %s:\n", path)
for _, err := range errs {
fmt.Printf("--> %s\n", err)
}
}
}
// Errors return the errors thrown during validation.
func (v *Validator) Errors() map[string][]error {
return v.errors
}
// StructValidator is a validator for structs.
type StructValidator struct {
errors []error
warnings []error
}
// NewStructValidator is a constructor of struct validator.
func NewStructValidator() *StructValidator {
val := new(StructValidator)
val.errors = make([]error, 0)
val.warnings = make([]error, 0)
return val
}
// Push an error to the validator.
func (v *StructValidator) Push(err error) {
v.errors = append(v.errors, err)
}
// PushWarning error to the validator.
func (v *StructValidator) PushWarning(err error) {
v.warnings = append(v.warnings, err)
}
// HasErrors checks whether the validator contains errors.
func (v *StructValidator) HasErrors() bool {
return len(v.errors) > 0
}
// HasWarnings checks whether the validator contains warning errors.
func (v *StructValidator) HasWarnings() bool {
return len(v.warnings) > 0
}
// Errors returns the errors.
func (v *StructValidator) Errors() []error {
return v.errors
}
// Warnings returns the warnings.
func (v *StructValidator) Warnings() []error {
return v.warnings
}
// Clear errors and warnings.
func (v *StructValidator) Clear() {
v.errors = []error{}
v.warnings = []error{}
}