-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathschema.go
358 lines (291 loc) · 9.57 KB
/
schema.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
package jtd
import (
"errors"
"reflect"
)
// Schema represents a JSON Typedef Schema.
type Schema struct {
Definitions map[string]Schema `json:"definitions"`
Metadata map[string]interface{} `json:"metadata"`
Nullable bool `json:"nullable"`
Ref *string `json:"ref"`
Type Type `json:"type"`
Enum []string `json:"enum"`
Elements *Schema `json:"elements"`
Properties map[string]Schema `json:"properties"`
OptionalProperties map[string]Schema `json:"optionalProperties"`
AdditionalProperties bool `json:"additionalProperties"`
Values *Schema `json:"values"`
Discriminator string `json:"discriminator"`
Mapping map[string]Schema `json:"mapping"`
}
// Type represents the values that the JSON Typedef "type" keyword can take on.
type Type string
const (
// TypeBoolean represents true or false.
TypeBoolean Type = "boolean"
// TypeFloat32 represents a JSON number. Code generators will create a
// single-precision floating point from this.
TypeFloat32 = "float32"
// TypeFloat64 represents a JSON number. Code generators will create a
// double-precision floating point from this.
TypeFloat64 = "float64"
// TypeInt8 represents a JSON number within the range of a int8.
TypeInt8 = "int8"
// TypeUint8 represents a JSON number within the range of a uint8.
TypeUint8 = "uint8"
// TypeInt16 represents a JSON number within the range of a int16.
TypeInt16 = "int16"
// TypeUint16 represents a JSON number within the range of a uint16.
TypeUint16 = "uint16"
// TypeInt32 represents a JSON number within the range of a int32.
TypeInt32 = "int32"
// TypeUint32 represents a JSON number within the range of a uint32.
TypeUint32 = "uint32"
// TypeString represents a JSON string.
TypeString = "string"
// TypeTimestamp represents a JSON string containing a RFC3339 timestamp.
TypeTimestamp = "timestamp"
)
// ErrInvalidForm indicates that a schema uses an invalid combination of
// keywords.
var ErrInvalidForm = errors.New("jtd: invalid form")
// ErrNonRootDefinition indicates that a schema uses the "definition" keyword
// outside of a root schema.
var ErrNonRootDefinition = errors.New("jtd: non-root definitions")
// ErrNoSuchDefinition indicates that a schema has a "ref" with no corresponding
// definition.
var ErrNoSuchDefinition = errors.New("jtd: ref to non-existent definition")
// ErrInvalidType indicates that a schema has a "type" keyword with an invalid
// value.
var ErrInvalidType = errors.New("jtd: invalid type")
// ErrEmptyEnum indicates that a schema has a "enum" keyword with no values.
var ErrEmptyEnum = errors.New("jtd: empty enum")
// ErrRepeatedEnumValue indicates that a schema has a "enum" keyword with
// repeated values.
var ErrRepeatedEnumValue = errors.New("jtd: enum contains repeated values")
// ErrSharedProperty indicates that a schema has the same property name in
// "properties" and "optionalProperties".
var ErrSharedProperty = errors.New("jtd: properties and optionalProperties share property")
// ErrNonPropertiesMapping indicates that a schema has a mapping value that
// isn't a schema of the properties form.
var ErrNonPropertiesMapping = errors.New("jtd: mapping value not of properties form")
// ErrMappingRepeatedDiscriminator indicates that a schema has a mapping value
// that has the same property as the discriminator it's within.
var ErrMappingRepeatedDiscriminator = errors.New("jtd: mapping re-specifies discriminator property")
// ErrNullableMapping indicates that a schema has a mapping value with
// "nullable" set to true.
var ErrNullableMapping = errors.New("jtd: mapping allows for nullable values")
// Validate returns an error if a schema is not a valid root JSON Typedef
// schema.
//
// Validate may return one of ErrInvalidForm, ErrNonRootDefinition,
// ErrNoSuchDefinition, ErrInvalidType, ErrEmptyEnum, ErrRepeatedEnumValue,
// ErrSharedProperty, ErrNonPropertiesMapping, ErrMappingRepeatedDiscriminator,
// or ErrNullableMapping.
func (s Schema) Validate() error {
return s.ValidateWithRoot(true, s)
}
// Index of valid form "signatures" -- i.e., combinations of the presence of the
// keywords (in order):
//
// ref type enum elements properties optionalProperties additionalProperties
// values discriminator mapping
//
// The keywords "definitions", "nullable", and "metadata" are not included here,
// because they would restrict nothing.
var validForms = [][]bool{
// Empty form
{false, false, false, false, false, false, false, false, false, false},
// Ref form
{true, false, false, false, false, false, false, false, false, false},
// Type form
{false, true, false, false, false, false, false, false, false, false},
// Enum form
{false, false, true, false, false, false, false, false, false, false},
// Elements form
{false, false, false, true, false, false, false, false, false, false},
// Properties form -- properties or optional properties or both, and never
// additional properties on its own
{false, false, false, false, true, false, false, false, false, false},
{false, false, false, false, false, true, false, false, false, false},
{false, false, false, false, true, true, false, false, false, false},
{false, false, false, false, true, false, true, false, false, false},
{false, false, false, false, false, true, true, false, false, false},
{false, false, false, false, true, true, true, false, false, false},
// Values form
{false, false, false, false, false, false, false, true, false, false},
// Discriminator form
{false, false, false, false, false, false, false, false, true, true},
}
// ValidateWithRoot returns an error if s is not a valid schema, given the root
// schema s is supposed to appear within.
//
// isRoot indicates whether the schema is expected to be a root schema. root is
// the root schema s is supposed to be contained within. If isRoot is true, then
// root should be equal to s for the return value to be meaningful.
func (s Schema) ValidateWithRoot(isRoot bool, root Schema) error {
formSignature := []bool{
s.Ref != nil,
s.Type != "",
s.Enum != nil,
s.Elements != nil,
s.Properties != nil,
s.OptionalProperties != nil,
s.AdditionalProperties,
s.Values != nil,
s.Discriminator != "",
s.Mapping != nil,
}
formOk := false
for _, form := range validForms {
formOk = formOk || reflect.DeepEqual(formSignature, form)
}
if !formOk {
return ErrInvalidForm
}
if s.Definitions != nil && !isRoot {
return ErrNonRootDefinition
}
for _, s := range s.Definitions {
if err := s.ValidateWithRoot(false, root); err != nil {
return err
}
}
if s.Ref != nil {
if root.Definitions == nil {
return ErrNoSuchDefinition
}
if _, ok := root.Definitions[*s.Ref]; !ok {
return ErrNoSuchDefinition
}
}
if s.Type != "" {
validTypes := []Type{
TypeBoolean,
TypeFloat32,
TypeFloat64,
TypeInt8,
TypeUint8,
TypeInt16,
TypeUint16,
TypeInt32,
TypeUint32,
TypeString,
TypeTimestamp,
}
ok := false
for _, t := range validTypes {
if s.Type == t {
ok = true
}
}
if !ok {
return ErrInvalidType
}
}
if s.Enum != nil {
if len(s.Enum) == 0 {
return ErrEmptyEnum
}
dedupe := map[string]struct{}{}
for _, value := range s.Enum {
if _, ok := dedupe[value]; ok {
return ErrRepeatedEnumValue
}
dedupe[value] = struct{}{}
}
}
if s.Elements != nil {
if err := s.Elements.ValidateWithRoot(false, root); err != nil {
return err
}
}
for k, p := range s.Properties {
if err := p.ValidateWithRoot(false, root); err != nil {
return err
}
if s.OptionalProperties != nil {
if _, ok := s.OptionalProperties[k]; ok {
return ErrSharedProperty
}
}
}
for _, s := range s.OptionalProperties {
if err := s.ValidateWithRoot(false, root); err != nil {
return err
}
}
if s.Values != nil {
if err := s.Values.ValidateWithRoot(false, root); err != nil {
return err
}
}
for _, m := range s.Mapping {
if err := m.ValidateWithRoot(false, root); err != nil {
return err
}
if m.Form() != FormProperties {
return ErrNonPropertiesMapping
}
if m.Properties != nil {
if _, ok := m.Properties[s.Discriminator]; ok {
return ErrMappingRepeatedDiscriminator
}
}
if m.OptionalProperties != nil {
if _, ok := m.OptionalProperties[s.Discriminator]; ok {
return ErrMappingRepeatedDiscriminator
}
}
if m.Nullable {
return ErrNullableMapping
}
}
return nil
}
// Form returns JSON Typedef schema form that s takes on.
func (s Schema) Form() Form {
if s.Ref != nil {
return FormRef
}
if s.Type != "" {
return FormType
}
if s.Enum != nil {
return FormEnum
}
if s.Elements != nil {
return FormElements
}
if s.Properties != nil || s.OptionalProperties != nil {
return FormProperties
}
if s.Values != nil {
return FormValues
}
if s.Mapping != nil {
return FormDiscriminator
}
return FormEmpty
}
// Form is an enumeration of the eight forms a JSON Typedef schema may take on.
type Form string
const (
// FormEmpty is the empty form.
FormEmpty Form = "empty"
// FormRef is the ref form.
FormRef = "ref"
// FormType is the type form.
FormType = "type"
// FormEnum is the enum form.
FormEnum = "enum"
// FormElements is the elements form.
FormElements = "elements"
// FormProperties is the properties form.
FormProperties = "properties"
// FormValues is the values form.
FormValues = "values"
// FormDiscriminator is the discriminator form.
FormDiscriminator = "discriminator"
)