Skip to content

Commit

Permalink
feat(IfThenElse): implement If/Then/Else, cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
b5 committed Jan 16, 2018
1 parent a99baf2 commit bef9c1e
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 60 deletions.
12 changes: 6 additions & 6 deletions keywords.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ func (t Type) Validate(data interface{}) error {
}
if len(t) == 1 {
return fmt.Errorf(`expected "%v" to be of type %s`, data, t[0])
} else {
str := ""
for _, ts := range t {
str += ts + ","
}
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
}

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

// JSONProp implements JSON property name indexing for Type
Expand Down
5 changes: 3 additions & 2 deletions keywords_arrays.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ func (it Items) JSONProp(name string) interface{} {
return it.Schemas[idx]
}

// JSONChildren implements the JSONContainer interface for Items
func (it Items) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, sch := range it.Schemas {
Expand Down Expand Up @@ -202,8 +203,8 @@ func (c *Contains) Validate(data interface{}) error {
}

// JSONProp implements JSON property name indexing for Contains
func (m Contains) JSONProp(name string) interface{} {
return Schema(m).JSONProp(name)
func (c Contains) JSONProp(name string) interface{} {
return Schema(c).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for Contains
Expand Down
27 changes: 27 additions & 0 deletions keywords_booleans.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ func (a AllOf) JSONProp(name string) interface{} {
return a[idx]
}

// JSONChildren implements the JSONContainer interface for AllOf
func (a AllOf) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, sch := range a {
res[strconv.Itoa(i)] = sch
}
return
}

// AnyOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
// An instance validates successfully against this keyword if it validates successfully against at
// least one schema defined by this keyword's value.
Expand Down Expand Up @@ -59,6 +68,15 @@ func (a AnyOf) JSONProp(name string) interface{} {
return a[idx]
}

// JSONChildren implements the JSONContainer interface for AnyOf
func (a AnyOf) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, sch := range a {
res[strconv.Itoa(i)] = sch
}
return
}

// OneOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
type OneOf []*Schema
Expand Down Expand Up @@ -92,6 +110,15 @@ func (o OneOf) JSONProp(name string) interface{} {
return o[idx]
}

// JSONChildren implements the JSONContainer interface for OneOf
func (o OneOf) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, sch := range o {
res[strconv.Itoa(i)] = sch
}
return
}

// Not MUST be a valid JSON Schema.
// An instance is valid against this keyword if it fails to validate successfully against the schema defined
// by this keyword.
Expand Down
23 changes: 20 additions & 3 deletions keywords_conditionals.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,33 @@ import (
// Instances that successfully validate against this keyword's subschema MUST also be valid against the subschema value of the "then" keyword, if present.
// Instances that fail to validate against this keyword's subschema MUST also be valid against the subschema value of the "else" keyword.
// Validation of the instance against this keyword on its own always succeeds, regardless of the validation outcome of against its subschema.
type If Schema
type If struct {
Schema Schema
Then *Then
Else *Else
}

// Validate implements the Validator interface for If
func (i *If) Validate(data interface{}) error {
if err := i.Schema.Validate(data); err == nil {
if i.Then != nil {
s := Schema(*i.Then)
sch := &s
return sch.Validate(data)
}
} else {
if i.Else != nil {
s := Schema(*i.Else)
sch := &s
return sch.Validate(data)
}
}
return nil
}

// JSONProp implements JSON property name indexing for If
func (i If) JSONProp(name string) interface{} {
return Schema(i).JSONProp(name)
return Schema(i.Schema).JSONProp(name)
}

// UnmarshalJSON implements the json.Unmarshaler interface for If
Expand All @@ -26,7 +43,7 @@ func (i *If) UnmarshalJSON(data []byte) error {
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
*i = If(sch)
*i = If{Schema: sch}
return nil
}

Expand Down
1 change: 1 addition & 0 deletions keywords_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ func (p Properties) JSONProp(name string) interface{} {
return p[name]
}

// JSONChildren implements the JSONContainer interface for Properties
func (p Properties) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for key, sch := range p {
Expand Down
95 changes: 86 additions & 9 deletions schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"encoding/json"
"fmt"
"github.com/qri-io/jsonpointer"
// "io/ioutil"
"net/http"
"net/url"
)

Expand Down Expand Up @@ -73,9 +75,7 @@ func (rs *RootSchema) UnmarshalJSON(data []byte) error {
if err := walkJSON(sch, func(elem JSONPather) error {
if sch, ok := elem.(*Schema); ok {
if sch.Ref != "" {
// fmt.Println(sch.Ref, ids[sch.Ref])
if ids[sch.Ref] != nil {
fmt.Println("using id:", sch.Ref)
sch.ref = ids[sch.Ref]
return nil
}
Expand Down Expand Up @@ -107,16 +107,68 @@ func (rs *RootSchema) UnmarshalJSON(data []byte) error {
return nil
}

func (rs *RootSchema) ValdiateBytes(data []byte) error {
// FetchRemoteReferences grabs any url-based schema references that cannot
// be locally resolved via network requests
func (rs *RootSchema) FetchRemoteReferences() error {
sch := &rs.Schema

// collect IDs for internal referencing:
refs := map[string]*Schema{}
if err := walkJSON(sch, func(elem JSONPather) error {
if sch, ok := elem.(*Schema); ok {
ref := sch.Ref
if ref != "" {
if refs[ref] == nil {
if u, err := url.Parse(ref); err == nil {
if res, err := http.Get(u.String()); err == nil {
s := &RootSchema{}
if err := json.NewDecoder(res.Body).Decode(s); err != nil {
return err
}
refs[ref] = &s.Schema
sch.ref = refs[ref]
}
}
}
}
}
return nil
}); err != nil {
return err
}

// pass a pointer to the schema component in here (instead of the RootSchema struct)
// to ensure root is evaluated for references
if err := walkJSON(sch, func(elem JSONPather) error {
if sch, ok := elem.(*Schema); ok {
if sch.Ref != "" && refs[sch.Ref] != nil {
if refs[sch.Ref] != nil {
fmt.Println("using remote ref:", sch.Ref)
sch.ref = refs[sch.Ref]
}
return nil
}
}
return nil
}); err != nil {
return err
}

rs.Schema = *sch
return nil
}

// ValidateBytes performs schema validation against a slice of json byte data
func (rs *RootSchema) ValidateBytes(data []byte) error {
var doc interface{}
if err := json.Unmarshal(data, &doc); err != nil {
return err
}
return rs.Validate(doc)
}

func (s *RootSchema) evalJSONValidatorPointer(ptr jsonpointer.Pointer) (res interface{}, err error) {
res = s
func (rs *RootSchema) evalJSONValidatorPointer(ptr jsonpointer.Pointer) (res interface{}, err error) {
res = rs
for _, token := range ptr {
if adr, ok := res.(JSONPather); ok {
res = adr.JSONProp(token)
Expand Down Expand Up @@ -164,8 +216,6 @@ const (
type Schema struct {
// internal tracking for true/false/{...} schemas
schemaType schemaType
// reference to root for ref parsing
root *RootSchema
// The "$id" keyword defines a URI for the schema,
// and the base URI that other URI references within the schema are resolved against.
// A subschema's "$id" is resolved against the base URI of its parent schema.
Expand Down Expand Up @@ -237,6 +287,11 @@ type Schema struct {
// might get stuck in an infinite recursive loop trying to validate the instance.
// Schemas SHOULD NOT make use of infinite recursive nesting like this; the behavior is undefined.
Ref string `json:"$ref,omitempty"`
// Format functions as both an annotation (Section 3.3) and as an assertion (Section 3.2).
// While no special effort is required to implement it as an annotation conveying semantic meaning,
// implementing validation is non-trivial.
Format string `json:"format,omitempty"`

ref Validator

// Definitions provides a standardized location for schema authors to inline re-usable JSON Schemas
Expand All @@ -261,6 +316,7 @@ type _schema struct {
Comment string `json:"comment,omitempty"`
Ref string `json:"$ref,omitempty"`
Definitions map[string]*Schema `json:"definitions,omitempty"`
Format string `json:"format,omitempty"`
}

// JSONProp implements the JSONPather for Schema
Expand All @@ -286,6 +342,8 @@ func (s Schema) JSONProp(name string) interface{} {
return s.Ref
case "definitions":
return s.Definitions
case "format":
return s.Format
default:
prop := s.Validators[name]
if prop == nil && s.extraDefinitions[name] != nil {
Expand All @@ -295,7 +353,7 @@ func (s Schema) JSONProp(name string) interface{} {
}
}

// JSONChildren implements the JSONPather for Schema
// JSONChildren implements the JSONContainer interface for Schema
func (s *Schema) JSONChildren() (ch map[string]JSONPather) {
ch = map[string]JSONPather{}

Expand Down Expand Up @@ -350,6 +408,7 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
Comment: _s.Comment,
Ref: _s.Ref,
Definitions: _s.Definitions,
Format: _s.Format,
Validators: map[string]Validator{},
}

Expand Down Expand Up @@ -377,7 +436,7 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
// props to validator factory functions
switch prop {
// skip any already-parsed props
case "$id", "title", "description", "default", "examples", "readOnly", "writeOnly", "comment", "$ref", "definitions":
case "$schema", "$id", "title", "description", "default", "examples", "readOnly", "writeOnly", "comment", "$ref", "definitions", "format":
continue
case "type":
val = new(Type)
Expand Down Expand Up @@ -461,6 +520,17 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
sch.Validators[prop] = val
}

if sch.Validators["if"] != nil {
if ite, ok := sch.Validators["if"].(*If); ok {
if s, ok := sch.Validators["then"].(*Then); ok {
ite.Then = s
}
if s, ok := sch.Validators["else"].(*Else); ok {
ite.Else = s
}
}
}

// TODO - replace all these assertions with methods on Schema that return proper types
if sch.Validators["items"] != nil && sch.Validators["additionalItems"] != nil && !sch.Validators["items"].(*Items).single {
sch.Validators["additionalItems"].(*AdditionalItems).startIndex = len(sch.Validators["items"].(*Items).Schemas)
Expand All @@ -482,6 +552,9 @@ func (s *Schema) Validate(data interface{}) error {
return s.ref.Validate(data)
}

// TODO - so far all default.json tests pass when no use of "default" is made.
// Is this correct?

for _, v := range s.Validators {
if err := v.Validate(data); err != nil {
return err
Expand All @@ -490,12 +563,16 @@ func (s *Schema) Validate(data interface{}) error {
return nil
}

// Definitions implements a map of schemas while also satsfying the JSON
// traversal methods
type Definitions map[string]*Schema

// JSONProp implements the JSONPather for Definitions
func (d Definitions) JSONProp(name string) interface{} {
return d[name]
}

// JSONChildren implements the JSONContainer interface for Definitions
func (d Definitions) JSONChildren() (r map[string]JSONPather) {
r = map[string]JSONPather{}
// fmt.Println("getting children for definitions:", d)
Expand Down
Loading

0 comments on commit bef9c1e

Please sign in to comment.