From a88f4bb6b7a574ad36d716ece068e9d134bbf5a0 Mon Sep 17 00:00:00 2001 From: Arvydas Sidorenko Date: Sat, 31 Aug 2019 20:48:36 +0200 Subject: [PATCH] feat(*): implement higher level json types --- keywords.go | 74 ++--- keywords_arrays.go | 32 +-- keywords_booleans.go | 8 +- keywords_conditionals.go | 6 +- keywords_format.go | 38 +-- keywords_numeric.go | 87 ++++-- keywords_objects.go | 66 ++--- keywords_strings.go | 16 +- schema.go | 4 +- schema_test.go | 30 +-- types.go | 248 +++++++++++++++++ val_error.go | 4 +- validate.go | 6 +- validate_test.go | 568 ++++++++++++++++++++++++++++++++++++++- 14 files changed, 1002 insertions(+), 185 deletions(-) create mode 100644 types.go diff --git a/keywords.go b/keywords.go index f8467ad..162bab0 100644 --- a/keywords.go +++ b/keywords.go @@ -5,47 +5,8 @@ import ( "fmt" "reflect" "strconv" - "strings" ) -// primitiveTypes is a map of strings to check types against -var primitiveTypes = map[string]bool{ - "null": true, - "boolean": true, - "object": true, - "array": true, - "number": true, - "string": true, - "integer": true, -} - -// DataType gives the primitive json type of a standard json-decoded value, plus the special case -// "integer" for when numbers are whole -func DataType(data interface{}) string { - if data == nil { - return "null" - } - - switch reflect.TypeOf(data).Kind() { - case reflect.Bool: - return "boolean" - case reflect.Float64: - number := reflect.ValueOf(data).Float() - if float64(int(number)) == number { - return "integer" - } - return "number" - case reflect.String: - return "string" - case reflect.Array, reflect.Slice: - return "array" - case reflect.Map, reflect.Struct: - return "object" - default: - return "unknown" - } -} - // Type specifies one of the six json primitive types. // The value of this keyword MUST be either a string or an array. // If it is an array, elements of the array MUST be strings and MUST be unique. @@ -55,7 +16,7 @@ func DataType(data interface{}) string { type Type struct { BaseValidator strVal bool // set to true if Type decoded from a string, false if an array - vals []string + vals Ts } // NewType creates a new Type Validator @@ -68,14 +29,13 @@ func (t Type) String() string { if len(t.vals) == 0 { return "unknown" } - return strings.Join(t.vals, ",") + return t.vals.String() } // Validate checks to see if input data satisfies the type constraint -func (t Type) Validate(propPath string, data interface{}, errs *[]ValError) { - jt := DataType(data) +func (t Type) Validate(propPath string, data Val, errs *[]ValError) { for _, typestr := range t.vals { - if jt == typestr || jt == "integer" && typestr == "number" { + if data.Type() == typestr || data.Type() == Integer && typestr == Number { return } } @@ -86,7 +46,7 @@ func (t Type) Validate(propPath string, data interface{}, errs *[]ValError) { str := "" for _, ts := range t.vals { - str += ts + "," + str += string(ts) + "," } t.AddError(errs, propPath, data, fmt.Sprintf(`type should be one of: %s`, str[:len(str)-1])) @@ -108,9 +68,9 @@ func (t Type) JSONProp(name string) interface{} { func (t *Type) UnmarshalJSON(data []byte) error { var single string if err := json.Unmarshal(data, &single); err == nil { - *t = Type{strVal: true, vals: []string{single}} + *t = Type{strVal: true, vals: Ts{T(single)}} } else { - var set []string + var set Ts if err := json.Unmarshal(data, &set); err == nil { *t = Type{vals: set} } else { @@ -119,7 +79,7 @@ func (t *Type) UnmarshalJSON(data []byte) error { } for _, pr := range t.vals { - if !primitiveTypes[pr] { + if !mapT[pr] { return fmt.Errorf(`"%s" is not a valid type`, pr) } } @@ -160,7 +120,7 @@ func (e Enum) Path() string { } // Validate implements the Validator interface for Enum -func (e Enum) Validate(propPath string, data interface{}, errs *[]ValError) { +func (e Enum) Validate(propPath string, data Val, errs *[]ValError) { for _, v := range e { test := &[]ValError{} v.Validate(propPath, data, test) @@ -209,14 +169,26 @@ func (c Const) Path() string { } // Validate implements the validate interface for Const -func (c Const) Validate(propPath string, data interface{}, errs *[]ValError) { +func (c Const) Validate(propPath string, data Val, errs *[]ValError) { var con interface{} if err := json.Unmarshal(c, &con); err != nil { AddError(errs, propPath, data, err.Error()) return } - if !reflect.DeepEqual(con, data) { + // Normalize the input to match the constant type. + var val interface{} + b, err := json.Marshal(data.Raw()) + if err != nil { + AddError(errs, propPath, data, err.Error()) + return + } + if err := json.Unmarshal(b, &val); err != nil { + AddError(errs, propPath, data, err.Error()) + return + } + + if !reflect.DeepEqual(con, val) { AddError(errs, propPath, data, fmt.Sprintf(`must equal %s`, InvalidValueString(con))) } } diff --git a/keywords_arrays.go b/keywords_arrays.go index a372624..8f5b4ef 100644 --- a/keywords_arrays.go +++ b/keywords_arrays.go @@ -30,23 +30,23 @@ func NewItems() Validator { } // Validate implements the Validator interface for Items -func (it Items) Validate(propPath string, data interface{}, errs *[]ValError) { +func (it Items) Validate(propPath string, data Val, 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 arr, ok := data.(ArrayVal); ok { if it.single { for i, elem := range arr { d, _ := jp.Descendant(strconv.Itoa(i)) - it.Schemas[0].Validate(d.String(), elem, errs) + it.Schemas[0].Validate(d.String(), dataToVal(elem), errs) } } else { for i, vs := range it.Schemas { if i < len(arr) { d, _ := jp.Descendant(strconv.Itoa(i)) - vs.Validate(d.String(), arr[i], errs) + vs.Validate(d.String(), dataToVal(arr[i]), errs) } } } @@ -114,20 +114,20 @@ func NewAdditionalItems() Validator { } // Validate implements the Validator interface for AdditionalItems -func (a *AdditionalItems) Validate(propPath string, data interface{}, errs *[]ValError) { +func (a *AdditionalItems) Validate(propPath string, data Val, 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 { + if arr, ok := data.(ArrayVal); ok { for i, elem := range arr { if i < a.startIndex { continue } d, _ := jp.Descendant(strconv.Itoa(i)) - a.Schema.Validate(d.String(), elem, errs) + a.Schema.Validate(d.String(), dataToVal(elem), errs) } } } @@ -168,8 +168,8 @@ func NewMaxItems() Validator { } // Validate implements the Validator interface for MaxItems -func (m MaxItems) Validate(propPath string, data interface{}, errs *[]ValError) { - if arr, ok := data.([]interface{}); ok { +func (m MaxItems) Validate(propPath string, data Val, errs *[]ValError) { + if arr, ok := data.(ArrayVal); ok { if len(arr) > int(m) { AddError(errs, propPath, data, fmt.Sprintf("array length %d exceeds %d max", len(arr), m)) return @@ -188,8 +188,8 @@ func NewMinItems() Validator { } // Validate implements the Validator interface for MinItems -func (m MinItems) Validate(propPath string, data interface{}, errs *[]ValError) { - if arr, ok := data.([]interface{}); ok { +func (m MinItems) Validate(propPath string, data Val, errs *[]ValError) { + if arr, ok := data.(ArrayVal); ok { if len(arr) < int(m) { AddError(errs, propPath, data, fmt.Sprintf("array length %d below %d minimum items", len(arr), m)) return @@ -209,8 +209,8 @@ func NewUniqueItems() Validator { } // Validate implements the Validator interface for UniqueItems -func (u *UniqueItems) Validate(propPath string, data interface{}, errs *[]ValError) { - if arr, ok := data.([]interface{}); ok { +func (u *UniqueItems) Validate(propPath string, data Val, errs *[]ValError) { + if arr, ok := data.(ArrayVal); ok { found := []interface{}{} for _, elem := range arr { for _, f := range found { @@ -234,12 +234,12 @@ func NewContains() Validator { } // Validate implements the Validator interface for Contains -func (c *Contains) Validate(propPath string, data interface{}, errs *[]ValError) { +func (c *Contains) Validate(propPath string, data Val, errs *[]ValError) { v := Schema(*c) - if arr, ok := data.([]interface{}); ok { + if arr, ok := data.(ArrayVal); ok { for _, elem := range arr { test := &[]ValError{} - v.Validate(propPath, elem, test) + v.Validate(propPath, dataToVal(elem), test) if len(*test) == 0 { return } diff --git a/keywords_booleans.go b/keywords_booleans.go index 2713d21..6f35a36 100644 --- a/keywords_booleans.go +++ b/keywords_booleans.go @@ -15,7 +15,7 @@ func NewAllOf() Validator { } // Validate implements the validator interface for AllOf -func (a AllOf) Validate(propPath string, data interface{}, errs *[]ValError) { +func (a AllOf) Validate(propPath string, data Val, errs *[]ValError) { for _, sch := range a { sch.Validate(propPath, data, errs) } @@ -53,7 +53,7 @@ func NewAnyOf() Validator { } // Validate implements the validator interface for AnyOf -func (a AnyOf) Validate(propPath string, data interface{}, errs *[]ValError) { +func (a AnyOf) Validate(propPath string, data Val, errs *[]ValError) { for _, sch := range a { test := &[]ValError{} sch.Validate(propPath, data, test) @@ -95,7 +95,7 @@ func NewOneOf() Validator { } // Validate implements the validator interface for OneOf -func (o OneOf) Validate(propPath string, data interface{}, errs *[]ValError) { +func (o OneOf) Validate(propPath string, data Val, errs *[]ValError) { matched := false for _, sch := range o { test := &[]ValError{} @@ -145,7 +145,7 @@ func NewNot() Validator { } // Validate implements the validator interface for Not -func (n *Not) Validate(propPath string, data interface{}, errs *[]ValError) { +func (n *Not) Validate(propPath string, data Val, errs *[]ValError) { sch := Schema(*n) test := &[]ValError{} sch.Validate(propPath, data, test) diff --git a/keywords_conditionals.go b/keywords_conditionals.go index 042ff20..5a368ce 100644 --- a/keywords_conditionals.go +++ b/keywords_conditionals.go @@ -20,7 +20,7 @@ func NewIf() Validator { } // Validate implements the Validator interface for If -func (i *If) Validate(propPath string, data interface{}, errs *[]ValError) { +func (i *If) Validate(propPath string, data Val, errs *[]ValError) { test := &[]ValError{} i.Schema.Validate(propPath, data, test) if len(*test) == 0 { @@ -76,7 +76,7 @@ func NewThen() Validator { } // Validate implements the Validator interface for Then -func (t *Then) Validate(propPath string, data interface{}, errs *[]ValError) {} +func (t *Then) Validate(propPath string, data Val, errs *[]ValError) {} // JSONProp implements JSON property name indexing for Then func (t Then) JSONProp(name string) interface{} { @@ -114,7 +114,7 @@ func NewElse() Validator { } // Validate implements the Validator interface for Else -func (e *Else) Validate(propPath string, data interface{}, err *[]ValError) {} +func (e *Else) Validate(propPath string, data Val, err *[]ValError) {} // JSONProp implements JSON property name indexing for Else func (e Else) JSONProp(name string) interface{} { diff --git a/keywords_format.go b/keywords_format.go index 795f0b5..2204bfb 100644 --- a/keywords_format.go +++ b/keywords_format.go @@ -58,44 +58,44 @@ func NewFormat() Validator { } // Validate validates input against a keyword -func (f Format) Validate(propPath string, data interface{}, errs *[]ValError) { +func (f Format) Validate(propPath string, data Val, errs *[]ValError) { var err error - if str, ok := data.(string); ok { + if str, ok := data.(StringVal); ok { switch f { case "date-time": - err = isValidDateTime(str) + err = isValidDateTime(string(str)) case "date": - err = isValidDate(str) + err = isValidDate(string(str)) case "email": - err = isValidEmail(str) + err = isValidEmail(string(str)) case "hostname": - err = isValidHostname(str) + err = isValidHostname(string(str)) case "idn-email": - err = isValidIDNEmail(str) + err = isValidIDNEmail(string(str)) case "idn-hostname": - err = isValidIDNHostname(str) + err = isValidIDNHostname(string(str)) case "ipv4": - err = isValidIPv4(str) + err = isValidIPv4(string(str)) case "ipv6": - err = isValidIPv6(str) + err = isValidIPv6(string(str)) case "iri-reference": - err = isValidIriRef(str) + err = isValidIriRef(string(str)) case "iri": - err = isValidIri(str) + err = isValidIri(string(str)) case "json-pointer": - err = isValidJSONPointer(str) + err = isValidJSONPointer(string(str)) case "regex": - err = isValidRegex(str) + err = isValidRegex(string(str)) case "relative-json-pointer": - err = isValidRelJSONPointer(str) + err = isValidRelJSONPointer(string(str)) case "time": - err = isValidTime(str) + err = isValidTime(string(str)) case "uri-reference": - err = isValidURIRef(str) + err = isValidURIRef(string(str)) case "uri-template": - err = isValidURITemplate(str) + err = isValidURITemplate(string(str)) case "uri": - err = isValidURI(str) + err = isValidURI(string(str)) default: err = nil } diff --git a/keywords_numeric.go b/keywords_numeric.go index de08d3c..254fcc3 100644 --- a/keywords_numeric.go +++ b/keywords_numeric.go @@ -15,12 +15,19 @@ func NewMultipleOf() Validator { } // Validate implements the Validator interface for MultipleOf -func (m MultipleOf) Validate(propPath string, data interface{}, errs *[]ValError) { - if num, ok := data.(float64); ok { - div := num / float64(m) - if float64(int(div)) != div { - AddError(errs, propPath, data, fmt.Sprintf("must be a multiple of %f", m)) - } +func (m MultipleOf) Validate(propPath string, data Val, errs *[]ValError) { + var num float64 + if v, ok := data.(IntVal); ok { + num = float64(v.Value()) + } else if v, ok := data.(NumberVal); ok { + num = v.Value() + } else { + return + } + + div := num / float64(m) + if float64(int(div)) != div { + AddError(errs, propPath, data, fmt.Sprintf("must be a multiple of %f", m)) } } @@ -35,11 +42,18 @@ func NewMaximum() Validator { } // Validate implements the Validator interface for Maximum -func (m Maximum) Validate(propPath string, data interface{}, errs *[]ValError) { - if num, ok := data.(float64); ok { - if num > float64(m) { - AddError(errs, propPath, data, fmt.Sprintf("must be less than or equal to %f", m)) - } +func (m Maximum) Validate(propPath string, data Val, errs *[]ValError) { + var num float64 + if v, ok := data.(IntVal); ok { + num = float64(v.Value()) + } else if v, ok := data.(NumberVal); ok { + num = v.Value() + } else { + return + } + + if num > float64(m) { + AddError(errs, propPath, data, fmt.Sprintf("must be less than or equal to %f", m)) } } @@ -54,11 +68,18 @@ func NewExclusiveMaximum() Validator { } // Validate implements the Validator interface for ExclusiveMaximum -func (m ExclusiveMaximum) Validate(propPath string, data interface{}, errs *[]ValError) { - if num, ok := data.(float64); ok { - if num >= float64(m) { - AddError(errs, propPath, data, fmt.Sprintf("must be less than %f", m)) - } +func (m ExclusiveMaximum) Validate(propPath string, data Val, errs *[]ValError) { + var num float64 + if v, ok := data.(IntVal); ok { + num = float64(v.Value()) + } else if v, ok := data.(NumberVal); ok { + num = v.Value() + } else { + return + } + + if num >= float64(m) { + AddError(errs, propPath, data, fmt.Sprintf("must be less than %f", m)) } } @@ -72,11 +93,18 @@ func NewMinimum() Validator { } // Validate implements the Validator interface for Minimum -func (m Minimum) Validate(propPath string, data interface{}, errs *[]ValError) { - if num, ok := data.(float64); ok { - if num < float64(m) { - AddError(errs, propPath, data, fmt.Sprintf("must be greater than or equal to %f", m)) - } +func (m Minimum) Validate(propPath string, data Val, errs *[]ValError) { + var num float64 + if v, ok := data.(IntVal); ok { + num = float64(v.Value()) + } else if v, ok := data.(NumberVal); ok { + num = v.Value() + } else { + return + } + + if num < float64(m) { + AddError(errs, propPath, data, fmt.Sprintf("must be greater than or equal to %f", m)) } } @@ -90,10 +118,17 @@ func NewExclusiveMinimum() Validator { } // Validate implements the Validator interface for ExclusiveMinimum -func (m ExclusiveMinimum) Validate(propPath string, data interface{}, errs *[]ValError) { - if num, ok := data.(float64); ok { - if num <= float64(m) { - AddError(errs, propPath, data, fmt.Sprintf("must be greater than %f", m)) - } +func (m ExclusiveMinimum) Validate(propPath string, data Val, errs *[]ValError) { + var num float64 + if v, ok := data.(IntVal); ok { + num = float64(v.Value()) + } else if v, ok := data.(NumberVal); ok { + num = v.Value() + } else { + return + } + + if num <= float64(m) { + AddError(errs, propPath, data, fmt.Sprintf("must be greater than %f", m)) } } diff --git a/keywords_objects.go b/keywords_objects.go index b560b8b..d990b2d 100644 --- a/keywords_objects.go +++ b/keywords_objects.go @@ -18,10 +18,10 @@ func NewMaxProperties() Validator { } // Validate implements the validator interface for MaxProperties -func (m MaxProperties) Validate(propPath string, data interface{}, errs *[]ValError) { - if obj, ok := data.(map[string]interface{}); ok { - if len(obj) > int(m) { - AddError(errs, propPath, data, fmt.Sprintf("%d object Properties exceed %d maximum", len(obj), m)) +func (m MaxProperties) Validate(propPath string, data Val, errs *[]ValError) { + if obj, ok := data.(*ObjectVal); ok { + if len(obj.Map()) > int(m) { + AddError(errs, propPath, data, fmt.Sprintf("%d object Properties exceed %d maximum", len(obj.Map()), m)) } } } @@ -37,10 +37,10 @@ func NewMinProperties() Validator { } // Validate implements the validator interface for minProperties -func (m minProperties) Validate(propPath string, data interface{}, errs *[]ValError) { - if obj, ok := data.(map[string]interface{}); ok { - if len(obj) < int(m) { - AddError(errs, propPath, data, fmt.Sprintf("%d object Properties below %d minimum", len(obj), m)) +func (m minProperties) Validate(propPath string, data Val, errs *[]ValError) { + if obj, ok := data.(*ObjectVal); ok { + if len(obj.Map()) < int(m) { + AddError(errs, propPath, data, fmt.Sprintf("%d object Properties below %d minimum", len(obj.Map()), m)) } } } @@ -56,10 +56,10 @@ func NewRequired() Validator { } // Validate implements the validator interface for Required -func (r Required) Validate(propPath string, data interface{}, errs *[]ValError) { - if obj, ok := data.(map[string]interface{}); ok { +func (r Required) Validate(propPath string, data Val, errs *[]ValError) { + if obj, ok := data.(*ObjectVal); ok { for _, key := range r { - if val, ok := obj[key]; val == nil && !ok { + if val, ok := obj.Map()[key]; val == nil && !ok { AddError(errs, propPath, data, fmt.Sprintf(`"%s" value is required`, key)) } } @@ -92,18 +92,18 @@ func NewProperties() Validator { } // Validate implements the validator interface for Properties -func (p Properties) Validate(propPath string, data interface{}, errs *[]ValError) { +func (p Properties) Validate(propPath string, data Val, errs *[]ValError) { jp, err := jsonpointer.Parse(propPath) if err != nil { AddError(errs, propPath, nil, "invalid property path") return } - if obj, ok := data.(map[string]interface{}); ok { - for key, val := range obj { + if obj, ok := data.(*ObjectVal); ok { + for key, val := range obj.Map() { if p[key] != nil { d, _ := jp.Descendant(key) - p[key].Validate(d.String(), val, errs) + p[key].Validate(d.String(), dataToVal(val), errs) } } } @@ -146,19 +146,19 @@ type patternSchema struct { } // Validate implements the validator interface for PatternProperties -func (p PatternProperties) Validate(propPath string, data interface{}, errs *[]ValError) { +func (p PatternProperties) Validate(propPath string, data Val, errs *[]ValError) { jp, err := jsonpointer.Parse(propPath) if err != nil { AddError(errs, propPath, nil, "invalid property path") return } - if obj, ok := data.(map[string]interface{}); ok { - for key, val := range obj { + if obj, ok := data.(*ObjectVal); ok { + for key, val := range obj.Map() { for _, ptn := range p { if ptn.re.Match([]byte(key)) { d, _ := jp.Descendant(key) - ptn.schema.Validate(d.String(), val, errs) + ptn.schema.Validate(d.String(), dataToVal(val), errs) } } } @@ -237,16 +237,16 @@ func NewAdditionalProperties() Validator { } // Validate implements the validator interface for AdditionalProperties -func (ap AdditionalProperties) Validate(propPath string, data interface{}, errs *[]ValError) { +func (ap AdditionalProperties) Validate(propPath string, data Val, errs *[]ValError) { jp, err := jsonpointer.Parse(propPath) if err != nil { AddError(errs, propPath, nil, "invalid property path") return } - if obj, ok := data.(map[string]interface{}); ok { + if obj, ok := data.(*ObjectVal); ok { KEYS: - for key, val := range obj { + for key, val := range obj.Map() { if ap.Properties != nil { for propKey := range *ap.Properties { if propKey == key { @@ -263,7 +263,7 @@ func (ap AdditionalProperties) Validate(propPath string, data interface{}, errs } // c := len(*errs) d, _ := jp.Descendant(key) - ap.Schema.Validate(d.String(), val, errs) + ap.Schema.Validate(d.String(), dataToVal(val), errs) // if len(*errs) > c { // // fmt.Sprintf("object key %s AdditionalProperties error: %s", key, err.Error()) // return @@ -320,16 +320,16 @@ func NewDependencies() Validator { } // Validate implements the validator interface for Dependencies -func (d Dependencies) Validate(propPath string, data interface{}, errs *[]ValError) { +func (d Dependencies) Validate(propPath string, data Val, errs *[]ValError) { jp, err := jsonpointer.Parse(propPath) if err != nil { AddError(errs, propPath, nil, "invalid property path") return } - if obj, ok := data.(map[string]interface{}); ok { + if obj, ok := data.(*ObjectVal); ok { for key, val := range d { - if obj[key] != nil { + if obj.Map()[key] != nil { d, _ := jp.Descendant(key) val.Validate(d.String(), obj, errs) } @@ -361,13 +361,13 @@ type Dependency struct { } // Validate implements the validator interface for Dependency -func (d Dependency) Validate(propPath string, data interface{}, errs *[]ValError) { - if obj, ok := data.(map[string]interface{}); ok { +func (d Dependency) Validate(propPath string, data Val, errs *[]ValError) { + if obj, ok := data.(*ObjectVal); ok { if d.schema != nil { d.schema.Validate(propPath, data, errs) } else if len(d.props) > 0 { for _, k := range d.props { - if obj[k] == nil { + if obj.Map()[k] == nil { AddError(errs, propPath, data, fmt.Sprintf("Dependency property %s is Required", k)) } } @@ -411,7 +411,7 @@ func NewPropertyNames() Validator { } // Validate implements the validator interface for PropertyNames -func (p PropertyNames) Validate(propPath string, data interface{}, errs *[]ValError) { +func (p PropertyNames) Validate(propPath string, data Val, errs *[]ValError) { jp, err := jsonpointer.Parse(propPath) if err != nil { AddError(errs, propPath, nil, "invalid property path") @@ -419,11 +419,11 @@ func (p PropertyNames) Validate(propPath string, data interface{}, errs *[]ValEr } sch := Schema(p) - if obj, ok := data.(map[string]interface{}); ok { - for key := range obj { + if obj, ok := data.(*ObjectVal); ok { + for key := range obj.Map() { // TODO - adjust error message & prop path d, _ := jp.Descendant(key) - sch.Validate(d.String(), key, errs) + sch.Validate(d.String(), dataToVal(key), errs) } } } diff --git a/keywords_strings.go b/keywords_strings.go index d2c8b5b..56ada04 100644 --- a/keywords_strings.go +++ b/keywords_strings.go @@ -18,9 +18,9 @@ func NewMaxLength() Validator { } // Validate implements the Validator interface for MaxLength -func (m MaxLength) Validate(propPath string, data interface{}, errs *[]ValError) { - if str, ok := data.(string); ok { - if utf8.RuneCountInString(str) > int(m) { +func (m MaxLength) Validate(propPath string, data Val, errs *[]ValError) { + if str, ok := data.(StringVal); ok { + if utf8.RuneCountInString(string(str)) > int(m) { AddError(errs, propPath, data, fmt.Sprintf("max length of %d characters exceeded: %s", m, str)) } } @@ -38,9 +38,9 @@ func NewMinLength() Validator { } // Validate implements the Validator interface for MinLength -func (m MinLength) Validate(propPath string, data interface{}, errs *[]ValError) { - if str, ok := data.(string); ok { - if utf8.RuneCountInString(str) < int(m) { +func (m MinLength) Validate(propPath string, data Val, errs *[]ValError) { + if str, ok := data.(StringVal); ok { + if utf8.RuneCountInString(string(str)) < int(m) { AddError(errs, propPath, data, fmt.Sprintf("min length of %d characters required: %s", m, str)) } } @@ -58,9 +58,9 @@ func NewPattern() Validator { } // Validate implements the Validator interface for Pattern -func (p Pattern) Validate(propPath string, data interface{}, errs *[]ValError) { +func (p Pattern) Validate(propPath string, data Val, errs *[]ValError) { re := regexp.Regexp(p) - if str, ok := data.(string); ok { + if str, ok := data.(StringVal); ok { if !re.Match([]byte(str)) { AddError(errs, propPath, data, fmt.Sprintf("regexp pattern %s mismatch on string: %s", re.String(), str)) } diff --git a/schema.go b/schema.go index ee1f939..73ffa36 100644 --- a/schema.go +++ b/schema.go @@ -188,7 +188,7 @@ func (rs *RootSchema) ValidateBytes(data []byte) ([]ValError, error) { if err := json.Unmarshal(data, &doc); err != nil { return errs, fmt.Errorf("error parsing JSON bytes: %s", err.Error()) } - rs.Validate("/", doc, &errs) + rs.Validate("/", dataToVal(doc), &errs) return errs, nil } @@ -365,7 +365,7 @@ func (s *Schema) Path() string { // Validate uses the schema to check an instance, collecting validation // errors in a slice -func (s *Schema) Validate(propPath string, data interface{}, errs *[]ValError) { +func (s *Schema) Validate(propPath string, data Val, errs *[]ValError) { if s.Ref != "" && s.ref != nil { s.ref.Validate(propPath, data, errs) return diff --git a/schema_test.go b/schema_test.go index 32dfed7..8ad1788 100644 --- a/schema_test.go +++ b/schema_test.go @@ -435,7 +435,7 @@ func runJSONTests(t *testing.T, testFilepaths []string) { for i, c := range ts.Tests { tests++ got := []ValError{} - sc.Validate("/", c.Data, &got) + sc.Validate("/", dataToVal(c.Data), &got) valid := len(got) == 0 if valid != c.Valid { t.Errorf("%s: %s test case %d: %s. error: %s", base, ts.Description, i, c.Description, got) @@ -449,28 +449,28 @@ func runJSONTests(t *testing.T, testFilepaths []string) { } func TestDataType(t *testing.T) { - type customObject struct {} + type customObject struct{} type customNumber float64 cases := []struct { data interface{} - expect string + expect T }{ - {nil, "null"}, - {float64(4), "integer"}, - {float64(4.5), "number"}, - {customNumber(4.5), "number"}, - {"foo", "string"}, - {[]interface{}{}, "array"}, - {[0]interface{}{}, "array"}, - {map[string]interface{}{}, "object"}, - {struct{}{}, "object"}, - {customObject{}, "object"}, - {int8(42), "unknown"}, + {nil, Null}, + {float64(4), Integer}, + {float64(4.5), Number}, + {customNumber(4.5), Number}, + {"foo", String}, + {[]interface{}{}, Array}, + {[0]interface{}{}, Array}, + {map[string]interface{}{}, Object}, + {struct{}{}, Object}, + {customObject{}, Object}, + {int8(42), Unknown}, } for i, c := range cases { - got := DataType(c.data) + got := dataToT(c.data) if got != c.expect { t.Errorf("case %d result mismatch. expected: '%s', got: '%s'", i, c.expect, got) } diff --git a/types.go b/types.go new file mode 100644 index 0000000..e0b1361 --- /dev/null +++ b/types.go @@ -0,0 +1,248 @@ +package jsonschema + +import ( + "fmt" + "reflect" + "strings" +) + +type T string + +const ( + Unknown T = "unknown" + Null = "null" + Bool = "boolean" + Object = "object" + Array = "array" + Number = "number" + Integer = "integer" + String = "string" +) + +// dataToT resolves data to a JSON type. +func dataToT(data interface{}) T { + if data == nil { + return Null + } + + knd := reflect.TypeOf(data).Kind() + _ = knd + + switch reflect.TypeOf(data).Kind() { + case reflect.Bool: + return Bool + case reflect.Float64: + number := reflect.ValueOf(data).Float() + if float64(int(number)) == number { + return Integer + } + return Number + case reflect.String: + return String + case reflect.Array, reflect.Slice: + return Array + case reflect.Map, reflect.Struct: + return Object + default: + return Unknown + } +} + +// Ts is a collection of JSON types. +type Ts []T + +func (ts Ts) String() string { + switch len(ts) { + case 0: + return "" + case 1: + return string(ts[0]) + } + + // Calculate string length. + n := len(ts) - 1 + for i := 0; i < len(ts); i++ { + n += len(ts[i]) + } + + var b strings.Builder + b.Grow(n) + b.WriteString(string(ts[0])) + for _, t := range ts[1:] { + b.WriteString(",") + b.WriteString(string(t)) + } + return b.String() +} + +var mapT = map[T]bool{ + Null: true, + Bool: true, + Object: true, + Array: true, + Number: true, + Integer: true, + String: true, +} + +type Val interface { + Type() T + Raw() interface{} +} + +type NullVal struct{} + +func (v NullVal) Type() T { + return Null +} + +func (v NullVal) Raw() interface{} { + return nil +} + +type BoolVal bool + +func (v BoolVal) Type() T { + return Bool +} + +func (v BoolVal) Raw() interface{} { + return bool(v) +} + +type NumberVal float64 + +func (v NumberVal) Type() T { + return Number +} + +func (v NumberVal) Raw() interface{} { + return float64(v) +} + +func (v NumberVal) Value() float64 { + return float64(v) +} + +type IntVal int64 + +func (v IntVal) Type() T { + return Integer +} + +func (v IntVal) Raw() interface{} { + return int64(v) +} + +func (v IntVal) Value() int64 { + return int64(v) +} + +type StringVal string + +func (v StringVal) Type() T { + return String +} + +func (v StringVal) Raw() interface{} { + return string(v) +} + +type ObjectVal struct { + raw interface{} + m map[string]interface{} +} + +func (v *ObjectVal) Type() T { + return Object +} + +func (v *ObjectVal) Raw() interface{} { + return v.raw +} + +func (v *ObjectVal) Map() map[string]interface{} { + v.ensureM() + return v.m +} + +func (v *ObjectVal) ensureM() { + if v.raw == nil || v.m != nil { + return + } + + switch reflect.TypeOf(v.raw).Kind() { + case reflect.Map: + switch m := v.raw.(type) { + case map[string]interface{}: + v.m = m + case map[interface{}]interface{}: + tmp := make(map[string]interface{}, len(m)) + for k, v := range m { + switch str := k.(type) { + case string: + tmp[str] = v + default: + return + } + } + v.m = tmp + } + case reflect.Struct: + val := reflect.ValueOf(v.raw) + tmp := make(map[string]interface{}, val.NumField()) + for i := 0; i < val.NumField(); i++ { + f := val.Field(i) + t := val.Type() + tmp[t.Name()] = f.Interface() + } + v.m = tmp + } +} + +type ArrayVal []interface{} + +func (v ArrayVal) Type() T { + return Array +} + +func (v ArrayVal) Raw() interface{} { + return []interface{}(v) +} + +var _ Val = NullVal{} +var _ Val = BoolVal(false) +var _ Val = NumberVal(0) +var _ Val = IntVal(0) +var _ Val = StringVal("") +var _ Val = &ObjectVal{} +var _ Val = ArrayVal{} + +func dataToVal(data interface{}) Val { + if data == nil { + return NullVal{} + } + + val := reflect.ValueOf(data) + + switch dataToT(data) { + case Bool: + return BoolVal(val.Bool()) + case Number: + return NumberVal(val.Float()) + case Integer: + return IntVal(int64(val.Float())) + case String: + return StringVal(val.String()) + case Object: + return &ObjectVal{raw: data} + case Array: + arr := make([]interface{}, 0, val.Len()) + for i := 0; i < val.Len(); i++ { + arr = append(arr, val.Index(i).Interface()) + } + return ArrayVal(arr) + default: + panic(fmt.Sprintf("unable to handle data of type '%v' (%v)", dataToT(data), data)) + } +} diff --git a/val_error.go b/val_error.go index 75852f6..752118d 100644 --- a/val_error.go +++ b/val_error.go @@ -45,10 +45,10 @@ func InvalidValueString(data interface{}) string { } // AddError creates and appends a ValError to errs -func AddError(errs *[]ValError, propPath string, data interface{}, msg string) { +func AddError(errs *[]ValError, propPath string, data Val, msg string) { *errs = append(*errs, ValError{ PropertyPath: propPath, - InvalidValue: data, + InvalidValue: data.Raw(), Message: msg, }) } diff --git a/validate.go b/validate.go index 57ff528..d05c864 100644 --- a/validate.go +++ b/validate.go @@ -11,7 +11,7 @@ type Validator interface { // Validate checks decoded JSON data and writes // validation errors (if any) to an outparam slice of ValErrors // propPath indicates the position of data in the json tree - Validate(propPath string, data interface{}, errs *[]ValError) + Validate(propPath string, val Val, errs *[]ValError) } // BaseValidator is a foundation for building a validator @@ -30,11 +30,11 @@ func (b BaseValidator) Path() string { } // AddError is a convenience method for appending a new error to an existing error slice -func (b BaseValidator) AddError(errs *[]ValError, propPath string, data interface{}, msg string) { +func (b BaseValidator) AddError(errs *[]ValError, propPath string, data Val, msg string) { *errs = append(*errs, ValError{ PropertyPath: propPath, RulePath: b.Path(), - InvalidValue: data, + InvalidValue: data.Raw(), Message: msg, }) } diff --git a/validate_test.go b/validate_test.go index daf9817..e80c82d 100644 --- a/validate_test.go +++ b/validate_test.go @@ -12,8 +12,8 @@ func newIsFoo() Validator { return new(IsFoo) } -func (f IsFoo) Validate(propPath string, data interface{}, errs *[]ValError) { - if str, ok := data.(string); ok { +func (f IsFoo) Validate(propPath string, data Val, errs *[]ValError) { + if str, ok := data.(StringVal); ok { if str != "foo" { AddError(errs, propPath, data, fmt.Sprintf("should be foo. plz make '%s' == foo. plz", str)) } @@ -44,7 +44,7 @@ func ExampleCustomValidator() { type FooValidator uint8 -func (f *FooValidator) Validate(propPath string, data interface{}, errs *[]ValError) {} +func (f *FooValidator) Validate(propPath string, data Val, errs *[]ValError) {} func TestRegisterValidator(t *testing.T) { newFoo := func() Validator { @@ -57,3 +57,565 @@ func TestRegisterValidator(t *testing.T) { t.Errorf("expected %s to be added as a default validator", "foo") } } + +var jsonSchema = ` +{ + "definitions": {}, + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://example.com/root.json", + "type": "array", + "items": { + "$id": "#/items", + "type": "object", + "properties": { + "_id": { + "$id": "#/items/properties/_id", + "type": "string" + }, + "index": { + "$id": "#/items/properties/index", + "type": "integer" + }, + "guid": { + "$id": "#/items/properties/guid", + "type": "string" + }, + "isActive": { + "$id": "#/items/properties/isActive", + "type": "boolean" + }, + "balance": { + "$id": "#/items/properties/balance", + "type": "string" + }, + "picture": { + "$id": "#/items/properties/picture", + "type": "string" + }, + "age": { + "$id": "#/items/properties/age", + "type": "integer" + }, + "eyeColor": { + "$id": "#/items/properties/eyeColor", + "type": "string" + }, + "name": { + "$id": "#/items/properties/name", + "type": "object", + "properties": { + "first": { + "$id": "#/items/properties/name/properties/first", + "type": "string" + }, + "last": { + "$id": "#/items/properties/name/properties/last", + "type": "string" + } + } + }, + "company": { + "$id": "#/items/properties/company", + "type": "string" + }, + "email": { + "$id": "#/items/properties/email", + "type": "string" + }, + "phone": { + "$id": "#/items/properties/phone", + "type": "string" + }, + "address": { + "$id": "#/items/properties/address", + "type": "string" + }, + "about": { + "$id": "#/items/properties/about", + "type": "string" + }, + "registered": { + "$id": "#/items/properties/registered", + "type": "string" + }, + "latitude": { + "$id": "#/items/properties/latitude", + "type": "string" + }, + "longitude": { + "$id": "#/items/properties/longitude", + "type": "string" + }, + "tags": { + "$id": "#/items/properties/tags", + "type": "array", + "items": { + "$id": "#/items/properties/tags/items", + "type": "string" + } + }, + "range": { + "$id": "#/items/properties/range", + "type": "array", + "items": { + "$id": "#/items/properties/range/items", + "type": "integer" + } + }, + "friends": { + "$id": "#/items/properties/friends", + "type": "array", + "items": { + "$id": "#/items/properties/friends/items", + "type": "object", + "properties": { + "id": { + "$id": "#/items/properties/friends/items/properties/id", + "type": "integer" + }, + "name": { + "$id": "#/items/properties/friends/items/properties/name", + "type": "string" + } + } + } + }, + "greeting": { + "$id": "#/items/properties/greeting", + "type": "string" + }, + "favoriteFruit": { + "$id": "#/items/properties/favoriteFruit", + "type": "string" + } + } + } +} +` + +var jsonData = ` +[ + { + "_id": "5d61319e267927d1568d252c", + "index": 0, + "guid": "806698a0-107f-4705-8578-635db974dda5", + "isActive": false, + "balance": "$3,450.34", + "picture": "http://placehold.it/32x32", + "age": 38, + "eyeColor": "brown", + "name": { + "first": "Stacy", + "last": "Hansen" + }, + "company": "OBONES", + "email": "stacy.hansen@obones.name", + "phone": "+1 (910) 534-3387", + "address": "961 Havens Place, Joppa, Montana, 4767", + "about": "Sit aliqua tempor ullamco id commodo minim ut magna commodo reprehenderit sint. Consequat in commodo veniam ut fugiat reprehenderit ut cupidatat dolor esse veniam enim dolor. Occaecat exercitation amet culpa in fugiat enim incididunt duis excepteur non enim. Do ea sunt mollit ea magna exercitation anim. Laboris quis pariatur tempor irure. Duis reprehenderit culpa est labore aliquip irure cillum.", + "registered": "Sunday, May 21, 2017 6:04 AM", + "latitude": "29.235737", + "longitude": "-172.020409", + "tags": [ + "ullamco", + "nulla", + "anim", + "ipsum", + "ea" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Logan Baxter" + }, + { + "id": 1, + "name": "Ida Osborn" + }, + { + "id": 2, + "name": "Marie Woodard" + } + ], + "greeting": "Hello, Stacy! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5d61319e0395dad4f03a0643", + "index": 1, + "guid": "578f5162-3cc5-4d21-82ee-99dee1939880", + "isActive": false, + "balance": "$3,457.18", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "blue", + "name": { + "first": "Carter", + "last": "Hatfield" + }, + "company": "KEEG", + "email": "carter.hatfield@keeg.me", + "phone": "+1 (854) 563-3004", + "address": "457 Cypress Avenue, Walker, Vermont, 5373", + "about": "Cupidatat voluptate amet et amet occaecat ea elit irure ullamco reprehenderit. Dolor Lorem proident exercitation duis irure voluptate reprehenderit anim Lorem elit adipisicing ea nisi enim. Pariatur anim laboris et tempor veniam id fugiat officia labore dolore in laborum id. Nostrud esse ipsum incididunt fugiat sit aliqua ullamco sunt. Ad dolor aute deserunt mollit ullamco sint magna do consequat veniam occaecat excepteur eiusmod aliquip.", + "registered": "Tuesday, April 18, 2017 2:49 AM", + "latitude": "76.414341", + "longitude": "-44.996223", + "tags": [ + "nulla", + "reprehenderit", + "in", + "est", + "elit" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Lottie Valencia" + }, + { + "id": 1, + "name": "Shelia Downs" + }, + { + "id": 2, + "name": "Hale Cunningham" + } + ], + "greeting": "Hello, Carter! You have 8 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5d61319e9691e77d4f7651eb", + "index": 2, + "guid": "f7a8fe8f-bc76-4862-adf4-2f04e7fcf649", + "isActive": false, + "balance": "$2,421.14", + "picture": "http://placehold.it/32x32", + "age": 39, + "eyeColor": "brown", + "name": { + "first": "Adrienne", + "last": "Ruiz" + }, + "company": "FARMAGE", + "email": "adrienne.ruiz@farmage.tv", + "phone": "+1 (822) 480-3761", + "address": "471 Hyman Court, Hamilton, New Hampshire, 5914", + "about": "Laborum consequat pariatur cupidatat nulla laborum sint sint id Lorem. Sit adipisicing eiusmod enim excepteur ex incididunt duis est ex. Eu et labore ipsum commodo qui pariatur incididunt. Nostrud et anim aliqua exercitation voluptate deserunt proident velit ut aliqua eiusmod sit.", + "registered": "Sunday, April 8, 2018 3:47 PM", + "latitude": "-14.903675", + "longitude": "148.65533", + "tags": [ + "officia", + "enim", + "cupidatat", + "anim", + "ipsum" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Janette Williamson" + }, + { + "id": 1, + "name": "Lea Wilkins" + }, + { + "id": 2, + "name": "Mary Carlson" + } + ], + "greeting": "Hello, Adrienne! You have 6 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5d61319e99ad0afdaec7ae1b", + "index": 3, + "guid": "a1768c3f-97bf-473e-a17f-1a1569f73434", + "isActive": true, + "balance": "$2,114.76", + "picture": "http://placehold.it/32x32", + "age": 28, + "eyeColor": "brown", + "name": { + "first": "Terry", + "last": "Wolfe" + }, + "company": "CEDWARD", + "email": "terry.wolfe@cedward.co.uk", + "phone": "+1 (965) 495-3608", + "address": "849 Visitation Place, Glendale, Palau, 6575", + "about": "Eiusmod duis magna sit magna fugiat ex mollit eiusmod duis fugiat non tempor. Aliquip pariatur nulla consectetur laborum qui ad quis aute nisi. Fugiat ea exercitation irure do enim ipsum in ullamco tempor adipisicing cupidatat cillum Lorem aliquip. Anim sit qui eiusmod Lorem do ad veniam deserunt nisi qui cillum.", + "registered": "Friday, March 25, 2016 1:12 PM", + "latitude": "-41.353055", + "longitude": "-41.574776", + "tags": [ + "culpa", + "incididunt", + "nisi", + "proident", + "ad" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Haley Ortiz" + }, + { + "id": 1, + "name": "Valencia Ray" + }, + { + "id": 2, + "name": "Dillard Crane" + } + ], + "greeting": "Hello, Terry! You have 6 unread messages.", + "favoriteFruit": "strawberry" + }, + { + "_id": "5d61319ecdd42b472f2d2915", + "index": 4, + "guid": "b66970fd-a086-40c5-8e27-d4c5fba320b8", + "isActive": false, + "balance": "$1,061.76", + "picture": "http://placehold.it/32x32", + "age": 31, + "eyeColor": "brown", + "name": { + "first": "Florine", + "last": "Odonnell" + }, + "company": "EMOLTRA", + "email": "florine.odonnell@emoltra.net", + "phone": "+1 (988) 507-3553", + "address": "775 Creamer Street, Columbus, Utah, 2783", + "about": "Enim qui ea ut sit laborum mollit dolor culpa cillum voluptate non reprehenderit. Est incididunt consequat ea ipsum amet veniam. Excepteur ut duis officia excepteur proident pariatur et elit velit ea. Fugiat aute dolore nisi amet aute dolore. Dolor duis proident ex non est enim.", + "registered": "Sunday, May 6, 2018 6:07 AM", + "latitude": "-82.113645", + "longitude": "-179.215071", + "tags": [ + "nulla", + "duis", + "consequat", + "Lorem", + "nisi" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Clay Hooper" + }, + { + "id": 1, + "name": "Chrystal Barnett" + }, + { + "id": 2, + "name": "Griffith Fischer" + } + ], + "greeting": "Hello, Florine! You have 8 unread messages.", + "favoriteFruit": "banana" + }, + { + "_id": "5d61319e42bb19e55e71d26c", + "index": 5, + "guid": "7a715f20-2415-4cd4-8dd7-5576e73db827", + "isActive": false, + "balance": "$1,282.02", + "picture": "http://placehold.it/32x32", + "age": 37, + "eyeColor": "green", + "name": { + "first": "Susie", + "last": "Hebert" + }, + "company": "ORBOID", + "email": "susie.hebert@orboid.org", + "phone": "+1 (879) 446-3634", + "address": "529 Green Street, Longoria, Iowa, 2493", + "about": "Adipisicing irure ea voluptate magna aute velit irure elit voluptate aliqua proident sunt deserunt. Sit irure proident veniam id dolore irure sit aliqua pariatur et. Ut Lorem pariatur pariatur adipisicing sit id sit Lorem et ea tempor. Ipsum veniam excepteur incididunt officia officia esse amet exercitation. Do in in quis et dolor eiusmod ad ipsum ad dolor aliquip. Consequat do minim magna aute et irure incididunt magna voluptate cillum aliquip dolor sunt.", + "registered": "Thursday, May 17, 2018 9:42 AM", + "latitude": "58.540125", + "longitude": "44.642099", + "tags": [ + "deserunt", + "occaecat", + "cupidatat", + "nulla", + "amet" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Benita Pace" + }, + { + "id": 1, + "name": "Clarice Powell" + }, + { + "id": 2, + "name": "Kelley Mayer" + } + ], + "greeting": "Hello, Susie! You have 10 unread messages.", + "favoriteFruit": "apple" + }, + { + "_id": "5d61319eb9f74a89f8189c59", + "index": 6, + "guid": "0ac7c4eb-c4c3-4f35-869a-05ba5cb8a052", + "isActive": false, + "balance": "$1,401.41", + "picture": "http://placehold.it/32x32", + "age": 23, + "eyeColor": "brown", + "name": { + "first": "Miranda", + "last": "Erickson" + }, + "company": "COLLAIRE", + "email": "miranda.erickson@collaire.com", + "phone": "+1 (961) 443-2037", + "address": "917 Putnam Avenue, Bluffview, North Carolina, 7451", + "about": "Cillum consectetur adipisicing minim laborum irure. Est veniam consequat amet reprehenderit dolor nulla veniam. Quis amet deserunt Lorem dolor sint nostrud anim non.", + "registered": "Sunday, August 3, 2014 10:02 PM", + "latitude": "-36.905012", + "longitude": "81.905921", + "tags": [ + "consequat", + "ut", + "tempor", + "consectetur", + "consequat" + ], + "range": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "friends": [ + { + "id": 0, + "name": "Cole Morrow" + }, + { + "id": 1, + "name": "Ochoa Wiley" + }, + { + "id": 2, + "name": "Sparks Reynolds" + } + ], + "greeting": "Hello, Miranda! You have 10 unread messages.", + "favoriteFruit": "strawberry" + } +] +` + +func BenchmarkTypeValidation(b *testing.B) { + var rs RootSchema + if err := json.Unmarshal([]byte(jsonSchema), &rs); err != nil { + b.Errorf("unable to unmarshal jsonschema: %v", err) + } + + var raw []interface{} + if err := json.Unmarshal([]byte(jsonData), &raw); err != nil { + b.Errorf("unable to unmarshal json data: %v", err) + } + data := dataToVal(raw) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + var errs []ValError + rs.Validate("", data, &errs) + for _, err := range errs { + b.Errorf("invalid json data: %v", err) + } + } +}