Skip to content

Commit

Permalink
feat: return multiple errors on validation call.
Browse files Browse the repository at this point in the history
This commit introduces a new type: ValError and adjusts the
signature of Validator.Validate to return a slice of ValError
instead of a single error. This change comes with the expectation
that all validators should return the full list of errors that
a given validator detects. Error-free passes can return either
nil or an empty []ValError slice.
This adjustment starts with a basic "just supply the message",
but ValError comes with extra fields that we can iterate upon
to populate in the future.
Having the full list of errors for a given instance makes this
package far more useful, at the expense of forcing implementers
to understand a few extra things. Meh. Worth it.

closes #15
  • Loading branch information
b5 committed Jan 20, 2018
1 parent b6ed67c commit 00b42a8
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 113 deletions.
28 changes: 20 additions & 8 deletions keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,22 +58,28 @@ func newTipe() Validator {
}

// Validate checks to see if input data satisfies the type constraint
func (t tipe) Validate(data interface{}) error {
func (t tipe) Validate(data interface{}) (errs []ValError) {
jt := DataType(data)
for _, typestr := range t.vals {
if jt == typestr || jt == "integer" && typestr == "number" {
return nil
}
}
if len(t.vals) == 1 {
return fmt.Errorf(`expected "%v" to be of type %s`, data, t.vals[0])
errs = append(errs, ValError{
Message: fmt.Sprintf(`expected "%v" to be of type %s`, data, t.vals[0]),
})
return
}

str := ""
for _, ts := range t.vals {
str += ts + ","
}
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
errs = append(errs, ValError{
Message: fmt.Sprintf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1]),
})
return
}

// JSONProp implements JSON property name indexing for tipe
Expand Down Expand Up @@ -138,13 +144,15 @@ func (e enum) String() string {
}

// Validate implements the Validator interface for enum
func (e enum) Validate(data interface{}) error {
func (e enum) Validate(data interface{}) []ValError {
for _, v := range e {
if err := v.Validate(data); err == nil {
return nil
}
}
return fmt.Errorf("expected %s to be one of %s", data)
return []ValError{
{Message: fmt.Sprintf("expected %s to be one of %s", data)},
}
}

// JSONProp implements JSON property name indexing for enum
Expand Down Expand Up @@ -178,14 +186,18 @@ func newKonst() Validator {
}

// Validate implements the validate interface for konst
func (c konst) Validate(data interface{}) error {
func (c konst) Validate(data interface{}) []ValError {
var con interface{}
if err := json.Unmarshal(c, &con); err != nil {
return err
return []ValError{
{Message: err.Error()},
}
}

if !reflect.DeepEqual(con, data) {
return fmt.Errorf(`%s must equal %s`, string(c), data)
return []ValError{
{Message: fmt.Sprintf(`%s must equal %s`, string(c), data)},
}
}
return nil
}
Expand Down
44 changes: 26 additions & 18 deletions keywords_arrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,19 @@ func newItems() Validator {
}

// Validate implements the Validator interface for items
func (it items) Validate(data interface{}) error {
func (it items) Validate(data interface{}) []ValError {
if arr, ok := data.([]interface{}); ok {
if it.single {
for i, elem := range arr {
if err := it.Schemas[0].Validate(elem); err != nil {
return fmt.Errorf("element %d %s", i, err.Error())
for _, elem := range arr {
if ves := it.Schemas[0].Validate(elem); len(ves) > 0 {
return ves
}
}
} else {
for i, vs := range it.Schemas {
if i < len(arr) {
if err := vs.Validate(arr[i]); err != nil {
return fmt.Errorf("element %d %s", i, err.Error())
if ves := vs.Validate(arr[i]); len(ves) > 0 {
return ves
}
}
}
Expand Down Expand Up @@ -108,20 +108,20 @@ func newAdditionalItems() Validator {
}

// Validate implements the Validator interface for additionalItems
func (a *additionalItems) Validate(data interface{}) error {
func (a *additionalItems) Validate(data interface{}) (errs []ValError) {
if a.startIndex >= 0 {
if arr, ok := data.([]interface{}); ok {
for i, elem := range arr {
if i < a.startIndex {
continue
}
if err := a.Schema.Validate(elem); err != nil {
return fmt.Errorf("element %d: %s", i, err.Error())
if ves := a.Schema.Validate(elem); len(ves) > 0 {
errs = append(errs, ves...)
}
}
}
}
return nil
return
}

// JSONProp implements JSON property name indexing for additionalItems
Expand Down Expand Up @@ -158,10 +158,12 @@ func newMaxItems() Validator {
}

// Validate implements the Validator interface for maxItems
func (m maxItems) Validate(data interface{}) error {
func (m maxItems) Validate(data interface{}) []ValError {
if arr, ok := data.([]interface{}); ok {
if len(arr) > int(m) {
return fmt.Errorf("%d array items exceeds %d max", len(arr), m)
return []ValError{
{Message: fmt.Sprintf("%d array items exceeds %d max", len(arr), m)},
}
}
}
return nil
Expand All @@ -177,10 +179,12 @@ func newMinItems() Validator {
}

// Validate implements the Validator interface for minItems
func (m minItems) Validate(data interface{}) error {
func (m minItems) Validate(data interface{}) []ValError {
if arr, ok := data.([]interface{}); ok {
if len(arr) < int(m) {
return fmt.Errorf("%d array items below %d minimum", len(arr), m)
return []ValError{
{Message: fmt.Sprintf("%d array items below %d minimum", len(arr), m)},
}
}
}
return nil
Expand All @@ -197,13 +201,15 @@ func newUniqueItems() Validator {
}

// Validate implements the Validator interface for uniqueItems
func (u *uniqueItems) Validate(data interface{}) error {
func (u *uniqueItems) Validate(data interface{}) []ValError {
if arr, ok := data.([]interface{}); ok {
found := []interface{}{}
for _, elem := range arr {
for _, f := range found {
if reflect.DeepEqual(f, elem) {
return fmt.Errorf("arry must be unique: %v", arr)
return []ValError{
{Message: fmt.Sprintf("arry must be unique: %v", arr)},
}
}
}
found = append(found, elem)
Expand All @@ -221,15 +227,17 @@ func newContains() Validator {
}

// Validate implements the Validator interface for contains
func (c *contains) Validate(data interface{}) error {
func (c *contains) Validate(data interface{}) []ValError {
v := Schema(*c)
if arr, ok := data.([]interface{}); ok {
for _, elem := range arr {
if err := v.Validate(elem); err == nil {
return nil
}
}
return fmt.Errorf("expected %v to contain at least one of: %s", data, c)
return []ValError{
{Message: fmt.Sprintf("expected %v to contain at least one of: %s", data, c)},
}
}
return nil
}
Expand Down
32 changes: 20 additions & 12 deletions keywords_booleans.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ func newAllOf() Validator {
}

// Validate implements the validator interface for allOf
func (a allOf) Validate(data interface{}) error {
for i, sch := range a {
if err := sch.Validate(data); err != nil {
return fmt.Errorf("allOf element %d error: %s", i, err.Error())
func (a allOf) Validate(data interface{}) (errs []ValError) {
for _, sch := range a {
if ves := sch.Validate(data); len(ves) > 0 {
errs = append(errs, ves...)
}
}
return nil
return
}

// JSONProp implements JSON property name indexing for allOf
Expand Down Expand Up @@ -55,13 +55,15 @@ func newAnyOf() Validator {
}

// Validate implements the validator interface for anyOf
func (a anyOf) Validate(data interface{}) error {
func (a anyOf) Validate(data interface{}) []ValError {
for _, sch := range a {
if err := sch.Validate(data); err == nil {
return nil
}
}
return fmt.Errorf("value did not match any specified anyOf schemas: %v", data)
return []ValError{
{Message: fmt.Sprintf("value did not match any specified anyOf schemas: %v", data)},
}
}

// JSONProp implements JSON property name indexing for anyOf
Expand Down Expand Up @@ -94,18 +96,22 @@ func newOneOf() Validator {
}

// Validate implements the validator interface for oneOf
func (o oneOf) Validate(data interface{}) error {
func (o oneOf) Validate(data interface{}) []ValError {
matched := false
for _, sch := range o {
if err := sch.Validate(data); err == nil {
if matched {
return fmt.Errorf("value matched more than one specified oneOf schemas")
return []ValError{
{Message: fmt.Sprintf("value matched more than one specified oneOf schemas")},
}
}
matched = true
}
}
if !matched {
return fmt.Errorf("value did not match any of the specified oneOf schemas")
return []ValError{
{Message: fmt.Sprintf("value did not match any of the specified oneOf schemas")},
}
}
return nil
}
Expand Down Expand Up @@ -141,11 +147,13 @@ func newNot() Validator {
}

// Validate implements the validator interface for not
func (n *not) Validate(data interface{}) error {
func (n *not) Validate(data interface{}) []ValError {
sch := Schema(*n)
if sch.Validate(data) == nil {
// TODO - make this error actually make sense
return fmt.Errorf("not clause")
return []ValError{
{Message: fmt.Sprintf("not clause")},
}
}
return nil
}
Expand Down
6 changes: 3 additions & 3 deletions keywords_conditionals.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func newIif() Validator {
}

// Validate implements the Validator interface for iif
func (i *iif) Validate(data interface{}) error {
func (i *iif) Validate(data interface{}) []ValError {
if err := i.Schema.Validate(data); err == nil {
if i.then != nil {
s := Schema(*i.then)
Expand Down Expand Up @@ -71,7 +71,7 @@ func newThen() Validator {
}

// Validate implements the Validator interface for then
func (t *then) Validate(data interface{}) error {
func (t *then) Validate(data interface{}) []ValError {
return nil
}

Expand Down Expand Up @@ -110,7 +110,7 @@ func newEls() Validator {
}

// Validate implements the Validator interface for els
func (e *els) Validate(data interface{}) error {
func (e *els) Validate(data interface{}) []ValError {
return nil
}

Expand Down
30 changes: 20 additions & 10 deletions keywords_numeric.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ func newMultipleOf() Validator {
}

// Validate implements the Validator interface for multipleOf
func (m multipleOf) Validate(data interface{}) error {
func (m multipleOf) Validate(data interface{}) []ValError {
if num, ok := data.(float64); ok {
div := num / float64(m)
if float64(int(div)) != div {
return fmt.Errorf("%f must be a multiple of %f", num, m)
return []ValError{
{Message: fmt.Sprintf("%f must be a multiple of %f", num, m)},
}
}
}
return nil
Expand All @@ -34,10 +36,12 @@ func newMaximum() Validator {
}

// Validate implements the Validator interface for maximum
func (m maximum) Validate(data interface{}) error {
func (m maximum) Validate(data interface{}) []ValError {
if num, ok := data.(float64); ok {
if num > float64(m) {
return fmt.Errorf("%f must be less than or equal to %f", num, m)
return []ValError{
{Message: fmt.Sprintf("%f must be less than or equal to %f", num, m)},
}
}
}
return nil
Expand All @@ -53,10 +57,12 @@ func newExclusiveMaximum() Validator {
}

// Validate implements the Validator interface for exclusiveMaximum
func (m exclusiveMaximum) Validate(data interface{}) error {
func (m exclusiveMaximum) Validate(data interface{}) []ValError {
if num, ok := data.(float64); ok {
if num >= float64(m) {
return fmt.Errorf("%f must be less than %f", num, m)
return []ValError{
{Message: fmt.Sprintf("%f must be less than %f", num, m)},
}
}
}
return nil
Expand All @@ -71,10 +77,12 @@ func newMinimum() Validator {
}

// Validate implements the Validator interface for minimum
func (m minimum) Validate(data interface{}) error {
func (m minimum) Validate(data interface{}) []ValError {
if num, ok := data.(float64); ok {
if num < float64(m) {
return fmt.Errorf("%f must be greater than or equal to %f", num, m)
return []ValError{
{Message: fmt.Sprintf("%f must be greater than or equal to %f", num, m)},
}
}
}
return nil
Expand All @@ -89,10 +97,12 @@ func newExclusiveMinimum() Validator {
}

// Validate implements the Validator interface for exclusiveMinimum
func (m exclusiveMinimum) Validate(data interface{}) error {
func (m exclusiveMinimum) Validate(data interface{}) []ValError {
if num, ok := data.(float64); ok {
if num <= float64(m) {
return fmt.Errorf("%f must be greater than %f", num, m)
return []ValError{
{Message: fmt.Sprintf("%f must be greater than %f", num, m)},
}
}
}
return nil
Expand Down
Loading

0 comments on commit 00b42a8

Please sign in to comment.