forked from qri-io/jsonschema
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathkeywords_objects.go
454 lines (399 loc) · 13.9 KB
/
keywords_objects.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
package jsonschema
import (
"encoding/json"
"fmt"
"github.com/qri-io/jsonpointer"
"regexp"
"strconv"
)
// MaxProperties MUST be a non-negative integer.
// An object instance is valid against "MaxProperties" if its number of Properties is less than, or equal to, the value of this keyword.
type MaxProperties int
// NewMaxProperties allocates a new MaxProperties validator
func NewMaxProperties() Validator {
return new(MaxProperties)
}
// 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))
}
}
}
// minProperties MUST be a non-negative integer.
// An object instance is valid against "minProperties" if its number of Properties is greater than, or equal to, the value of this keyword.
// Omitting this keyword has the same behavior as a value of 0.
type minProperties int
// NewMinProperties allocates a new MinProperties validator
func NewMinProperties() Validator {
return new(minProperties)
}
// 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))
}
}
}
// Required ensures that for a given object instance, every item in the array is the name of a property in the instance.
// The value of this keyword MUST be an array. Elements of this array, if any, MUST be strings, and MUST be unique.
// Omitting this keyword has the same behavior as an empty array.
type Required []string
// NewRequired allocates a new Required validator
func NewRequired() Validator {
return &Required{}
}
// 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 {
for _, key := range r {
if val, ok := obj[key]; val == nil && !ok {
AddError(errs, propPath, data, fmt.Sprintf(`"%s" value is required`, key))
}
}
}
}
// JSONProp implements JSON property name indexing for Required
func (r Required) JSONProp(name string) interface{} {
idx, err := strconv.Atoi(name)
if err != nil {
return nil
}
if idx > len(r) || idx < 0 {
return nil
}
return r[idx]
}
// Properties MUST be an object. Each value of this object MUST be a valid JSON Schema.
// This keyword determines how child instances validate for objects, and does not directly validate
// the immediate instance itself.
// Validation succeeds if, for each name that appears in both the instance and as a name within this
// keyword's value, the child instance for that name successfully validates against the corresponding schema.
// Omitting this keyword has the same behavior as an empty object.
type Properties map[string]*Schema
// NewProperties allocates a new Properties validator
func NewProperties() Validator {
return &Properties{}
}
// Validate implements the validator interface for Properties
func (p Properties) Validate(propPath string, data interface{}, 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 p[key] != nil {
d, _ := jp.Descendant(key)
p[key].Validate(d.String(), val, errs)
}
}
}
}
// JSONProp implements JSON property name indexing for Properties
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 {
res[key] = sch
}
return
}
// PatternProperties determines how child instances validate for objects, and does not directly validate the immediate instance itself.
// Validation of the primitive instance type against this keyword always succeeds.
// Validation succeeds if, for each instance name that matches any regular expressions that appear as a property name in this
// keyword's value, the child instance for that name successfully validates against each schema that corresponds to a matching
// regular expression.
// Each property name of this object SHOULD be a valid regular expression,
// according to the ECMA 262 regular expression dialect.
// Each property value of this object MUST be a valid JSON Schema.
// Omitting this keyword has the same behavior as an empty object.
type PatternProperties []patternSchema
// NewPatternProperties allocates a new PatternProperties validator
func NewPatternProperties() Validator {
return &PatternProperties{}
}
type patternSchema struct {
key string
re *regexp.Regexp
schema *Schema
}
// Validate implements the validator interface for PatternProperties
func (p PatternProperties) Validate(propPath string, data interface{}, 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 {
for _, ptn := range p {
if ptn.re.Match([]byte(key)) {
d, _ := jp.Descendant(key)
ptn.schema.Validate(d.String(), val, errs)
}
}
}
}
return
}
// JSONProp implements JSON property name indexing for PatternProperties
func (p PatternProperties) JSONProp(name string) interface{} {
for _, pp := range p {
if pp.key == name {
return pp.schema
}
}
return nil
}
// JSONChildren implements the JSONContainer interface for PatternProperties
func (p PatternProperties) JSONChildren() (res map[string]JSONPather) {
res = map[string]JSONPather{}
for i, pp := range p {
res[strconv.Itoa(i)] = pp.schema
}
return
}
// UnmarshalJSON implements the json.Unmarshaler interface for PatternProperties
func (p *PatternProperties) UnmarshalJSON(data []byte) error {
var props map[string]*Schema
if err := json.Unmarshal(data, &props); err != nil {
return err
}
ptn := make(PatternProperties, len(props))
i := 0
for key, sch := range props {
re, err := regexp.Compile(key)
if err != nil {
return fmt.Errorf("invalid pattern: %s: %s", key, err.Error())
}
ptn[i] = patternSchema{
key: key,
re: re,
schema: sch,
}
i++
}
*p = ptn
return nil
}
// MarshalJSON implements json.Marshaler for PatternProperties
func (p PatternProperties) MarshalJSON() ([]byte, error) {
obj := map[string]interface{}{}
for _, prop := range p {
obj[prop.key] = prop.schema
}
return json.Marshal(obj)
}
// AdditionalProperties determines how child instances validate for objects, and does not directly validate the immediate instance itself.
// Validation with "AdditionalProperties" applies only to the child values of instance names that do not match any names in "Properties",
// and do not match any regular expression in "PatternProperties".
// For all such Properties, validation succeeds if the child instance validates against the "AdditionalProperties" schema.
// Omitting this keyword has the same behavior as an empty schema.
type AdditionalProperties struct {
Properties *Properties
patterns *PatternProperties
Schema *Schema
}
// NewAdditionalProperties allocates a new AdditionalProperties validator
func NewAdditionalProperties() Validator {
return &AdditionalProperties{}
}
// Validate implements the validator interface for AdditionalProperties
func (ap AdditionalProperties) Validate(propPath string, data interface{}, 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 {
KEYS:
for key, val := range obj {
if ap.Properties != nil {
for propKey := range *ap.Properties {
if propKey == key {
continue KEYS
}
}
}
if ap.patterns != nil {
for _, ptn := range *ap.patterns {
if ptn.re.Match([]byte(key)) {
continue KEYS
}
}
}
// c := len(*errs)
d, _ := jp.Descendant(key)
ap.Schema.Validate(d.String(), val, errs)
// if len(*errs) > c {
// // fmt.Sprintf("object key %s AdditionalProperties error: %s", key, err.Error())
// return
// }
}
}
}
// UnmarshalJSON implements the json.Unmarshaler interface for AdditionalProperties
func (ap *AdditionalProperties) UnmarshalJSON(data []byte) error {
sch := &Schema{}
if err := json.Unmarshal(data, sch); err != nil {
return err
}
// fmt.Println("unmarshal:", sch.Ref)
*ap = AdditionalProperties{Schema: sch}
return nil
}
// JSONProp implements JSON property name indexing for AdditionalProperties
func (ap *AdditionalProperties) JSONProp(name string) interface{} {
return ap.Schema.JSONProp(name)
}
// JSONChildren implements the JSONContainer interface for AdditionalProperties
func (ap *AdditionalProperties) JSONChildren() (res map[string]JSONPather) {
if ap.Schema.Ref != "" {
return map[string]JSONPather{"$ref": ap.Schema}
}
return ap.Schema.JSONChildren()
}
// MarshalJSON implements json.Marshaler for AdditionalProperties
func (ap AdditionalProperties) MarshalJSON() ([]byte, error) {
return json.Marshal(ap.Schema)
}
// Dependencies : [CREF1]
// This keyword specifies rules that are evaluated if the instance is an object and contains a
// certain property.
// This keyword's value MUST be an object. Each property specifies a Dependency.
// Each Dependency value MUST be an array or a valid JSON Schema.
// If the Dependency value is a subschema, and the Dependency key is a property in the instance,
// the entire instance must validate against the Dependency value.
// If the Dependency value is an array, each element in the array, if any, MUST be a string,
// and MUST be unique. If the Dependency key is a property in the instance, each of the items
// in the Dependency value must be a property that exists in the instance.
// Omitting this keyword has the same behavior as an empty object.
type Dependencies map[string]Dependency
// NewDependencies allocates a new Dependencies validator
func NewDependencies() Validator {
return &Dependencies{}
}
// Validate implements the validator interface for Dependencies
func (d Dependencies) Validate(propPath string, data interface{}, 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 d {
if obj[key] != nil {
d, _ := jp.Descendant(key)
val.Validate(d.String(), obj, errs)
}
}
}
return
}
// JSONProp implements JSON property name indexing for Dependencies
func (d Dependencies) JSONProp(name string) interface{} {
return d[name]
}
// JSONChildren implements the JSONContainer interface for Dependencies
// func (d Dependencies) JSONChildren() (res map[string]JSONPather) {
// res = map[string]JSONPather{}
// for key, dep := range d {
// if dep.schema != nil {
// res[key] = dep.schema
// }
// }
// return
// }
// Dependency is an instance used only in the Dependencies proprty
type Dependency struct {
schema *Schema
props []string
}
// 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 {
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 {
AddError(errs, propPath, data, fmt.Sprintf("Dependency property %s is Required", k))
}
}
}
}
}
// UnmarshalJSON implements the json.Unmarshaler interface for Dependencies
func (d *Dependency) UnmarshalJSON(data []byte) error {
props := []string{}
if err := json.Unmarshal(data, &props); err == nil {
*d = Dependency{props: props}
return nil
}
sch := &Schema{}
err := json.Unmarshal(data, sch)
if err == nil {
*d = Dependency{schema: sch}
}
return err
}
// MarshalJSON implements json.Marshaler for Dependency
func (d Dependency) MarshalJSON() ([]byte, error) {
if d.schema != nil {
return json.Marshal(d.schema)
}
return json.Marshal(d.props)
}
// PropertyNames checks if every property name in the instance validates against the provided schema
// if the instance is an object.
// Note the property name that the schema is testing will always be a string.
// Omitting this keyword has the same behavior as an empty schema.
type PropertyNames Schema
// NewPropertyNames allocates a new PropertyNames validator
func NewPropertyNames() Validator {
return &PropertyNames{}
}
// Validate implements the validator interface for PropertyNames
func (p PropertyNames) Validate(propPath string, data interface{}, errs *[]ValError) {
jp, err := jsonpointer.Parse(propPath)
if err != nil {
AddError(errs, propPath, nil, "invalid property path")
return
}
sch := Schema(p)
if obj, ok := data.(map[string]interface{}); ok {
for key := range obj {
// TODO - adjust error message & prop path
d, _ := jp.Descendant(key)
sch.Validate(d.String(), key, errs)
}
}
}
// JSONProp implements JSON property name indexing for Properties
func (p PropertyNames) JSONProp(name string) interface{} {
return Schema(p).JSONProp(name)
}
// JSONChildren implements the JSONContainer interface for PropertyNames
func (p PropertyNames) JSONChildren() (res map[string]JSONPather) {
return Schema(p).JSONChildren()
}
// UnmarshalJSON implements the json.Unmarshaler interface for PropertyNames
func (p *PropertyNames) UnmarshalJSON(data []byte) error {
var sch Schema
if err := json.Unmarshal(data, &sch); err != nil {
return err
}
*p = PropertyNames(sch)
return nil
}
// MarshalJSON implements json.Marshaler for PropertyNames
func (p PropertyNames) MarshalJSON() ([]byte, error) {
return json.Marshal(Schema(p))
}