Skip to content

Commit

Permalink
feat(Validate,ValError): overhaul and upgrade error collection & repo…
Browse files Browse the repository at this point in the history
…rting

Merge pull request #27 from qri-io/multi_errors
  • Loading branch information
b5 committed Mar 9, 2018
2 parents 7b9a1f9 + 66b03e6 commit 265a1d7
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 229 deletions.
50 changes: 27 additions & 23 deletions keywords.go
Expand Up @@ -49,6 +49,7 @@ func DataType(data interface{}) string {
// "integer" which matches any number with a zero fractional part.
// An instance validates if and only if the instance is in any of the sets listed for this keyword.
type Type struct {
BaseValidator
strVal bool // set to true if Type decoded from a string, false if an array
vals []string
}
Expand All @@ -59,28 +60,24 @@ func NewType() Validator {
}

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

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

t.AddError(errs, propPath, data, fmt.Sprintf(`type should be one of: %s`, str[:len(str)-1]))
}

// JSONProp implements JSON property name indexing for Type
Expand Down Expand Up @@ -145,16 +142,22 @@ func (e Enum) String() string {
return str[:len(str)-2] + "]"
}

// Path gives a jsonpointer path to the validator
func (e Enum) Path() string {
return ""
}

// Validate implements the Validator interface for Enum
func (e Enum) Validate(data interface{}) []ValError {
func (e Enum) Validate(propPath string, data interface{}, errs *[]ValError) {
for _, v := range e {
if err := v.Validate(data); err == nil {
return nil
test := &[]ValError{}
v.Validate(propPath, data, test)
if len(*test) == 0 {
return
}
}
return []ValError{
{Message: fmt.Sprintf("expected %s to be one of %s", data, e.String())},
}

AddError(errs, propPath, data, fmt.Sprintf("should be one of %s", e.String()))
}

// JSONProp implements JSON property name indexing for Enum
Expand Down Expand Up @@ -188,21 +191,22 @@ func NewConst() Validator {
return &Const{}
}

// Path gives a jsonpointer path to the validator
func (c Const) Path() string {
return ""
}

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

if !reflect.DeepEqual(con, data) {
return []ValError{
{Message: fmt.Sprintf(`%s must equal %s`, string(c), data)},
}
AddError(errs, propPath, data, fmt.Sprintf(`must equal %s`, InvalidValueString(data)))
}
return nil
}

// JSONProp implements JSON property name indexing for Const
Expand Down
74 changes: 37 additions & 37 deletions keywords_arrays.go
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"reflect"
"strconv"

"github.com/qri-io/jsonpointer"
)

// Items MUST be either a valid JSON Schema or an array of valid JSON Schemas.
Expand All @@ -16,7 +18,7 @@ import (
// against the schema at the same position, if any.
// * Omitting this keyword has the same behavior as an empty schema.
type Items struct {
// need to track weather user specficied a singl object or arry
// need to track weather user specficied a single object or arry
// b/c it affects AdditionalItems validation semantics
single bool
Schemas []*Schema
Expand All @@ -28,25 +30,27 @@ func NewItems() Validator {
}

// Validate implements the Validator interface for Items
func (it Items) Validate(data interface{}) []ValError {
func (it Items) Validate(propPath string, data interface{}, errs *[]ValError) {
jp, err := jsonpointer.Parse(propPath)
if err != nil {
AddError(errs, propPath, nil, fmt.Sprintf("invalid property path: %s", err.Error()))
}

if arr, ok := data.([]interface{}); ok {
if it.single {
for _, elem := range arr {
if ves := it.Schemas[0].Validate(elem); len(ves) > 0 {
return ves
}
for i, elem := range arr {
d, _ := jp.Descendant(strconv.Itoa(i))
it.Schemas[0].Validate(d.String(), elem, errs)
}
} else {
for i, vs := range it.Schemas {
if i < len(arr) {
if ves := vs.Validate(arr[i]); len(ves) > 0 {
return ves
}
d, _ := jp.Descendant(strconv.Itoa(i))
vs.Validate(d.String(), arr[i], errs)
}
}
}
}
return nil
}

// JSONProp implements JSON property name indexing for Items
Expand Down Expand Up @@ -110,20 +114,23 @@ func NewAdditionalItems() Validator {
}

// Validate implements the Validator interface for AdditionalItems
func (a *AdditionalItems) Validate(data interface{}) (errs []ValError) {
func (a *AdditionalItems) Validate(propPath string, data interface{}, errs *[]ValError) {
jp, err := jsonpointer.Parse(propPath)
if err != nil {
AddError(errs, propPath, nil, fmt.Sprintf("invalid property path: %s", err.Error()))
}

if a.startIndex >= 0 {
if arr, ok := data.([]interface{}); ok {
for i, elem := range arr {
if i < a.startIndex {
continue
}
if ves := a.Schema.Validate(elem); len(ves) > 0 {
errs = append(errs, ves...)
}
d, _ := jp.Descendant(strconv.Itoa(i))
a.Schema.Validate(d.String(), elem, errs)
}
}
}
return
}

// JSONProp implements JSON property name indexing for AdditionalItems
Expand Down Expand Up @@ -161,15 +168,13 @@ func NewMaxItems() Validator {
}

// Validate implements the Validator interface for MaxItems
func (m MaxItems) Validate(data interface{}) []ValError {
func (m MaxItems) Validate(propPath string, data interface{}, errs *[]ValError) {
if arr, ok := data.([]interface{}); ok {
if len(arr) > int(m) {
return []ValError{
{Message: fmt.Sprintf("%d array Items exceeds %d max", len(arr), m)},
}
AddError(errs, propPath, data, fmt.Sprintf("array length %d exceeds %d max", len(arr), m))
return
}
}
return nil
}

// MinItems MUST be a non-negative integer.
Expand All @@ -183,15 +188,13 @@ func NewMinItems() Validator {
}

// Validate implements the Validator interface for MinItems
func (m MinItems) Validate(data interface{}) []ValError {
func (m MinItems) Validate(propPath string, data interface{}, errs *[]ValError) {
if arr, ok := data.([]interface{}); ok {
if len(arr) < int(m) {
return []ValError{
{Message: fmt.Sprintf("%d array Items below %d minimum", len(arr), m)},
}
AddError(errs, propPath, data, fmt.Sprintf("array length %d below %d minimum items", len(arr), m))
return
}
}
return nil
}

// UniqueItems requires array instance elements be unique
Expand All @@ -206,21 +209,19 @@ func NewUniqueItems() Validator {
}

// Validate implements the Validator interface for UniqueItems
func (u *UniqueItems) Validate(data interface{}) []ValError {
func (u *UniqueItems) Validate(propPath string, data interface{}, errs *[]ValError) {
if arr, ok := data.([]interface{}); ok {
found := []interface{}{}
for _, elem := range arr {
for _, f := range found {
if reflect.DeepEqual(f, elem) {
return []ValError{
{Message: fmt.Sprintf("arry must be unique: %v", arr)},
}
AddError(errs, propPath, data, fmt.Sprintf("array items must be unique. duplicated entry: %v", elem))
return
}
}
found = append(found, elem)
}
}
return nil
}

// Contains validates that an array instance is valid against "Contains" if at
Expand All @@ -233,19 +234,18 @@ func NewContains() Validator {
}

// Validate implements the Validator interface for Contains
func (c *Contains) Validate(data interface{}) []ValError {
func (c *Contains) Validate(propPath string, data interface{}, errs *[]ValError) {
v := Schema(*c)
if arr, ok := data.([]interface{}); ok {
for _, elem := range arr {
if err := v.Validate(elem); err == nil {
return nil
test := &[]ValError{}
v.Validate(propPath, elem, test)
if len(*test) == 0 {
return
}
}
return []ValError{
{Message: fmt.Sprintf("expected %v to contain at least one of: %v", data, c)},
}
AddError(errs, propPath, data, fmt.Sprintf("must contain at least one of: %v", c))
}
return nil
}

// JSONProp implements JSON property name indexing for Contains
Expand Down
47 changes: 20 additions & 27 deletions keywords_booleans.go
Expand Up @@ -2,7 +2,6 @@ package jsonschema

import (
"encoding/json"
"fmt"
"strconv"
)

Expand All @@ -16,13 +15,10 @@ func NewAllOf() Validator {
}

// Validate implements the validator interface for AllOf
func (a AllOf) Validate(data interface{}) (errs []ValError) {
func (a AllOf) Validate(propPath string, data interface{}, errs *[]ValError) {
for _, sch := range a {
if ves := sch.Validate(data); len(ves) > 0 {
errs = append(errs, ves...)
}
sch.Validate(propPath, data, errs)
}
return
}

// JSONProp implements JSON property name indexing for AllOf
Expand Down Expand Up @@ -57,15 +53,15 @@ func NewAnyOf() Validator {
}

// Validate implements the validator interface for AnyOf
func (a AnyOf) Validate(data interface{}) []ValError {
func (a AnyOf) Validate(propPath string, data interface{}, errs *[]ValError) {
for _, sch := range a {
if err := sch.Validate(data); err == nil {
return nil
test := &[]ValError{}
sch.Validate(propPath, data, test)
if len(*test) == 0 {
return
}
}
return []ValError{
{Message: fmt.Sprintf("value did Not match any specified AnyOf schemas: %v", data)},
}
AddError(errs, propPath, data, "did Not match any specified AnyOf schemas")
}

// JSONProp implements JSON property name indexing for AnyOf
Expand Down Expand Up @@ -99,24 +95,22 @@ func NewOneOf() Validator {
}

// Validate implements the validator interface for OneOf
func (o OneOf) Validate(data interface{}) []ValError {
func (o OneOf) Validate(propPath string, data interface{}, errs *[]ValError) {
matched := false
for _, sch := range o {
if err := sch.Validate(data); err == nil {
test := &[]ValError{}
sch.Validate(propPath, data, test)
if len(*test) == 0 {
if matched {
return []ValError{
{Message: fmt.Sprintf("value matched more than one specified OneOf schemas")},
}
AddError(errs, propPath, data, "matched more than one specified OneOf schemas")
return
}
matched = true
}
}
if !matched {
return []ValError{
{Message: fmt.Sprintf("value did Not match any of the specified OneOf schemas")},
}
AddError(errs, propPath, data, "did not match any of the specified OneOf schemas")
}
return nil
}

// JSONProp implements JSON property name indexing for OneOf
Expand Down Expand Up @@ -151,15 +145,14 @@ func NewNot() Validator {
}

// Validate implements the validator interface for Not
func (n *Not) Validate(data interface{}) []ValError {
func (n *Not) Validate(propPath string, data interface{}, errs *[]ValError) {
sch := Schema(*n)
if sch.Validate(data) == nil {
test := &[]ValError{}
sch.Validate(propPath, data, test)
if len(*test) == 0 {
// TODO - make this error actually make sense
return []ValError{
{Message: fmt.Sprintf("Not clause")},
}
AddError(errs, propPath, data, "cannot match schema")
}
return nil
}

// JSONProp implements JSON property name indexing for Not
Expand Down

0 comments on commit 265a1d7

Please sign in to comment.