From e91878c70ed8f29ffdb3c6ca22365f113d155a9b Mon Sep 17 00:00:00 2001 From: Anand Krishnamoorthi Date: Fri, 26 Apr 2024 07:33:49 -0400 Subject: [PATCH] Update to OPA v0.64.0 Implement json.marshal_with_options builtin closes #215, closes #218 Signed-off-by: Anand Krishnamoorthi --- README.md | 6 ++-- docs/builtins.md | 37 ++++++++++----------- src/builtins/encoding.rs | 69 ++++++++++++++++++++++++++++++++++++++++ tests/opa.rs | 2 +- 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index fc5ff7ce..83c51b7d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Regorus is also - *cross-platform* - Written in platform-agnostic Rust. - *current* - We strive to keep Regorus up to date with latest OPA release. Regorus supports `import rego.v1`. - - *compliant* - Regorus is mostly compliant with the latest [OPA release v0.63.0](https://github.com/open-policy-agent/opa/releases/tag/v0.63.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins. + - *compliant* - Regorus is mostly compliant with the latest [OPA release v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0). See [OPA Conformance](#opa-conformance) for details. Note that while we behaviorally produce the same results, we don't yet support all the builtins. - *extensible* - Extend the Rego language by implementing custom stateful builtins in Rust. See [add_extension](https://github.com/microsoft/regorus/blob/fc68bf9c8bea36427dae9401a7d1f6ada771f7ab/src/engine.rs#L352). Support for extensibility using other languages coming soon. @@ -69,7 +69,7 @@ $ cargo build -r --example regorus --features "yaml" --no-default-features; stri -rwxr-xr-x 1 anand staff 2.9M Jan 19 11:26 target/release/examples/regorus* ``` -Regorus passes the [OPA v0.63.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few +Regorus passes the [OPA v0.64.0 test-suite](https://www.openpolicyagent.org/docs/latest/ir/#test-suite) barring a few builtins. See [OPA Conformance](#opa-conformance) below. ## Bindings @@ -245,7 +245,7 @@ Benchmark 1: opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.jso ``` ## OPA Conformance -Regorus has been verified to be compliant with [OPA v0.63.0](https://github.com/open-policy-agent/opa/releases/tag/v0.63.0) +Regorus has been verified to be compliant with [OPA v0.64.0](https://github.com/open-policy-agent/opa/releases/tag/v0.64.0) using a [test driver](https://github.com/microsoft/regorus/blob/main/tests/opa.rs) that loads and runs the OPA testsuite using Regorus, and verifies that expected outputs are produced. The test driver can be invoked by running: diff --git a/docs/builtins.md b/docs/builtins.md index 71e99029..9eafd6e0 100644 --- a/docs/builtins.md +++ b/docs/builtins.md @@ -159,24 +159,25 @@ In future, each builtin will be associated with a feature (many builtins could b | [type_name](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-types-type_name) | _ | - [Encoding](https://www.openpolicyagent.org/docs/latest/policy-reference/#encoding) - | Builtin | Feature | - |----------------------------------------------------------------------------------------------------------------------------------|-------------| - | [base64.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64is_valid) | `base64` | - | [base64url.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urldecode) | `base64` | - | [base64url.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode) | `base64url` | - | [base64url.encode_no_pad](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode_no_pad) | `base64url` | - | [hex.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexdecode) | `hex` | - | [hex.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexencode) | `hex` | - | [json.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonis_valid) | _ | - | [json.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal) | _ | - | [json.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonunmarshal) | _ | - | [urlquery.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode) | `urlquery` | - | [urlquery.decode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode_object) | `urlquery` | - | [urlquery.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode) | `urlquery` | - | [urlquery.encode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode_object) | `urlquery` | - | [yaml.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlis_valid) | `yaml` | - | [yaml.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlmarshal) | `yaml` | - | [yaml.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlunmarshal) | `yaml` | + | Builtin | Feature | + |--------------------------------------------------------------------------------------------------------------------------------------|-------------| + | [base64.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64is_valid) | `base64` | + | [base64url.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urldecode) | `base64` | + | [base64url.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode) | `base64url` | + | [base64url.encode_no_pad](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-base64urlencode_no_pad) | `base64url` | + | [hex.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexdecode) | `hex` | + | [hex.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-hexencode) | `hex` | + | [json.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonis_valid) | _ | + | [json.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal) | _ | + | [json.marshal_with_options](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonmarshal_with_options) | _ | + | [json.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-jsonunmarshal) | _ | + | [urlquery.decode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode) | `urlquery` | + | [urlquery.decode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlquerydecode_object) | `urlquery` | + | [urlquery.encode](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode) | `urlquery` | + | [urlquery.encode_object](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-urlqueryencode_object) | `urlquery` | + | [yaml.is_valid](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlis_valid) | `yaml` | + | [yaml.marshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlmarshal) | `yaml` | + | [yaml.unmarshal](https://www.openpolicyagent.org/docs/latest/policy-reference/#builtin-encoding-yamlunmarshal) | `yaml` | - [Time](https://www.openpolicyagent.org/docs/latest/policy-reference/#time) | Builtin | Feature | diff --git a/src/builtins/encoding.rs b/src/builtins/encoding.rs index 16a51d66..7ac34054 100644 --- a/src/builtins/encoding.rs +++ b/src/builtins/encoding.rs @@ -42,6 +42,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { } m.insert("json.is_valid", (json_is_valid, 1)); m.insert("json.marshal", (json_marshal, 1)); + m.insert("json.marshal_with_options", (json_marshal_with_options, 2)); m.insert("json.unmarshal", (json_unmarshal, 1)); #[cfg(feature = "yaml")] @@ -368,6 +369,74 @@ fn json_marshal(span: &Span, params: &[Ref], args: &[Value], _strict: bool )) } +fn json_marshal_with_options( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { + let name = "json.marshal_with_options"; + ensure_args_count(span, name, params, args, 2)?; + + let options = ensure_object(name, ¶ms[1], args[1].clone())?; + let (mut pretty, mut indent, mut prefix) = (true, Some("\t".to_owned()), None); + for (option, option_value) in options.iter() { + match option { + Value::String(s) if s.as_ref() == "pretty" && option_value.as_bool().is_ok() => { + pretty = option_value == &Value::Bool(true); + } + Value::String(s) if s.as_ref() == "pretty" => bail!(params[1] + .span() + .error("marshaling option `pretty` must be true or false")), + Value::String(s) if s.as_ref() == "prefix" && option_value.as_string().is_ok() => { + prefix = Some(option_value.as_string()?.as_ref().to_string()); + } + Value::String(s) if s.as_ref() == "prefix" => bail!(params[1] + .span() + .error("marshaling option `pretty` must be string")), + Value::String(s) if s.as_ref() == "indent" && option_value.as_string().is_ok() => { + indent = Some(option_value.as_string()?.as_ref().to_string()); + } + Value::String(s) if s.as_ref() == "indent" => bail!(params[1] + .span() + .error("marshaling option `pretty` must be string")), + _ => bail!(params[1] + .span() + .error("marshaling option must be one of `indent`, `prefix` or `pretty`")), + } + } + + if !pretty || options.is_empty() { + return Ok(Value::String( + serde_json::to_string(&args[0]) + .with_context(|| span.error("could not serialize to json"))? + .into(), + )); + } + + let lines: Vec = serde_json::to_string_pretty(&args[0]) + .with_context(|| span.error("could not serialize to json"))? + .split('\n') + .map(|line| { + let mut line = line.to_string(); + + if let Some(indent) = &indent { + let start_trimmed = line.trim_start(); + let leading_spaces = line.len() - start_trimmed.len(); + let indentation_level = leading_spaces / 2; + line = indent.repeat(indentation_level) + start_trimmed; + } + + if let Some(prefix) = &prefix { + line = prefix.to_owned() + &line; + } + line + }) + .collect(); + + Ok(Value::from(lines.join("\n"))) +} + fn json_unmarshal( span: &Span, params: &[Ref], diff --git a/tests/opa.rs b/tests/opa.rs index 59b2a798..9c36b285 100644 --- a/tests/opa.rs +++ b/tests/opa.rs @@ -13,7 +13,7 @@ use serde::{Deserialize, Serialize}; use walkdir::WalkDir; const OPA_REPO: &str = "https://github.com/open-policy-agent/opa"; -const OPA_BRANCH: &str = "v0.63.0"; +const OPA_BRANCH: &str = "v0.64.0"; #[derive(Serialize, Deserialize, PartialEq, Debug)] #[serde(deny_unknown_fields)]