mirror of https://github.com/databricks/cli.git
added support for additionalProperties = false for validation and a test for a map of struct
This commit is contained in:
parent
91617e50c1
commit
753c54e899
|
@ -19,34 +19,26 @@ type Schema struct {
|
||||||
// Type of the object
|
// Type of the object
|
||||||
Type JavascriptType `json:"type"`
|
Type JavascriptType `json:"type"`
|
||||||
|
|
||||||
// TODO: See what happens if these REQUIRED constraint is not satisfied
|
|
||||||
// Type == object if this is non empty
|
|
||||||
// keys are named properties of the object
|
// keys are named properties of the object
|
||||||
// values are json schema for the values of the named properties
|
// values are json schema for the values of the named properties
|
||||||
Properties map[string]*Schema `json:"properties,omitempty"`
|
Properties map[string]*Schema `json:"properties,omitempty"`
|
||||||
|
|
||||||
// REQUIRED: Type == array if this is non empty
|
|
||||||
// the schema for all values of the array
|
// the schema for all values of the array
|
||||||
Items *Schema `json:"items,omitempty"`
|
Items *Schema `json:"items,omitempty"`
|
||||||
|
|
||||||
// REQUIRED: Type == object if this is non empty
|
|
||||||
// the schema for any properties not mentioned in the Schema.Properties field.
|
// the schema for any properties not mentioned in the Schema.Properties field.
|
||||||
// we leverage this to validate Maps in bundle configuration
|
// we leverage this to validate Maps in bundle configuration
|
||||||
AdditionalProperties *Schema `json:"additionalProperties,omitempty"`
|
// OR
|
||||||
|
// a boolean type with value false
|
||||||
|
//
|
||||||
|
// Its type during runtime will either be *Schema or bool
|
||||||
|
AdditionalProperties interface{} `json:"additionalProperties,omitempty"`
|
||||||
|
|
||||||
// REQUIRED: Type == object if this is non empty
|
|
||||||
// required properties for the object. Any propertites listed here should
|
// required properties for the object. Any propertites listed here should
|
||||||
// also be listed in Schema.Properties
|
// also be listed in Schema.Properties
|
||||||
Required []string `json:"required,omitempty"`
|
Required []string `json:"required,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE about loops in golangType: Right now we error out if there is a loop
|
|
||||||
// in the types traversed when generating the json schema. This can be solved
|
|
||||||
// using $refs but there is complexity around making sure we do not create json
|
|
||||||
// schemas where properties indirectly refer to each other, which would be an
|
|
||||||
// invalid json schema. See https://json-schema.org/understanding-json-schema/structuring.html#recursion
|
|
||||||
// for more details
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This function translates golang types into json schema. Here is the mapping
|
This function translates golang types into json schema. Here is the mapping
|
||||||
between json schema types and golang types
|
between json schema types and golang types
|
||||||
|
@ -77,6 +69,7 @@ type Schema struct {
|
||||||
- []MyStruct -> {
|
- []MyStruct -> {
|
||||||
type: object
|
type: object
|
||||||
properties: {}
|
properties: {}
|
||||||
|
additionalProperties: false
|
||||||
}
|
}
|
||||||
for details visit: https://json-schema.org/understanding-json-schema/reference/object.html#properties
|
for details visit: https://json-schema.org/understanding-json-schema/reference/object.html#properties
|
||||||
*/
|
*/
|
||||||
|
@ -142,10 +135,15 @@ func errWithTrace(prefix string, trace *list.List) error {
|
||||||
return fmt.Errorf("[ERROR] " + prefix + ". traversal trace: " + traceString)
|
return fmt.Errorf("[ERROR] " + prefix + ". traversal trace: " + traceString)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A wrapper over toProperty function with checks for an cycles to avoid being
|
// A wrapper over toSchema function to detect cycles in the bundle config struct
|
||||||
// stuck in an loop when traversing the config struct
|
|
||||||
func safeToSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debugTrace *list.List) (*Schema, error) {
|
func safeToSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debugTrace *list.List) (*Schema, error) {
|
||||||
// detect cycles. Fail if a cycle is detected
|
// WE ERROR OUT IF THERE ARE CYCLES IN THE JSON SCHEMA
|
||||||
|
// There are mechanisms to deal with cycles though recursive identifiers in json
|
||||||
|
// schema. However if we use them, we would need to make sure we are able to detect
|
||||||
|
// cycles two properties (directly or indirectly) pointing to each other
|
||||||
|
//
|
||||||
|
// see: https://json-schema.org/understanding-json-schema/structuring.html#recursion
|
||||||
|
// for details
|
||||||
_, ok := seenTypes[golangType]
|
_, ok := seenTypes[golangType]
|
||||||
if ok {
|
if ok {
|
||||||
fmt.Println("[DEBUG] traceSet: ", seenTypes)
|
fmt.Println("[DEBUG] traceSet: ", seenTypes)
|
||||||
|
@ -244,7 +242,7 @@ func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debu
|
||||||
}
|
}
|
||||||
|
|
||||||
// case map
|
// case map
|
||||||
var additionalProperties *Schema
|
var additionalProperties interface{}
|
||||||
if golangType.Kind() == reflect.Map {
|
if golangType.Kind() == reflect.Map {
|
||||||
if golangType.Key().Kind() != reflect.String {
|
if golangType.Key().Kind() != reflect.String {
|
||||||
return nil, fmt.Errorf("only string keyed maps allowed")
|
return nil, fmt.Errorf("only string keyed maps allowed")
|
||||||
|
@ -285,6 +283,9 @@ func toSchema(golangType reflect.Type, seenTypes map[reflect.Type]struct{}, debu
|
||||||
// remove current field from debug trace
|
// remove current field from debug trace
|
||||||
back := debugTrace.Back()
|
back := debugTrace.Back()
|
||||||
debugTrace.Remove(back)
|
debugTrace.Remove(back)
|
||||||
|
|
||||||
|
// set Schema.AdditionalProperties to false
|
||||||
|
additionalProperties = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ import (
|
||||||
// TODO: Have a test that combines multiple different cases
|
// TODO: Have a test that combines multiple different cases
|
||||||
// TODO: have a test for what happens when omitempty in different cases: primitives, object, map, array
|
// TODO: have a test for what happens when omitempty in different cases: primitives, object, map, array
|
||||||
|
|
||||||
|
|
||||||
func TestIntSchema(t *testing.T) {
|
func TestIntSchema(t *testing.T) {
|
||||||
var elemInt int
|
var elemInt int
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
expected :=
|
expected :=
|
||||||
`{
|
` {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"bool_val": {
|
"bool_val": {
|
||||||
|
@ -154,7 +155,8 @@ func TestStructOfPrimitivesSchema(t *testing.T) {
|
||||||
"uint_val": {
|
"uint_val": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}`
|
}`
|
||||||
|
|
||||||
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
||||||
|
@ -200,11 +202,14 @@ func TestStructOfStructsSchema(t *testing.T) {
|
||||||
"b": {
|
"b": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"additionalProperties": false
|
||||||
}
|
|
||||||
}`
|
}`
|
||||||
|
|
||||||
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
||||||
|
@ -242,9 +247,11 @@ func TestStructOfMapsSchema(t *testing.T) {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"additionalProperties": false
|
||||||
}`
|
}`
|
||||||
|
|
||||||
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
||||||
|
@ -282,9 +289,11 @@ func TestStructOfSliceSchema(t *testing.T) {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
"additionalProperties": false
|
||||||
}`
|
}`
|
||||||
|
|
||||||
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
||||||
|
@ -314,6 +323,37 @@ func TestMapOfPrimitivesSchema(t *testing.T) {
|
||||||
assert.Equal(t, expected, string(jsonSchema))
|
assert.Equal(t, expected, string(jsonSchema))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMapOfStructSchema(t *testing.T) {
|
||||||
|
type Foo struct {
|
||||||
|
MyInt int `json:"my_int"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var elem map[string]Foo
|
||||||
|
|
||||||
|
schema, err := NewSchema(reflect.TypeOf(elem))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
jsonSchema, err := json.MarshalIndent(schema, " ", " ")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
expected :=
|
||||||
|
`{
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"my_int": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
t.Log("[DEBUG] actual: ", string(jsonSchema))
|
||||||
|
t.Log("[DEBUG] expected: ", expected)
|
||||||
|
assert.Equal(t, expected, string(jsonSchema))
|
||||||
|
}
|
||||||
|
|
||||||
func TestObjectSchema(t *testing.T) {
|
func TestObjectSchema(t *testing.T) {
|
||||||
type Person struct {
|
type Person struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|
Loading…
Reference in New Issue