mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
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:
+10
-2
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ type SubSchema struct {
|
||||
// hierarchy
|
||||
Parent *SubSchema
|
||||
ItemsChildren []*SubSchema
|
||||
itemsChildrenIsSingleSchema bool
|
||||
ItemsChildrenIsSingleSchema bool
|
||||
PropertiesChildren []*SubSchema
|
||||
|
||||
// validation : number / integer
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user