This repository has been archived by the owner on Jul 6, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 100
/
schema.go
617 lines (577 loc) · 20.3 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
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
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
package service
import (
"encoding/json"
"fmt"
"regexp"
)
const jsonSchemaVersion = "http://json-schema.org/draft-04/schema#"
// PlanSchemas is the root of a tree that encapsulates all plan-related schemas
// for validating input parameters to all service instance and service binding
// operations.
type PlanSchemas struct {
ServiceInstances InstanceSchemas `json:"service_instance,omitempty"`
ServiceBindings BindingSchemas `json:"service_binding,omitempty"`
}
// InstanceSchemas encapsulates all plan-related schemas for validating input
// parameters to all service instance operations.
type InstanceSchemas struct {
ProvisioningParametersSchema InputParametersSchema `json:"create,omitempty"`
UpdatingParametersSchema InputParametersSchema `json:"update,omitempty"`
}
// BindingSchemas encapsulates all plan-related schemas for validating input
// parameters to all service binding operations.
type BindingSchemas struct {
BindingParametersSchema InputParametersSchema `json:"create,omitempty"`
}
// InputParametersSchema encapsulates schema for validating input parameters
// to any single operation.
type InputParametersSchema struct {
RequiredProperties []string `json:"required,omitempty"`
SecureProperties []string `json:"-"`
PropertySchemas map[string]PropertySchema `json:"properties,omitempty"`
}
// GetPropertySchemas returns a map of subordinate property schemas
func (i InputParametersSchema) GetPropertySchemas() map[string]PropertySchema {
return i.PropertySchemas
}
// GetAdditionalPropertySchema returns the "additional" property schema-- the
// schema that defines and allows for arbitrary properties on an object
func (i InputParametersSchema) GetAdditionalPropertySchema() PropertySchema {
return nil
}
// MarshalJSON defines custom JSON marshaling for InputParametersSchema and
// introduces an intermediate "parameters" property which is required by the
// OSB spec.
func (i InputParametersSchema) MarshalJSON() ([]byte, error) {
type inputParametersSchema InputParametersSchema
type inputParametersSchemaWrapper struct {
Schema string `json:"$schema"`
Type string `json:"type"`
inputParametersSchema
Additional bool `json:"additionalProperties"`
}
return json.Marshal(
struct {
Parameters inputParametersSchemaWrapper `json:"parameters"`
}{
Parameters: inputParametersSchemaWrapper{
Schema: jsonSchemaVersion,
Type: "object",
inputParametersSchema: inputParametersSchema(i),
Additional: false,
},
},
)
}
// Validate validates the given map[string]interface{} again this schema
func (i InputParametersSchema) Validate(valMap map[string]interface{}) error {
for _, requiredProperty := range i.RequiredProperties {
_, ok := valMap[requiredProperty]
if !ok {
return NewValidationError(requiredProperty, "field is required")
}
}
for k, v := range valMap {
propertySchema, ok := i.PropertySchemas[k]
if !ok {
return NewValidationError(k, "unrecognized field")
}
if err := propertySchema.validate(k, v); err != nil {
return err
}
}
return nil
}
// PropertySchema is an interface for the schema of any kind of property.
type PropertySchema interface {
validate(context string, value interface{}) error
}
// CustomStringPropertyValidator is a function type that describes the signature
// for functions that provide custom validation logic for string properties.
type CustomStringPropertyValidator func(context, value string) error
// StringPropertySchema represents schema for a single string property
type StringPropertySchema struct {
Title string `json:"title,omitempty"` // nolint: lll
Description string `json:"description,omitempty"` // nolint: lll
MinLength *int `json:"minLength,omitempty"` // nolint: lll
MaxLength *int `json:"maxLength,omitempty"` // nolint: lll
AllowedValues []string `json:"enum,omitempty"`
AllowedPattern string `json:"pattern,omitempty"` // nolint: lll
CustomPropertyValidator CustomStringPropertyValidator `json:"-"`
DefaultValue string `json:"default,omitempty"` // nolint: lll
OneOf []EnumValue `json:"oneOf,omitempty"` // nolint: lll
}
// MarshalJSON provides functionality to marshal a StringPropertySchema to JSON
func (s StringPropertySchema) MarshalJSON() ([]byte, error) {
type stringPropertySchema StringPropertySchema
return json.Marshal(
struct {
Type string `json:"type"`
stringPropertySchema
}{
Type: "string",
stringPropertySchema: stringPropertySchema(s),
},
)
}
func (s StringPropertySchema) validate(
context string,
value interface{},
) error {
if value == nil {
return nil
}
val, ok := value.(string)
if !ok {
return NewValidationError(context, "field value is not of type string")
}
if s.MinLength != nil && len(val) < *s.MinLength {
return NewValidationError(
context,
fmt.Sprintf("field length is less than minimum %d", *s.MinLength),
)
}
if s.MaxLength != nil && len(val) > *s.MaxLength {
return NewValidationError(
context,
fmt.Sprintf("field length is greater than maximum %d", *s.MaxLength),
)
}
if len(s.AllowedValues) > 0 {
var found bool
for _, allowedValue := range s.AllowedValues {
if val == allowedValue {
found = true
break
}
}
if !found {
return NewValidationError(context, "field value is invalid")
}
}
if len(s.OneOf) > 0 {
var found bool
for _, allowedValue := range s.OneOf {
if val == allowedValue.Value {
found = true
break
}
}
if !found {
return NewValidationError(context, "field value is invalid")
}
}
if s.AllowedPattern != "" {
pattern := regexp.MustCompile(s.AllowedPattern)
if !pattern.MatchString(val) {
return NewValidationError(context, "field value is invalid")
}
}
if s.CustomPropertyValidator != nil {
return s.CustomPropertyValidator(context, val)
}
return nil
}
// EnumValue represents an enum item in the oneOf JSON schema collection
type EnumValue struct {
Value string
Title string
}
// MarshalJSON provides functionality to marshal an EnumValue to JSON
// according to JSON schema definition.
func (v EnumValue) MarshalJSON() ([]byte, error) {
return json.Marshal(
struct {
Enum []string `json:"enum"`
Title string `json:"title"`
}{
Title: v.Title,
Enum: []string{v.Value},
},
)
}
// CustomIntPropertyValidator is a function type that describes the signature
// for functions that provide custom validation logic for integer properties.
type CustomIntPropertyValidator func(context string, value int64) error
// IntPropertySchema represents schema for a single integer property
type IntPropertySchema struct {
Title string `json:"title,omitempty"` // nolint: lll
Description string `json:"description,omitempty"` // nolint: lll
MinValue *int64 `json:"minimum,omitempty"`
MaxValue *int64 `json:"maximum,omitempty"`
AllowedValues []int64 `json:"enum,omitempty"`
AllowedIncrement *int64 `json:"multipleOf,omitempty"` // nolint: lll
CustomPropertyValidator CustomIntPropertyValidator `json:"-"`
DefaultValue *int64 `json:"default,omitempty"`
}
// MarshalJSON provides functionality to marshal an IntPropertySchema to JSON
func (i IntPropertySchema) MarshalJSON() ([]byte, error) {
type intPropertySchema IntPropertySchema
return json.Marshal(
struct {
Type string `json:"type"`
intPropertySchema
}{
Type: "integer",
intPropertySchema: intPropertySchema(i),
},
)
}
func (i IntPropertySchema) validate(context string, value interface{}) error {
if value == nil {
return nil
}
var val int64
if floatVal, ok := value.(float64); ok {
val = int64(floatVal)
if floatVal != float64(val) {
return NewValidationError(context, "field value is not of type integer")
}
} else if floatVal, ok := value.(*float64); ok {
val = int64(*floatVal)
if *floatVal != float64(val) {
return NewValidationError(context, "field value is not of type integer")
}
} else if floatVal, ok := value.(float32); ok {
val = int64(floatVal)
if floatVal != float32(val) {
return NewValidationError(context, "field value is not of type integer")
}
} else if floatVal, ok := value.(*float32); ok {
val = int64(*floatVal)
if *floatVal != float32(val) {
return NewValidationError(context, "field value is not of type integer")
}
} else if intVal, ok := value.(int64); ok {
val = intVal
} else if intVal, ok := value.(*int64); ok {
val = *intVal
} else if intVal, ok := value.(int32); ok {
val = int64(intVal)
} else if intVal, ok := value.(*int32); ok {
val = int64(*intVal)
} else if intVal, ok := value.(int); ok {
val = int64(intVal)
} else if intVal, ok := value.(*int); ok {
val = int64(*intVal)
} else {
return NewValidationError(context, "field value is not of type integer")
}
if i.MinValue != nil && val < *i.MinValue {
return NewValidationError(
context,
fmt.Sprintf("field value is less than minimum %d", *i.MinValue),
)
}
if i.MaxValue != nil && val > *i.MaxValue {
return NewValidationError(
context,
fmt.Sprintf("field value is greater than maximum %d", *i.MaxValue),
)
}
if len(i.AllowedValues) > 0 {
var found bool
for _, allowedValue := range i.AllowedValues {
if val == allowedValue {
found = true
break
}
}
if !found {
return NewValidationError(context, "field value is invalid")
}
}
if i.AllowedIncrement != nil && val%*i.AllowedIncrement != 0 {
return NewValidationError(
context,
fmt.Sprintf("field value is not a multiple of %d", *i.AllowedIncrement),
)
}
if i.CustomPropertyValidator != nil {
return i.CustomPropertyValidator(context, val)
}
return nil
}
// CustomFloatPropertyValidator is a function type that describes the signature
// for functions that provide custom validation logic for float properties.
type CustomFloatPropertyValidator func(context string, value float64) error
// FloatPropertySchema represents schema for a single floating point property
type FloatPropertySchema struct {
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
MinValue *float64 `json:"minimum,omitempty"`
MaxValue *float64 `json:"maximum,omitempty"`
AllowedValues []float64 `json:"enum,omitempty"`
// krancour: AllowedIncrement is for the schema consumer's benefit only.
// Validation vis-a-vis AllowedIncrement is not currently supported because of
// floating point division errors. If you need this, write a custom property
// validator instead, test it well, and avoid floating-point division if you
// can.
AllowedIncrement *float64 `json:"multipleOf,omitempty"` // nolint: lll
CustomPropertyValidator CustomFloatPropertyValidator `json:"-"`
DefaultValue *float64 `json:"default,omitempty"` // nolint: lll
}
// MarshalJSON provides functionality to marshal a FloatPropertySchema to JSON
func (f FloatPropertySchema) MarshalJSON() ([]byte, error) {
type floatPropertySchema FloatPropertySchema
return json.Marshal(
struct {
Type string `json:"type"`
floatPropertySchema
}{
Type: "number",
floatPropertySchema: floatPropertySchema(f),
},
)
}
func (f FloatPropertySchema) validate(context string, value interface{}) error {
if value == nil {
return nil
}
var val float64
if floatVal, ok := value.(float64); ok {
val = floatVal
} else if floatVal, ok := value.(*float64); ok {
val = *floatVal
} else if floatVal, ok := value.(float32); ok {
val = float64(floatVal)
} else if floatVal, ok := value.(*float32); ok {
val = float64(*floatVal)
} else {
return NewValidationError(context, "field value is not of type float")
}
if f.MinValue != nil && val < *f.MinValue {
return NewValidationError(
context,
fmt.Sprintf("field value is less than minimum %f", *f.MinValue),
)
}
if f.MaxValue != nil && val > *f.MaxValue {
return NewValidationError(
context,
fmt.Sprintf("field value is greater than maximum %f", *f.MaxValue),
)
}
if len(f.AllowedValues) > 0 {
var found bool
for _, allowedValue := range f.AllowedValues {
if val == allowedValue {
found = true
break
}
}
if !found {
return NewValidationError(context, "field value is invalid")
}
}
// krancour: Currently not supported because of floating point division
// errors. If you need this, write a custom property validator instead, test
// it well, and avoid floating-point division if you can.
// if f.AllowedIncrement != nil && math.Mod(val, *f.AllowedIncrement) != 0 {
// return NewValidationError(
// context,
// fmt.Sprintf("field value is not a multiple of %f", *f.AllowedIncrement),
// )
// }
if f.CustomPropertyValidator != nil {
return f.CustomPropertyValidator(context, val)
}
return nil
}
// CustomObjectPropertyValidator is a function type that describes the signature
// for functions that provide custom validation logic for object properties.
type CustomObjectPropertyValidator func(
context string,
value map[string]interface{},
) error
// KeyedPropertySchemaContainer is an interface for any PropertySchema that
// contains an map of subordinate PropertySchemas. The existence of this
// interface alllows Params to treat InputParametersSchema and
// ObjectPropertySchema the same even though there are some differences between
// the two that are unimportant from Params' perspective.
type KeyedPropertySchemaContainer interface {
GetPropertySchemas() map[string]PropertySchema
GetAdditionalPropertySchema() PropertySchema
}
// ObjectPropertySchema represents the attributes of a complicated schema type
// that can have nested properties
type ObjectPropertySchema struct {
Title string `json:"title,omitempty"` // nolint: lll
Description string `json:"description,omitempty"` // nolint: lll
RequiredProperties []string `json:"required,omitempty"` // nolint: lll
PropertySchemas map[string]PropertySchema `json:"properties,omitempty"` // nolint: lll
Additional PropertySchema `json:"additionalProperties,omitempty"` // nolint: lll
CustomPropertyValidator CustomObjectPropertyValidator `json:"-"`
DefaultValue map[string]interface{} `json:"-"`
}
// GetPropertySchemas returns a map of subordinate property schemas
func (o ObjectPropertySchema) GetPropertySchemas() map[string]PropertySchema {
return o.PropertySchemas
}
// GetAdditionalPropertySchema returns the "additional" property schema-- the
// schema that defines and allows for arbitrary properties on an object
func (o ObjectPropertySchema) GetAdditionalPropertySchema() PropertySchema {
return o.Additional
}
// MarshalJSON provides functionality to marshal an ObjectPropertySchema to JSON
func (o ObjectPropertySchema) MarshalJSON() ([]byte, error) {
type objectPropertySchema ObjectPropertySchema
ops := objectPropertySchema(o)
if ops.Additional == nil {
ops.Additional = &falsePropertySchema{}
}
return json.Marshal(struct {
Type string `json:"type"`
objectPropertySchema
}{
Type: "object",
objectPropertySchema: ops,
})
}
func (o ObjectPropertySchema) validate(
context string,
value interface{},
) error {
if value == nil {
return nil
}
valMap, ok := value.(map[string]interface{})
if !ok {
return NewValidationError(context, "field value is not of type object")
}
for _, requiredProperty := range o.RequiredProperties {
_, ok := valMap[requiredProperty]
if !ok {
propertyContext := fmt.Sprintf("%s.%s", context, requiredProperty)
return NewValidationError(propertyContext, "field is required")
}
}
for k, v := range valMap {
propertySchema, ok := o.PropertySchemas[k]
propertyContext := fmt.Sprintf("%s.%s", context, k)
if ok {
if err := propertySchema.validate(propertyContext, v); err != nil {
return err
}
} else if o.Additional == nil {
return NewValidationError(propertyContext, "unrecognized field")
} else {
if err := o.Additional.validate(propertyContext, v); err != nil {
return err
}
}
}
if o.CustomPropertyValidator != nil {
return o.CustomPropertyValidator(context, valMap)
}
return nil
}
// CustomArrayPropertyValidator is a function type that describes the signature
// for functions that provide custom validation logic for array properties.
type CustomArrayPropertyValidator func(
context string,
value []interface{},
) error
// ArrayPropertySchema represents the attributes of an array type
type ArrayPropertySchema struct {
Title string `json:"title,omitempty"` // nolint: lll
Description string `json:"description,omitempty"` // nolint: lll
MinItems *int `json:"minItems,omitempty"` // nolint: lll
MaxItems *int `json:"maxItems,omitempty"` // nolint: lll
ItemsSchema PropertySchema `json:"items,omitempty"`
CustomPropertyValidator CustomArrayPropertyValidator `json:"-"`
DefaultValue []interface{} `json:"-"`
}
// MarshalJSON provides functionality to marshal an
// ArrayPropertySchema to JSON
func (a ArrayPropertySchema) MarshalJSON() ([]byte, error) {
type arrayPropertySchema ArrayPropertySchema
return json.Marshal(struct {
Type string `json:"type"`
arrayPropertySchema
}{
Type: "array",
arrayPropertySchema: arrayPropertySchema(a),
})
}
func (a ArrayPropertySchema) validate(context string, value interface{}) error {
if value == nil {
return nil
}
valArray, ok := value.([]interface{})
if !ok {
return NewValidationError(context, "field value is not of type array")
}
if a.MinItems != nil && len(valArray) < *a.MinItems {
return NewValidationError(
context,
fmt.Sprintf("field contains fewer than minimum elements %d", *a.MinItems),
)
}
if a.MaxItems != nil && len(valArray) > *a.MaxItems {
return NewValidationError(
context,
fmt.Sprintf(
"field contains greater than maximum elements %d",
*a.MaxItems,
),
)
}
if a.ItemsSchema != nil {
for i, val := range valArray {
itemContext := fmt.Sprintf("%s[%d]", context, i)
if err := a.ItemsSchema.validate(itemContext, val); err != nil {
return err
}
}
}
if a.CustomPropertyValidator != nil {
return a.CustomPropertyValidator(context, valArray)
}
return nil
}
// AddCommonSchema annotates a PlanSchema object with common attributes
// based on the given ServiceProperties
func (p *PlanSchemas) AddCommonSchema(sp ServiceProperties) {
if p.ServiceInstances.ProvisioningParametersSchema.PropertySchemas == nil {
p.ServiceInstances.ProvisioningParametersSchema.PropertySchemas =
map[string]PropertySchema{}
}
ps := p.ServiceInstances.ProvisioningParametersSchema.PropertySchemas
if sp.ParentServiceID == "" {
if sp.ChildServiceID != "" {
p.ServiceInstances.ProvisioningParametersSchema.RequiredProperties =
append(
p.ServiceInstances.ProvisioningParametersSchema.RequiredProperties,
"alias",
)
ps["alias"] = &StringPropertySchema{
Title: "Alias",
Description: "Alias to by which child services instances may " +
"reference this instance",
}
}
} else {
p.ServiceInstances.ProvisioningParametersSchema.RequiredProperties =
append(
p.ServiceInstances.ProvisioningParametersSchema.RequiredProperties,
"parentAlias",
)
ps["parentAlias"] = &StringPropertySchema{
Title: "Parent Alias",
Description: "Alias of the parent service instance",
}
}
}
// falsePropertySchema is used internally to deal with the fact that in JSON
// schema, additionalProperties can be a schema or it can be the bool value
// false.
type falsePropertySchema struct{}
// MarshalJSON provides functionality to marshal an ObjectPropertySchema to JSON
func (falsePropertySchema) MarshalJSON() ([]byte, error) {
return []byte("false"), nil
}
// No-op
func (falsePropertySchema) validate(string, interface{}) error {
return nil
}