mirror of
https://github.com/open-policy-agent/opa.git
synced 2026-08-12 19:32:48 -06:00
builtins: add object.union_n
Fixes: #4012 Signed-off-by: Anders Eknert <anders@eknert.com>
This commit is contained in:
@@ -157,6 +157,7 @@ var DefaultBuiltins = [...]*Builtin{
|
||||
|
||||
// Object Manipulation
|
||||
ObjectUnion,
|
||||
ObjectUnionN,
|
||||
ObjectRemove,
|
||||
ObjectFilter,
|
||||
ObjectGet,
|
||||
@@ -1330,6 +1331,20 @@ var ObjectUnion = &Builtin{
|
||||
),
|
||||
}
|
||||
|
||||
// ObjectUnionN creates a new object that is the asymmetric union of all objects merged from left to right
|
||||
var ObjectUnionN = &Builtin{
|
||||
Name: "object.union_n",
|
||||
Decl: types.NewFunction(
|
||||
types.Args(
|
||||
types.NewArray(
|
||||
nil,
|
||||
types.NewObject(nil, types.NewDynamicProperty(types.A, types.A)),
|
||||
),
|
||||
),
|
||||
types.A,
|
||||
),
|
||||
}
|
||||
|
||||
// ObjectRemove Removes specified keys from an object
|
||||
var ObjectRemove = &Builtin{
|
||||
Name: "object.remove",
|
||||
|
||||
@@ -2558,6 +2558,31 @@
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "object.union_n",
|
||||
"decl": {
|
||||
"args": [
|
||||
{
|
||||
"dynamic": {
|
||||
"dynamic": {
|
||||
"key": {
|
||||
"type": "any"
|
||||
},
|
||||
"value": {
|
||||
"type": "any"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
],
|
||||
"result": {
|
||||
"type": "any"
|
||||
},
|
||||
"type": "function"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "opa.runtime",
|
||||
"decl": {
|
||||
|
||||
@@ -343,7 +343,8 @@ complex types.
|
||||
| ------- |-------------|---------------|
|
||||
| <span class="opa-keep-it-together">`value := object.get(object, key, default)`</span> | `value` is the value stored by the `object` at `key`. If no value is found, `default` is returned. | ✅ |
|
||||
| <span class="opa-keep-it-together">`output := object.remove(object, keys)`</span> | `output` is a new object which is the result of removing the specified `keys` from `object`. `keys` must be either an array, object, or set of keys. | ✅ |
|
||||
| <span class="opa-keep-it-together">`output := object.union(objectA, objectB)`</span> | `output` is a new object which is the result of an asymmetric recursive union of two objects where conflicts are resolved by choosing the key from the right-hand object (`objectB`). For example: `object.union({"a": 1, "b": 2, "c": {"d": 3}}, {"a": 7, "c": {"d": 4, "e": 5}})` will result in `{"a": 7, "b": 2, "c": {"d": 4, "e": 5}}` | ✅ |
|
||||
| <span class="opa-keep-it-together">`output := object.union(objectA, objectB)`</span> | `output` is a new object which is the result of an asymmetric recursive union of two objects where conflicts are resolved by choosing the key from the right-hand object (`objectB`). For example: `object.union({"a": 1, "b": 2, "c": {"d": 3}}, {"a": 7, "c": {"d": 4, "e": 5}})` will result in `{"a": 7, "b": 2, "c": {"d": 4, "e": 5}}` | ✅ |
|
||||
| <span class="opa-keep-it-together">`output := object.union_n(array)`</span> | `output` is a new object which is the result of an asymmetric recursive union of all objects in `array`, merged from left to right, where conflicts are resolved by choosing the key from the right-hand object. For example: `object.union_n([{"a": 1}, {"b": 2}, {"a": 3}])` will result in `{"b": 2, "a": 3}` | ``SDK-dependent`` |
|
||||
| <span class="opa-keep-it-together">`filtered := object.filter(object, keys)`</span> | `filtered` is a new object with the remaining data from `object` with only keys specified in `keys` which is an array, object, or set of keys. For example: `object.filter({"a": {"b": "x", "c": "y"}, "d": "z"}, ["a"])` will result in `{"a": {"b": "x", "c": "y"}}`). | ✅ |
|
||||
| <span class="opa-keep-it-together">`filtered := json.filter(object, paths)`</span> | `filtered` is the remaining data from `object` with only keys specified in `paths` which is an array or set of JSON string paths. For example: `json.filter({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"b": "x"}}`). Paths are not filtered in-order and are deduplicated before being evaluated. | ✅ |
|
||||
| <span class="opa-keep-it-together">`output := json.remove(object, paths)`</span> | `output` is a new object which is the result of removing all keys specified in `paths` which is an array or set of JSON string paths. For example: `json.remove({"a": {"b": "x", "c": "y"}}, ["a/b"])` will result in `{"a": {"c": "y"}}`. Paths are not removed in-order and are deduplicated before being evaluated. | ✅ |
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
cases:
|
||||
- note: objectunionn/empty array
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p := object.union_n([{}])
|
||||
|
||||
query: data.test.p = x
|
||||
want_result:
|
||||
- x: {}
|
||||
|
||||
- note: objectunionn/single item array
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
p := object.union_n([{"foo": "bar"}])
|
||||
|
||||
query: data.test.p = x
|
||||
want_result:
|
||||
- x: {"foo": "bar"}
|
||||
|
||||
- note: objectunionn/merge objects
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x := object.union_n([{"foo": "bar"}, {"x": "y"}])
|
||||
|
||||
query: data.test.x = x
|
||||
want_result:
|
||||
- x: {"foo": "bar", "x": "y"}
|
||||
|
||||
- note: objectunionn/merge objects conflict
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x := object.union_n([{"foo": "bar"}, {"foo": "baz"}])
|
||||
|
||||
query: data.test.x = x
|
||||
want_result:
|
||||
- x: {"foo": "baz"}
|
||||
|
||||
- note: objectunionn/merge objects extended
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
|
||||
x := object.union_n([{
|
||||
"a": 1,
|
||||
"b": 2,
|
||||
"c": 3,
|
||||
}, {
|
||||
"foo": "baz",
|
||||
"a": "a",
|
||||
"b": 2,
|
||||
"d": 4,
|
||||
}, {
|
||||
"a": "final A!",
|
||||
"e": 5.0,
|
||||
}])
|
||||
|
||||
query: data.test.x = x
|
||||
want_result:
|
||||
- x: {"foo": "baz", "a": "final A!", "b": 2, "c": 3, "d": 4, "e": 5.0}
|
||||
@@ -26,6 +26,25 @@ func builtinObjectUnion(_ BuiltinContext, operands []*ast.Term, iter func(*ast.T
|
||||
return iter(ast.NewTerm(r))
|
||||
}
|
||||
|
||||
func builtinObjectUnionN(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
arr, err := builtins.ArrayOperand(operands[0].Value, 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r := ast.NewObject()
|
||||
arr.Foreach(func(t *ast.Term) {
|
||||
var o ast.Object
|
||||
o, err = builtins.ObjectOperand(t.Value, 1)
|
||||
r = mergeWithOverwrite(r, o)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return iter(ast.NewTerm(r))
|
||||
}
|
||||
|
||||
func builtinObjectRemove(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
|
||||
// Expect an object and an array/set/object of keys
|
||||
obj, err := builtins.ObjectOperand(operands[0].Value, 1)
|
||||
@@ -134,6 +153,7 @@ func mergeWithOverwrite(objA, objB ast.Object) ast.Object {
|
||||
|
||||
func init() {
|
||||
RegisterBuiltinFunc(ast.ObjectUnion.Name, builtinObjectUnion)
|
||||
RegisterBuiltinFunc(ast.ObjectUnionN.Name, builtinObjectUnionN)
|
||||
RegisterBuiltinFunc(ast.ObjectRemove.Name, builtinObjectRemove)
|
||||
RegisterBuiltinFunc(ast.ObjectFilter.Name, builtinObjectFilter)
|
||||
RegisterBuiltinFunc(ast.ObjectGet.Name, builtinObjectGet)
|
||||
|
||||
Reference in New Issue
Block a user