ast: Treat array.items as dynamic element type (#3477)

This commit fixes the conversion to treat array.items schema as the
dynamic element type in arrays as opposed to the static element
type. If array.items is defined as an object then it applies to ALL
elements in the array. On the other hand, if array.items is defined as
an array then it applies pairwise to the elements in the array.

In order to fix this, we have to expose a new field from the schema library.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
Torin Sandall
2021-05-21 07:38:02 -04:00
committed by GitHub
parent db726336bd
commit 7c9402cd5f
5 changed files with 50 additions and 10 deletions
+10 -2
View File
@@ -902,7 +902,7 @@ func parseSchema(schema interface{}) (types.Type, error) {
return types.N, nil
} else if subSchema.Types.Contains("object") {
if subSchema.PropertiesChildren != nil && len(subSchema.PropertiesChildren) > 0 {
if len(subSchema.PropertiesChildren) > 0 {
staticProps := make([]*types.StaticProperty, 0, len(subSchema.PropertiesChildren))
for _, pSchema := range subSchema.PropertiesChildren {
newtype, err := parseSchema(pSchema)
@@ -916,7 +916,15 @@ func parseSchema(schema interface{}) (types.Type, error) {
return types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)), nil
} else if subSchema.Types.Contains("array") {
if subSchema.ItemsChildren != nil && len(subSchema.ItemsChildren) > 0 {
if len(subSchema.ItemsChildren) > 0 {
if subSchema.ItemsChildrenIsSingleSchema {
iSchema := subSchema.ItemsChildren[0]
newtype, err := parseSchema(iSchema)
if err != nil {
return nil, fmt.Errorf("unexpected schema type %v", iSchema)
}
return types.NewArray(nil, newtype), nil
}
newTypes := make([]types.Type, 0, len(subSchema.ItemsChildren))
for i := 0; i != len(subSchema.ItemsChildren); i++ {
iSchema := subSchema.ItemsChildren[i]
+36 -4
View File
@@ -8,6 +8,8 @@ import (
)
func testParseSchema(t *testing.T, schema string, expectedType types.Type) {
t.Helper()
var sch interface{}
err := util.Unmarshal([]byte(schema), &sch)
if err != nil {
@@ -26,15 +28,14 @@ func testParseSchema(t *testing.T, schema string, expectedType types.Type) {
}
func TestParseSchemaObject(t *testing.T) {
//Expected type is: object<b: array<object<a: number, b: array<number>, c: any>>, foo: string>
innerObjectStaticProps := []*types.StaticProperty{}
innerObjectStaticProps = append(innerObjectStaticProps, &types.StaticProperty{Key: "a", Value: types.N})
innerObjectStaticProps = append(innerObjectStaticProps, &types.StaticProperty{Key: "b", Value: types.NewArray([]types.Type{types.N}, nil)})
innerObjectStaticProps = append(innerObjectStaticProps, &types.StaticProperty{Key: "b", Value: types.NewArray(nil, types.N)})
innerObjectStaticProps = append(innerObjectStaticProps, &types.StaticProperty{Key: "c", Value: types.A})
innerObjectType := types.NewObject(innerObjectStaticProps, nil)
staticProps := []*types.StaticProperty{}
staticProps = append(staticProps, &types.StaticProperty{Key: "b", Value: types.NewArray([]types.Type{innerObjectType}, nil)})
staticProps = append(staticProps, &types.StaticProperty{Key: "b", Value: types.NewArray(nil, innerObjectType)})
staticProps = append(staticProps, &types.StaticProperty{Key: "foo", Value: types.S})
expectedType := types.NewObject(staticProps, nil)
@@ -54,7 +55,7 @@ func TestSetTypesWithSchemaRef(t *testing.T) {
if newtype == nil {
t.Fatalf("parseSchema returned nil type")
}
if newtype.String() != "object<apiVersion: string, kind: string, metadata: object<annotations: object[any: any], clusterName: string, creationTimestamp: string, deletionGracePeriodSeconds: number, deletionTimestamp: string, finalizers: array<string>, generateName: string, generation: number, initializers: object<pending: array<object<name: string>>, result: object<apiVersion: string, code: number, details: object<causes: array<object<field: string, message: string, reason: string>>, group: string, kind: string, name: string, retryAfterSeconds: number, uid: string>, kind: string, message: string, metadata: object<continue: string, resourceVersion: string, selfLink: string>, reason: string, status: string>>, labels: object[any: any], managedFields: array<object<apiVersion: string, fields: object[any: any], manager: string, operation: string, time: string>>, name: string, namespace: string, ownerReferences: array<object<apiVersion: string, blockOwnerDeletion: boolean, controller: boolean, kind: string, name: string, uid: string>>, resourceVersion: string, selfLink: string, uid: string>>" {
if newtype.String() != "object<apiVersion: string, kind: string, metadata: object<annotations: object[any: any], clusterName: string, creationTimestamp: string, deletionGracePeriodSeconds: number, deletionTimestamp: string, finalizers: array[string], generateName: string, generation: number, initializers: object<pending: array[object<name: string>], result: object<apiVersion: string, code: number, details: object<causes: array[object<field: string, message: string, reason: string>], group: string, kind: string, name: string, retryAfterSeconds: number, uid: string>, kind: string, message: string, metadata: object<continue: string, resourceVersion: string, selfLink: string>, reason: string, status: string>>, labels: object[any: any], managedFields: array[object<apiVersion: string, fields: object[any: any], manager: string, operation: string, time: string>], name: string, namespace: string, ownerReferences: array[object<apiVersion: string, blockOwnerDeletion: boolean, controller: boolean, kind: string, name: string, uid: string>], resourceVersion: string, selfLink: string, uid: string>>" {
t.Fatalf("parseSchema returned an incorrect type: %s", newtype.String())
}
}
@@ -119,6 +120,37 @@ func TestParseSchemaBasics(t *testing.T) {
schema: `{"type": "number"}`,
exp: types.N,
},
{
note: "array of objects",
schema: `{
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"value": {"type": "number"}
}
}
}`,
exp: types.NewArray(nil, types.NewObject([]*types.StaticProperty{
types.NewStaticProperty("id", types.S),
types.NewStaticProperty("value", types.N),
}, nil)),
},
{
note: "static array items",
schema: `{
"type": "array",
"items": [
{"type": "string"},
{"type": "number"}
]
}`,
exp: types.NewArray([]types.Type{
types.S,
types.N,
}, nil),
},
}
for _, tc := range tests {
+2 -2
View File
@@ -314,7 +314,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
default:
return invalidType(StringSchema+"/"+StringArrayOfSchemas, KeyItems)
}
currentSchema.itemsChildrenIsSingleSchema = false
currentSchema.ItemsChildrenIsSingleSchema = false
}
case map[string]interface{}, bool:
newSchema := &SubSchema{Parent: currentSchema, Property: KeyItems}
@@ -324,7 +324,7 @@ func (d *Schema) parseSchema(documentNode interface{}, currentSchema *SubSchema)
if err != nil {
return err
}
currentSchema.itemsChildrenIsSingleSchema = true
currentSchema.ItemsChildrenIsSingleSchema = true
default:
return invalidType(StringSchema+"/"+StringArrayOfSchemas, KeyItems)
}
+1 -1
View File
@@ -102,7 +102,7 @@ type SubSchema struct {
// hierarchy
Parent *SubSchema
ItemsChildren []*SubSchema
itemsChildrenIsSingleSchema bool
ItemsChildrenIsSingleSchema bool
PropertiesChildren []*SubSchema
// validation : number / integer
+1 -1
View File
@@ -462,7 +462,7 @@ func (v *SubSchema) validateArray(currentSubSchema *SubSchema, value []interface
nbValues := len(value)
// TODO explain
if currentSubSchema.itemsChildrenIsSingleSchema {
if currentSubSchema.ItemsChildrenIsSingleSchema {
for i := range value {
subContext := NewJSONContext(strconv.Itoa(i), context)
validationResult := currentSubSchema.ItemsChildren[0].subValidateWithContext(value[i], subContext)