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) HasErrors() bool Errors() []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{ 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{ 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"}) 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) } 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 } // NewStructValidator is a constructor of struct validator func NewStructValidator() *StructValidator { val := new(StructValidator) val.errors = make([]error, 0) return val } // Push an error in the validator. func (v *StructValidator) Push(err error) { v.errors = append(v.errors, err) } // HasErrors checks whether the validator contains errors. func (v *StructValidator) HasErrors() bool { return len(v.errors) > 0 } // Errors returns the errors. func (v *StructValidator) Errors() []error { return v.errors } // Clear errors func (v *StructValidator) Clear() { v.errors = []error{} }