Skip to content

Commit

Permalink
feat(*): implement higher level json types
Browse files Browse the repository at this point in the history
  • Loading branch information
asido committed Aug 31, 2019
1 parent 585074c commit 9a95448
Show file tree
Hide file tree
Showing 14 changed files with 440 additions and 185 deletions.
74 changes: 23 additions & 51 deletions keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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
}
}
Expand All @@ -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]))
Expand All @@ -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 {
Expand All @@ -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)
}
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)))
}
}
Expand Down
32 changes: 16 additions & 16 deletions keywords_arrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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)
}
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
}
Expand Down
8 changes: 4 additions & 4 deletions keywords_booleans.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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{}
Expand Down Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions keywords_conditionals.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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{} {
Expand Down Expand Up @@ -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{} {
Expand Down
38 changes: 19 additions & 19 deletions keywords_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Loading

0 comments on commit 9a95448

Please sign in to comment.