From 81221c5271986e9df4b0b2583a2653a4171d192f Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Fri, 30 Dec 2022 09:45:06 +0200 Subject: [PATCH 1/3] impl object union_n --- Cargo.toml | 2 ++ src/builtins/impls/mod.rs | 1 + src/builtins/impls/object.rs | 14 +++++++++++++- src/builtins/mod.rs | 3 +++ tests/infra-fixtures/test-object.rego | 7 +++++++ tests/smoke_test.rs | 1 + tests/snapshots/smoke_test__object.snap | 15 +++++++++++++++ 7 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 tests/infra-fixtures/test-object.rego create mode 100644 tests/snapshots/smoke_test__object.snap diff --git a/Cargo.toml b/Cargo.toml index d29e814..d07845b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -101,6 +101,7 @@ rand-builtins = ["rng"] yaml-builtins = ["dep:serde_yaml"] urlquery-builtins = ["dep:form_urlencoded", "dep:urlencoding"] time-builtins = ["time", "dep:chrono-tz", "dep:duration-str", "dep:chronoutil"] +object-builtins = [] all-crypto-builtins = [ "crypto-digest-builtins", @@ -122,6 +123,7 @@ all-builtins = [ "yaml-builtins", "urlquery-builtins", "time-builtins", + "object-builtins", ] [[test]] diff --git a/src/builtins/impls/mod.rs b/src/builtins/impls/mod.rs index 8013b1a..d49f63f 100644 --- a/src/builtins/impls/mod.rs +++ b/src/builtins/impls/mod.rs @@ -32,6 +32,7 @@ pub mod io; #[cfg(feature = "json-builtins")] pub mod json; pub mod net; +#[cfg(feature = "object-builtins")] pub mod object; pub mod opa; #[cfg(feature = "rng")] diff --git a/src/builtins/impls/object.rs b/src/builtins/impls/object.rs index 82cdf65..7968bba 100644 --- a/src/builtins/impls/object.rs +++ b/src/builtins/impls/object.rs @@ -21,5 +21,17 @@ use anyhow::{bail, Result}; /// will result in `{"b": 2, "a": 3}`. #[tracing::instrument(name = "object.union_n", err)] pub fn union_n(objects: Vec) -> Result { - bail!("not implemented"); + let mut map = serde_json::map::Map::new(); + for object in objects { + let data = match object.as_object() { + Some(data) => data, + None => bail!("invalid argument(s)"), + }; + + for (key, val) in data { + *map.entry(key).or_insert(val.clone()) = val.clone(); + } + } + + Ok(serde_json::Value::Object(map)) } diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 392f7f6..c5c20ca 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -106,7 +106,10 @@ pub fn resolve(name: &str) -> Result>> "net.cidr_expand" => Ok(self::impls::net::cidr_expand.wrap()), "net.cidr_merge" => Ok(self::impls::net::cidr_merge.wrap()), "net.lookup_ip_addr" => Ok(self::impls::net::lookup_ip_addr.wrap()), + + #[cfg(feature = "object-builtins")] "object.union_n" => Ok(self::impls::object::union_n.wrap()), + "opa.runtime" => Ok(self::impls::opa::runtime.wrap()), #[cfg(feature = "rng")] diff --git a/tests/infra-fixtures/test-object.rego b/tests/infra-fixtures/test-object.rego new file mode 100644 index 0000000..97de02d --- /dev/null +++ b/tests/infra-fixtures/test-object.rego @@ -0,0 +1,7 @@ +package test + +object_1 := object.union_n([{"a": 1}, {"b": 2}, {"a": 3}]) + +object_2 := object.union_n([{"a": 1}, {"b": 2}, {"a": 3, "b": 1}]) + +object_override_by_string := object.union_n([{"a": 1}, {"b": 2}, {"a": "3"}]) diff --git a/tests/smoke_test.rs b/tests/smoke_test.rs index cf70c51..3efade7 100644 --- a/tests/smoke_test.rs +++ b/tests/smoke_test.rs @@ -121,6 +121,7 @@ integration_test!(test_rand, "test-rand"); integration_test!(test_yaml, "test-yaml"); integration_test!(test_urlquery, "test-urlquery"); integration_test!(test_time, "test-time"); +integration_test!(test_object, "test-object"); /* #[tokio::test] diff --git a/tests/snapshots/smoke_test__object.snap b/tests/snapshots/smoke_test__object.snap new file mode 100644 index 0000000..4977e87 --- /dev/null +++ b/tests/snapshots/smoke_test__object.snap @@ -0,0 +1,15 @@ +--- +source: tests/smoke_test.rs +expression: "test_policy(\"test-object\", None).await.expect(\"error in test suite\")" +--- +- result: + object_1: + a: 3 + b: 2 + object_2: + a: 3 + b: 1 + object_override_by_string: + a: "3" + b: 2 + From 42eef94a89b947cbddf554399a630f6c106e51c2 Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Mon, 16 Jan 2023 09:38:21 +0200 Subject: [PATCH 2/3] recursively support --- src/builtins/impls/object.rs | 38 ++++++++++++++++++------- tests/infra-fixtures/test-object.rego | 2 ++ tests/snapshots/smoke_test__object.snap | 8 +++++- 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/builtins/impls/object.rs b/src/builtins/impls/object.rs index 7968bba..4eb31e3 100644 --- a/src/builtins/impls/object.rs +++ b/src/builtins/impls/object.rs @@ -14,24 +14,40 @@ //! Builtins to help handling JSON objects -use anyhow::{bail, Result}; +use anyhow::Result; +use serde_json::Value; /// Creates a new object that is the asymmetric union of all objects merged from /// left to right. For example: `object.union_n([{"a": 1}, {"b": 2}, {"a": 3}])` /// will result in `{"b": 2, "a": 3}`. #[tracing::instrument(name = "object.union_n", err)] -pub fn union_n(objects: Vec) -> Result { - let mut map = serde_json::map::Map::new(); +pub fn union_n(objects: Vec) -> Result { + let mut result = serde_json::json!({}); for object in objects { - let data = match object.as_object() { - Some(data) => data, - None => bail!("invalid argument(s)"), - }; + merge_value(&mut result, &object); + } + + Ok(result) +} - for (key, val) in data { - *map.entry(key).or_insert(val.clone()) = val.clone(); +fn merge_value(a: &mut Value, b: &Value) { + match (a, b) { + (Value::Object(ref mut a), &Value::Object(ref b)) => { + for (k, v) in b { + merge_value(a.entry(k).or_insert(Value::Null), v); + } + } + (Value::Array(ref mut a), &Value::Array(ref b)) => { + *a = vec![]; + a.extend(b.clone()); + } + (Value::Array(ref mut a), &Value::Object(ref b)) => { + *a = vec![]; + a.extend([Value::Object(b.clone())]); + } + (_, Value::Null) => {} + (a, b) => { + *a = b.clone(); } } - - Ok(serde_json::Value::Object(map)) } diff --git a/tests/infra-fixtures/test-object.rego b/tests/infra-fixtures/test-object.rego index 97de02d..75249e8 100644 --- a/tests/infra-fixtures/test-object.rego +++ b/tests/infra-fixtures/test-object.rego @@ -5,3 +5,5 @@ object_1 := object.union_n([{"a": 1}, {"b": 2}, {"a": 3}]) object_2 := object.union_n([{"a": 1}, {"b": 2}, {"a": 3, "b": 1}]) object_override_by_string := object.union_n([{"a": 1}, {"b": 2}, {"a": "3"}]) + +recursive := object.union_n([{"a": {"b": [1], "c": 1}}, {"a": {"b": [1, 2, 3]}}]) diff --git a/tests/snapshots/smoke_test__object.snap b/tests/snapshots/smoke_test__object.snap index 4977e87..0b99046 100644 --- a/tests/snapshots/smoke_test__object.snap +++ b/tests/snapshots/smoke_test__object.snap @@ -12,4 +12,10 @@ expression: "test_policy(\"test-object\", None).await.expect(\"error in test sui object_override_by_string: a: "3" b: 2 - + recursive: + a: + b: + - 1 + - 2 + - 3 + c: 1 \ No newline at end of file From 968fe3e4176b20054849c8a81cd2f4b7aa2070c3 Mon Sep 17 00:00:00 2001 From: Elad Kaplan Date: Tue, 31 Jan 2023 09:12:22 +0200 Subject: [PATCH 3/3] fix clippy --- src/builtins/impls/object.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/builtins/impls/object.rs b/src/builtins/impls/object.rs index 4eb31e3..4cc0a76 100644 --- a/src/builtins/impls/object.rs +++ b/src/builtins/impls/object.rs @@ -22,7 +22,7 @@ use serde_json::Value; /// will result in `{"b": 2, "a": 3}`. #[tracing::instrument(name = "object.union_n", err)] pub fn union_n(objects: Vec) -> Result { - let mut result = serde_json::json!({}); + let mut result = serde_json::Value::Object(serde_json::Map::default()); for object in objects { merge_value(&mut result, &object); }