Skip to content

Commit bef9c1e

Browse files
committed
feat(IfThenElse): implement If/Then/Else, cleanup
1 parent a99baf2 commit bef9c1e

File tree

9 files changed

+187
-60
lines changed

9 files changed

+187
-60
lines changed

keywords.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ func (t Type) Validate(data interface{}) error {
6060
}
6161
if len(t) == 1 {
6262
return fmt.Errorf(`expected "%v" to be of type %s`, data, t[0])
63-
} else {
64-
str := ""
65-
for _, ts := range t {
66-
str += ts + ","
67-
}
68-
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
6963
}
64+
65+
str := ""
66+
for _, ts := range t {
67+
str += ts + ","
68+
}
69+
return fmt.Errorf(`expected "%v" to be one of type: %s`, data, str[:len(str)-1])
7070
}
7171

7272
// JSONProp implements JSON property name indexing for Type

keywords_arrays.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func (it Items) JSONProp(name string) interface{} {
5656
return it.Schemas[idx]
5757
}
5858

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

204205
// JSONProp implements JSON property name indexing for Contains
205-
func (m Contains) JSONProp(name string) interface{} {
206-
return Schema(m).JSONProp(name)
206+
func (c Contains) JSONProp(name string) interface{} {
207+
return Schema(c).JSONProp(name)
207208
}
208209

209210
// UnmarshalJSON implements the json.Unmarshaler interface for Contains

keywords_booleans.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,15 @@ func (a AllOf) JSONProp(name string) interface{} {
3232
return a[idx]
3333
}
3434

35+
// JSONChildren implements the JSONContainer interface for AllOf
36+
func (a AllOf) JSONChildren() (res map[string]JSONPather) {
37+
res = map[string]JSONPather{}
38+
for i, sch := range a {
39+
res[strconv.Itoa(i)] = sch
40+
}
41+
return
42+
}
43+
3544
// AnyOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
3645
// An instance validates successfully against this keyword if it validates successfully against at
3746
// least one schema defined by this keyword's value.
@@ -59,6 +68,15 @@ func (a AnyOf) JSONProp(name string) interface{} {
5968
return a[idx]
6069
}
6170

71+
// JSONChildren implements the JSONContainer interface for AnyOf
72+
func (a AnyOf) JSONChildren() (res map[string]JSONPather) {
73+
res = map[string]JSONPather{}
74+
for i, sch := range a {
75+
res[strconv.Itoa(i)] = sch
76+
}
77+
return
78+
}
79+
6280
// OneOf MUST be a non-empty array. Each item of the array MUST be a valid JSON Schema.
6381
// An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.
6482
type OneOf []*Schema
@@ -92,6 +110,15 @@ func (o OneOf) JSONProp(name string) interface{} {
92110
return o[idx]
93111
}
94112

113+
// JSONChildren implements the JSONContainer interface for OneOf
114+
func (o OneOf) JSONChildren() (res map[string]JSONPather) {
115+
res = map[string]JSONPather{}
116+
for i, sch := range o {
117+
res[strconv.Itoa(i)] = sch
118+
}
119+
return
120+
}
121+
95122
// Not MUST be a valid JSON Schema.
96123
// An instance is valid against this keyword if it fails to validate successfully against the schema defined
97124
// by this keyword.

keywords_conditionals.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,33 @@ import (
88
// Instances that successfully validate against this keyword's subschema MUST also be valid against the subschema value of the "then" keyword, if present.
99
// Instances that fail to validate against this keyword's subschema MUST also be valid against the subschema value of the "else" keyword.
1010
// Validation of the instance against this keyword on its own always succeeds, regardless of the validation outcome of against its subschema.
11-
type If Schema
11+
type If struct {
12+
Schema Schema
13+
Then *Then
14+
Else *Else
15+
}
1216

1317
// Validate implements the Validator interface for If
1418
func (i *If) Validate(data interface{}) error {
19+
if err := i.Schema.Validate(data); err == nil {
20+
if i.Then != nil {
21+
s := Schema(*i.Then)
22+
sch := &s
23+
return sch.Validate(data)
24+
}
25+
} else {
26+
if i.Else != nil {
27+
s := Schema(*i.Else)
28+
sch := &s
29+
return sch.Validate(data)
30+
}
31+
}
1532
return nil
1633
}
1734

1835
// JSONProp implements JSON property name indexing for If
1936
func (i If) JSONProp(name string) interface{} {
20-
return Schema(i).JSONProp(name)
37+
return Schema(i.Schema).JSONProp(name)
2138
}
2239

2340
// UnmarshalJSON implements the json.Unmarshaler interface for If
@@ -26,7 +43,7 @@ func (i *If) UnmarshalJSON(data []byte) error {
2643
if err := json.Unmarshal(data, &sch); err != nil {
2744
return err
2845
}
29-
*i = If(sch)
46+
*i = If{Schema: sch}
3047
return nil
3148
}
3249

keywords_objects.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func (p Properties) JSONProp(name string) interface{} {
9292
return p[name]
9393
}
9494

95+
// JSONChildren implements the JSONContainer interface for Properties
9596
func (p Properties) JSONChildren() (res map[string]JSONPather) {
9697
res = map[string]JSONPather{}
9798
for key, sch := range p {

schema.go

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"encoding/json"
1010
"fmt"
1111
"github.com/qri-io/jsonpointer"
12+
// "io/ioutil"
13+
"net/http"
1214
"net/url"
1315
)
1416

@@ -73,9 +75,7 @@ func (rs *RootSchema) UnmarshalJSON(data []byte) error {
7375
if err := walkJSON(sch, func(elem JSONPather) error {
7476
if sch, ok := elem.(*Schema); ok {
7577
if sch.Ref != "" {
76-
// fmt.Println(sch.Ref, ids[sch.Ref])
7778
if ids[sch.Ref] != nil {
78-
fmt.Println("using id:", sch.Ref)
7979
sch.ref = ids[sch.Ref]
8080
return nil
8181
}
@@ -107,16 +107,68 @@ func (rs *RootSchema) UnmarshalJSON(data []byte) error {
107107
return nil
108108
}
109109

110-
func (rs *RootSchema) ValdiateBytes(data []byte) error {
110+
// FetchRemoteReferences grabs any url-based schema references that cannot
111+
// be locally resolved via network requests
112+
func (rs *RootSchema) FetchRemoteReferences() error {
113+
sch := &rs.Schema
114+
115+
// collect IDs for internal referencing:
116+
refs := map[string]*Schema{}
117+
if err := walkJSON(sch, func(elem JSONPather) error {
118+
if sch, ok := elem.(*Schema); ok {
119+
ref := sch.Ref
120+
if ref != "" {
121+
if refs[ref] == nil {
122+
if u, err := url.Parse(ref); err == nil {
123+
if res, err := http.Get(u.String()); err == nil {
124+
s := &RootSchema{}
125+
if err := json.NewDecoder(res.Body).Decode(s); err != nil {
126+
return err
127+
}
128+
refs[ref] = &s.Schema
129+
sch.ref = refs[ref]
130+
}
131+
}
132+
}
133+
}
134+
}
135+
return nil
136+
}); err != nil {
137+
return err
138+
}
139+
140+
// pass a pointer to the schema component in here (instead of the RootSchema struct)
141+
// to ensure root is evaluated for references
142+
if err := walkJSON(sch, func(elem JSONPather) error {
143+
if sch, ok := elem.(*Schema); ok {
144+
if sch.Ref != "" && refs[sch.Ref] != nil {
145+
if refs[sch.Ref] != nil {
146+
fmt.Println("using remote ref:", sch.Ref)
147+
sch.ref = refs[sch.Ref]
148+
}
149+
return nil
150+
}
151+
}
152+
return nil
153+
}); err != nil {
154+
return err
155+
}
156+
157+
rs.Schema = *sch
158+
return nil
159+
}
160+
161+
// ValidateBytes performs schema validation against a slice of json byte data
162+
func (rs *RootSchema) ValidateBytes(data []byte) error {
111163
var doc interface{}
112164
if err := json.Unmarshal(data, &doc); err != nil {
113165
return err
114166
}
115167
return rs.Validate(doc)
116168
}
117169

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

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

266322
// JSONProp implements the JSONPather for Schema
@@ -286,6 +342,8 @@ func (s Schema) JSONProp(name string) interface{} {
286342
return s.Ref
287343
case "definitions":
288344
return s.Definitions
345+
case "format":
346+
return s.Format
289347
default:
290348
prop := s.Validators[name]
291349
if prop == nil && s.extraDefinitions[name] != nil {
@@ -295,7 +353,7 @@ func (s Schema) JSONProp(name string) interface{} {
295353
}
296354
}
297355

298-
// JSONChildren implements the JSONPather for Schema
356+
// JSONChildren implements the JSONContainer interface for Schema
299357
func (s *Schema) JSONChildren() (ch map[string]JSONPather) {
300358
ch = map[string]JSONPather{}
301359

@@ -350,6 +408,7 @@ func (s *Schema) UnmarshalJSON(data []byte) error {
350408
Comment: _s.Comment,
351409
Ref: _s.Ref,
352410
Definitions: _s.Definitions,
411+
Format: _s.Format,
353412
Validators: map[string]Validator{},
354413
}
355414

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

523+
if sch.Validators["if"] != nil {
524+
if ite, ok := sch.Validators["if"].(*If); ok {
525+
if s, ok := sch.Validators["then"].(*Then); ok {
526+
ite.Then = s
527+
}
528+
if s, ok := sch.Validators["else"].(*Else); ok {
529+
ite.Else = s
530+
}
531+
}
532+
}
533+
464534
// TODO - replace all these assertions with methods on Schema that return proper types
465535
if sch.Validators["items"] != nil && sch.Validators["additionalItems"] != nil && !sch.Validators["items"].(*Items).single {
466536
sch.Validators["additionalItems"].(*AdditionalItems).startIndex = len(sch.Validators["items"].(*Items).Schemas)
@@ -482,6 +552,9 @@ func (s *Schema) Validate(data interface{}) error {
482552
return s.ref.Validate(data)
483553
}
484554

555+
// TODO - so far all default.json tests pass when no use of "default" is made.
556+
// Is this correct?
557+
485558
for _, v := range s.Validators {
486559
if err := v.Validate(data); err != nil {
487560
return err
@@ -490,12 +563,16 @@ func (s *Schema) Validate(data interface{}) error {
490563
return nil
491564
}
492565

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

570+
// JSONProp implements the JSONPather for Definitions
495571
func (d Definitions) JSONProp(name string) interface{} {
496572
return d[name]
497573
}
498574

575+
// JSONChildren implements the JSONContainer interface for Definitions
499576
func (d Definitions) JSONChildren() (r map[string]JSONPather) {
500577
r = map[string]JSONPather{}
501578
// fmt.Println("getting children for definitions:", d)

0 commit comments

Comments
 (0)