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{} }