diff --git a/README.md b/README.md index b548d0c..489433b 100644 --- a/README.md +++ b/README.md @@ -15,32 +15,33 @@ Regorus is available as a library that can be easily integrated into your Rust p ## Getting Started -[regorus](examples/regorus.rs) is an example program that shows how to integrate Regorus into your project and evaluate Rego policies. +[examples/regorus](examples/regorus.rs) is an example program that shows how to integrate Regorus into your project and evaluate Rego policies. -To build it, do +To build and install it, do - cargo build -r --example regorus + cargo install --example regorus --path . Check that the regorus example program is working - $ target/release/examples/regorus + $ regorus Usage: regorus Commands: eval Evaluate a Rego Query lex Tokenize a Rego policy - parse Parse q Rego policy + parse Parse a Rego policy help Print this message or the help of the given subcommand(s) - + Options: -h, --help Print help - -V, --version Print version + -V, --version Print versionUsage: regorus + First, let's evaluate a simple Rego expression `1*2+3` - target/release/examples/regorus eval "1*2+3" + regorus eval "1*2+3" This produces the following output @@ -63,16 +64,11 @@ This produces the following output Next, evaluate a sample [policy](examples/example.rego) and [input](examples/input.json) (borrowed from [Rego tutorial](https://www.openpolicyagent.org/docs/latest/#2-try-opa-eval)): - target/release/examples/regorus eval -d examples/example.rego -i examples/input.json data.example + regorus eval -d examples/example.rego -i examples/input.json data.example Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Instances (ACI) - target/release/examples/regorus eval -d tests/aci/framework.rego \ - -d tests/aci/policy.rego \ - -d tests/aci/api.rego \ - -d tests/aci/data.json \ - -i tests/aci/input.json \ - data.policy.mount_overlay=x + regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x ## ACI Policies @@ -94,26 +90,24 @@ Regorus successfully passes the ACI policy test-suite. It is fast and can run ea Run the ACI policies in the `tests/aci` directory, using data `tests/aci/data.json` and input `tests/aci/input.json`: - target/release/examples/regorus eval \ - -b tests/aci \ - -d tests/aci/data.json \ - -i tests/aci/input.json \ - data.framework.mount_overlay=x + regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x + Verify that [OPA](https://github.com/open-policy-agent/opa/releases) produces the same output - diff <(target/release/examples/regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x) <(opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x) + diff <(regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x) \ + <(opa eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x) ## Performance To check how fast Regorus runs on your system, first install a tool like [hyperfine](https://github.com/sharkdp/hyperfine). - cargo install hyperfine + cargo install hyperfine Then benchmark evaluation of the ACI policies, - $ hyperfine "target/release/examples/regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x" - Benchmark 1: target/release/examples/regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x + $ hyperfine "regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x" + Benchmark 1: regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.framework.mount_overlay=x Time (mean ± σ): 4.6 ms ± 0.2 ms [User: 4.1 ms, System: 0.4 ms] Range (min … max): 4.4 ms … 6.0 ms 422 runs diff --git a/examples/regorus.rs b/examples/regorus.rs index 1bdb296..8cc2d69 100644 --- a/examples/regorus.rs +++ b/examples/regorus.rs @@ -142,7 +142,7 @@ enum RegorusCommand { verbose: bool, }, - /// Parse q Rego policy. + /// Parse a Rego policy. Parse { /// Rego policy file. file: String, diff --git a/src/ast.rs b/src/ast.rs index a66dfea..3eb3975 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -321,6 +321,7 @@ pub enum Rule { Default { span: Span, refr: Ref, + args: Vec>, op: AssignOp, value: Ref, }, diff --git a/src/builtins/aggregates.rs b/src/builtins/aggregates.rs index f7e4025..0d6c226 100644 --- a/src/builtins/aggregates.rs +++ b/src/builtins/aggregates.rs @@ -21,7 +21,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("sum", (sum, 1)); } -fn count(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn count(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "count", params, args, 1)?; Ok(Value::from(Number::from(match &args[0] { @@ -38,7 +38,7 @@ fn count(span: &Span, params: &[Ref], args: &[Value]) -> Result { }))) } -fn max(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn max(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "max", params, args, 1)?; Ok(match &args[0] { @@ -53,7 +53,7 @@ fn max(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn min(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn min(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "min", params, args, 1)?; Ok(match &args[0] { @@ -68,7 +68,7 @@ fn min(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn product(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn product(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "product", params, args, 1)?; let mut v = Number::from(1_u64); @@ -93,7 +93,7 @@ fn product(span: &Span, params: &[Ref], args: &[Value]) -> Result { })) } -fn sort(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn sort(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "sort", params, args, 1)?; Ok(match &args[0] { Value::Array(a) => { @@ -110,7 +110,7 @@ fn sort(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn sum(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn sum(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "sum", params, args, 1)?; let mut v = Number::from(0_u64); diff --git a/src/builtins/arrays.rs b/src/builtins/arrays.rs index fe5c04d..1375787 100644 --- a/src/builtins/arrays.rs +++ b/src/builtins/arrays.rs @@ -18,7 +18,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("array.slice", (slice, 3)); } -fn concat(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn concat(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "array.concat"; ensure_args_count(span, name, params, args, 2)?; let mut v1 = ensure_array(name, ¶ms[0], args[0].clone())?; @@ -28,7 +28,7 @@ fn concat(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::Array(v1)) } -fn reverse(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn reverse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "array.reverse"; ensure_args_count(span, name, params, args, 1)?; @@ -37,7 +37,7 @@ fn reverse(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::Array(v1)) } -fn slice(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn slice(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "array.slice"; ensure_args_count(span, name, params, args, 3)?; diff --git a/src/builtins/bitwise.rs b/src/builtins/bitwise.rs index 82e035e..deac304 100644 --- a/src/builtins/bitwise.rs +++ b/src/builtins/bitwise.rs @@ -21,7 +21,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("bits.xor", (xor, 2)); } -fn and(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn and(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.and"; ensure_args_count(span, name, params, args, 2)?; @@ -34,7 +34,7 @@ fn and(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn lsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn lsh(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.lsh"; ensure_args_count(span, name, params, args, 2)?; @@ -47,7 +47,7 @@ fn lsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn negate(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn negate(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.negate"; ensure_args_count(span, name, params, args, 1)?; @@ -59,7 +59,7 @@ fn negate(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn or(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn or(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.or"; ensure_args_count(span, name, params, args, 2)?; @@ -72,7 +72,7 @@ fn or(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn rsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn rsh(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.rsh"; ensure_args_count(span, name, params, args, 2)?; @@ -85,7 +85,7 @@ fn rsh(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn xor(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn xor(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "bits.xor"; ensure_args_count(span, name, params, args, 2)?; diff --git a/src/builtins/conversions.rs b/src/builtins/conversions.rs index c7d4631..afb35fd 100644 --- a/src/builtins/conversions.rs +++ b/src/builtins/conversions.rs @@ -15,12 +15,13 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("to_number", (to_number, 1)); } -fn to_number(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn to_number(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "to_number"; ensure_args_count(span, name, params, args, 1)?; let span = params[0].span(); Ok(match &args[0] { + Value::Null => Value::from(0u64), Value::Bool(true) => Value::from(1u64), Value::Bool(false) => Value::from(0u64), Value::Number(_) => args[0].clone(), @@ -34,7 +35,9 @@ fn to_number(span: &Span, params: &[Ref], args: &[Value]) -> Result } }, _ => { - bail!(span.error(format!("`{name}` expects bool/number/string argument.").as_str())); + bail!( + span.error(format!("`{name}` expects bool/number/string/null argument.").as_str()) + ); } }) } diff --git a/src/builtins/crypto.rs b/src/builtins/crypto.rs index e377b70..a0aa071 100644 --- a/src/builtins/crypto.rs +++ b/src/builtins/crypto.rs @@ -28,7 +28,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("crypto.sha256", (crypto_sha256, 1)); } -fn hmac_equal_fixed_time(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn hmac_equal_fixed_time( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "crypto.hmac.equal"; ensure_args_count(span, name, params, args, 2)?; @@ -41,7 +46,7 @@ fn hmac_equal_fixed_time(span: &Span, params: &[Ref], args: &[Value]) -> R ))) } -fn hmac_md5(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn hmac_md5(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.hmac.md5"; ensure_args_count(span, name, params, args, 2)?; @@ -57,7 +62,7 @@ fn hmac_md5(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::String(hex::encode(result.into_bytes()).into())) } -fn hmac_sha1(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn hmac_sha1(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.hmac.sha1"; ensure_args_count(span, name, params, args, 2)?; @@ -73,7 +78,7 @@ fn hmac_sha1(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::String(hex::encode(result.into_bytes()).into())) } -fn hmac_sha256(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn hmac_sha256(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.hmac.sha256"; ensure_args_count(span, name, params, args, 2)?; @@ -89,7 +94,7 @@ fn hmac_sha256(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn hmac_sha512(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.hmac.sha512"; ensure_args_count(span, name, params, args, 2)?; @@ -105,7 +110,7 @@ fn hmac_sha512(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn crypto_md5(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.md5"; ensure_args_count(span, name, params, args, 1)?; @@ -119,7 +124,7 @@ fn crypto_md5(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn crypto_sha1(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "crypto.sha1"; ensure_args_count(span, name, params, args, 1)?; @@ -133,7 +138,12 @@ fn crypto_sha1(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn crypto_sha256( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "crypto.sha256"; ensure_args_count(span, name, params, args, 1)?; diff --git a/src/builtins/debugging.rs b/src/builtins/debugging.rs index 0c0a582..bb77b14 100644 --- a/src/builtins/debugging.rs +++ b/src/builtins/debugging.rs @@ -20,7 +20,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { // Symbol analyzer must ensure that vars used by print are defined before // the print statement. Scheduler must ensure the above constraint. // Additionally interpreter must allow undefined inputs to print. -fn print(span: &Span, _params: &[Ref], args: &[Value]) -> Result { +fn print(span: &Span, _params: &[Ref], args: &[Value], _strict: bool) -> Result { if args.len() > MAX_ARGS as usize { bail!(span.error("print supports up to 100 arguments")); } diff --git a/src/builtins/deprecated.rs b/src/builtins/deprecated.rs index e3bc5df..6e7d6af 100644 --- a/src/builtins/deprecated.rs +++ b/src/builtins/deprecated.rs @@ -19,7 +19,13 @@ lazy_static! { let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new(); m.insert("all", (all, 1)); - m.insert("any", (any, 1)); + m.insert("any", (any, 1)); + m.insert("cast_array", (cast_array, 1)); + m.insert("cast_boolean", (cast_boolean, 1)); + m.insert("cast_null", (cast_null, 1)); + m.insert("cast_object", (cast_object, 1)); + m.insert("cast_set", (cast_set, 1)); + m.insert("cast_string", (cast_string, 1)); m.insert("set_diff", (set_diff, 2)); #[cfg(feature = "crypto")] @@ -28,7 +34,7 @@ lazy_static! { }; } -fn all(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn all(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "all", params, args, 1)?; Ok(Value::Bool(match &args[0] { @@ -41,7 +47,7 @@ fn all(span: &Span, params: &[Ref], args: &[Value]) -> Result { })) } -fn any(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn any(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "any", params, args, 1)?; Ok(Value::Bool(match &args[0] { @@ -54,10 +60,70 @@ fn any(span: &Span, params: &[Ref], args: &[Value]) -> Result { })) } -fn set_diff(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn set_diff(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "set_diff"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_set(name, ¶ms[0], args[0].clone())?; let s2 = ensure_set(name, ¶ms[1], args[1].clone())?; Ok(Value::from_set(s1.difference(&s2).cloned().collect())) } + +fn cast_array(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_array"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::Array(_) => Ok(args[0].clone()), + _ if strict => bail!(params[0].span().error("array required")), + _ => Ok(Value::Undefined), + } +} + +fn cast_boolean(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_boolean"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::Bool(_) => Ok(args[0].clone()), + _ if strict => bail!(params[0].span().error("boolean required")), + _ => Ok(Value::Undefined), + } +} + +fn cast_null(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_null"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::Null => Ok(Value::Null), + _ if strict => bail!(params[0].span().error("null required")), + _ => Ok(Value::Undefined), + } +} + +fn cast_object(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_object"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::Object(_) => Ok(args[0].clone()), + _ if strict => bail!(params[0].span().error("object required")), + _ => Ok(Value::Undefined), + } +} + +fn cast_set(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_set"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::Set(_) => Ok(args[0].clone()), + _ if strict => bail!(params[0].span().error("set required")), + _ => Ok(Value::Undefined), + } +} + +fn cast_string(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { + let name = "cast_string"; + ensure_args_count(span, name, params, args, 1)?; + match &args[0] { + Value::String(_) => Ok(args[0].clone()), + _ if strict => bail!(params[0].span().error("string required")), + _ => Ok(Value::Undefined), + } +} diff --git a/src/builtins/encoding.rs b/src/builtins/encoding.rs index 108ff84..7df87b8 100644 --- a/src/builtins/encoding.rs +++ b/src/builtins/encoding.rs @@ -26,7 +26,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { } } -fn base64_decode(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn base64_decode( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "base64.decode"; ensure_args_count(span, name, params, args, 1)?; @@ -38,7 +43,12 @@ fn base64_decode(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn yaml_is_valid( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "yaml.is_valid"; ensure_args_count(span, name, params, args, 1)?; @@ -47,7 +57,7 @@ fn yaml_is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn yaml_marshal(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "yaml.marshal"; ensure_args_count(span, name, params, args, 1)?; Ok(Value::String( @@ -58,14 +68,24 @@ fn yaml_marshal(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn yaml_unmarshal( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "yaml.unmarshal"; ensure_args_count(span, name, params, args, 1)?; let yaml_str = ensure_string(name, ¶ms[0], &args[0])?; Value::from_yaml_str(&yaml_str).with_context(|| span.error("could not deserialize yaml.")) } -fn json_is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn json_is_valid( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "json.is_valid"; ensure_args_count(span, name, params, args, 1)?; @@ -73,7 +93,7 @@ fn json_is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn json_marshal(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "json.marshal"; ensure_args_count(span, name, params, args, 1)?; Ok(Value::String( @@ -83,7 +103,12 @@ fn json_marshal(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn json_unmarshal( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "json.unmarshal"; ensure_args_count(span, name, params, args, 1)?; let json_str = ensure_string(name, ¶ms[0], &args[0])?; diff --git a/src/builtins/glob.rs b/src/builtins/glob.rs index b55b2a3..b3993b1 100644 --- a/src/builtins/glob.rs +++ b/src/builtins/glob.rs @@ -55,7 +55,7 @@ fn make_glob<'a>(pattern: &'a str, span: &'a Span) -> Result> { Glob::new(pattern).or_else(|_| bail!(span.error("invalid glob"))) } -fn glob_match(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn glob_match(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "glob.match"; ensure_args_count(span, name, params, args, 3)?; @@ -101,7 +101,7 @@ fn glob_match(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn quote_meta(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "glob.quote_meta"; ensure_args_count(span, name, params, args, 1)?; diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 714cfcc..af6391c 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -38,7 +38,7 @@ use std::collections::HashMap; use anyhow::Result; use lazy_static::lazy_static; -pub type BuiltinFcn = (fn(&Span, &[Ref], &[Value]) -> Result, u8); +pub type BuiltinFcn = (fn(&Span, &[Ref], &[Value], bool) -> Result, u8); #[cfg(feature = "deprecated")] pub use deprecated::DEPRECATED; diff --git a/src/builtins/numbers.rs b/src/builtins/numbers.rs index 7238b2d..bc2428e 100644 --- a/src/builtins/numbers.rs +++ b/src/builtins/numbers.rs @@ -48,28 +48,28 @@ pub fn arithmetic_operation( })) } -fn abs(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn abs(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "abs", params, args, 1)?; Ok(Value::from( ensure_numeric("abs", ¶ms[0], &args[0])?.abs(), )) } -fn ceil(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn ceil(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "ceil", params, args, 1)?; Ok(Value::from( ensure_numeric("ceil", ¶ms[0], &args[0])?.ceil(), )) } -fn floor(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn floor(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "floor", params, args, 1)?; Ok(Value::from( ensure_numeric("floor", ¶ms[0], &args[0])?.floor(), )) } -fn range(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn range(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "numbers.range", params, args, 2)?; let v1 = ensure_numeric("numbers.range", ¶ms[0], &args[0].clone())?; let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1].clone())?; @@ -96,14 +96,14 @@ fn range(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::from_array(values)) } -fn round(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn round(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "round", params, args, 1)?; Ok(Value::from( ensure_numeric("round", ¶ms[0], &args[0])?.round(), )) } -fn intn(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn intn(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let fcn = "rand.intn"; ensure_args_count(span, fcn, params, args, 2)?; let _ = ensure_string(fcn, ¶ms[0], &args[0])?; diff --git a/src/builtins/objects.rs b/src/builtins/objects.rs index 97ffa84..7f23a52 100644 --- a/src/builtins/objects.rs +++ b/src/builtins/objects.rs @@ -122,7 +122,7 @@ fn merge_filters( Ok(filters) } -fn json_filter(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn json_filter(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "json.filter"; ensure_args_count(span, name, params, args, 2)?; ensure_object(name, ¶ms[0], args[0].clone())?; @@ -136,7 +136,7 @@ fn json_filter(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn filter(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "object.filter"; ensure_args_count(span, name, params, args, 2)?; let mut obj = ensure_object(name, ¶ms[0], args[0].clone())?; @@ -154,7 +154,7 @@ fn filter(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::Object(obj)) } -fn get(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn get(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "object.get"; ensure_args_count(span, name, params, args, 3)?; let obj = ensure_object(name, ¶ms[0], args[0].clone())?; @@ -179,14 +179,14 @@ fn get(span: &Span, params: &[Ref], args: &[Value]) -> Result { }) } -fn keys(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn keys(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "object.keys"; ensure_args_count(span, name, params, args, 1)?; let obj = ensure_object(name, ¶ms[0], args[0].clone())?; Ok(Value::from_set(obj.keys().cloned().collect())) } -fn remove(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn remove(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "object.remove"; ensure_args_count(span, name, params, args, 2)?; let mut obj = ensure_object(name, ¶ms[0], args[0].clone())?; @@ -225,7 +225,7 @@ fn is_subset(sup: &Value, sub: &Value) -> bool { } } -fn subset(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn subset(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "object.subset"; ensure_args_count(span, name, params, args, 2)?; diff --git a/src/builtins/regex.rs b/src/builtins/regex.rs index 6e5de98..6b80b14 100644 --- a/src/builtins/regex.rs +++ b/src/builtins/regex.rs @@ -26,7 +26,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("regex.template_match", (regex_template_match, 4)); } -fn find_all_string_submatch_n(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn find_all_string_submatch_n( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "regex.find_all_string_submatch_n"; ensure_args_count(span, name, params, args, 3)?; @@ -68,7 +73,7 @@ fn find_all_string_submatch_n(span: &Span, params: &[Ref], args: &[Value]) )) } -fn find_n(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn find_n(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "regex.find_n"; ensure_args_count(span, name, params, args, 3)?; @@ -98,14 +103,19 @@ fn find_n(span: &Span, params: &[Ref], args: &[Value]) -> Result { )) } -fn is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_valid(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "regex.is_valid"; ensure_args_count(span, name, params, args, 1)?; Ok(ensure_string(name, ¶ms[0], &args[0]) .map_or(Value::Bool(false), |p| Value::Bool(Regex::new(&p).is_ok()))) } -pub fn regex_match(span: &Span, params: &[Ref], args: &[Value]) -> Result { +pub fn regex_match( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "regex.match"; ensure_args_count(span, name, params, args, 2)?; let pattern = ensure_string(name, ¶ms[0], &args[0])?; @@ -116,7 +126,12 @@ pub fn regex_match(span: &Span, params: &[Ref], args: &[Value]) -> Result< Ok(Value::Bool(pattern.is_match(&value))) } -fn regex_replace(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn regex_replace( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "regex.replace"; ensure_args_count(span, name, params, args, 3)?; @@ -135,7 +150,7 @@ fn regex_replace(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn regex_split(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "regex.split"; ensure_args_count(span, name, params, args, 2)?; let pattern = ensure_string(name, ¶ms[0], &args[0])?; @@ -151,7 +166,12 @@ fn regex_split(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn regex_template_match( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "regex.template_match"; ensure_args_count(span, name, params, args, 4)?; let template = ensure_string(name, ¶ms[0], &args[0])?; diff --git a/src/builtins/semver.rs b/src/builtins/semver.rs index 7748587..b34a863 100644 --- a/src/builtins/semver.rs +++ b/src/builtins/semver.rs @@ -19,7 +19,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("semver.is_valid", (is_valid, 1)); } -fn compare(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn compare(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "semver.compare"; ensure_args_count(span, name, params, args, 2)?; @@ -35,7 +35,7 @@ fn compare(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::from(result as i64)) } -fn is_valid(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_valid(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "semver.is_valid"; ensure_args_count(span, name, params, args, 1)?; let v = ensure_string(name, ¶ms[0], &args[0])?; diff --git a/src/builtins/sets.rs b/src/builtins/sets.rs index 8eca717..e4c0f78 100644 --- a/src/builtins/sets.rs +++ b/src/builtins/sets.rs @@ -34,7 +34,12 @@ pub fn difference(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result], args: &[Value]) -> Result { +fn intersection_of_set_of_sets( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "intersection"; ensure_args_count(span, name, params, args, 1)?; let set = ensure_set(name, ¶ms[0], args[0].clone())?; @@ -61,7 +66,12 @@ fn intersection_of_set_of_sets(span: &Span, params: &[Ref], args: &[Value] Ok(Value::from_set(res)) } -fn union_of_set_of_sets(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn union_of_set_of_sets( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "union"; ensure_args_count(span, name, params, args, 1)?; let set = ensure_set(name, ¶ms[0], args[0].clone())?; diff --git a/src/builtins/strings.rs b/src/builtins/strings.rs index 53b3d98..1835ec2 100644 --- a/src/builtins/strings.rs +++ b/src/builtins/strings.rs @@ -42,7 +42,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("upper", (upper, 1)); } -fn concat(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn concat(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "concat"; ensure_args_count(span, name, params, args, 2)?; let delimiter = ensure_string(name, ¶ms[0], &args[0])?; @@ -50,7 +50,7 @@ fn concat(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::String(collection.join(&delimiter).into())) } -fn contains(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn contains(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "contains"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -58,7 +58,7 @@ fn contains(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::Bool(s1.contains(s2.as_ref()))) } -fn endswith(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn endswith(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "endswith"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -76,7 +76,7 @@ fn format_number(n: &Number, base: u64) -> String { } } -fn format_int(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn format_int(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "format_int"; ensure_args_count(span, name, params, args, 2)?; let mut n = ensure_numeric(name, ¶ms[0], &args[0])?; @@ -97,7 +97,7 @@ fn format_int(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn indexof(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "indexof"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -109,7 +109,7 @@ fn indexof(span: &Span, params: &[Ref], args: &[Value]) -> Result { } #[allow(dead_code)] -fn indexof_n(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn indexof_n(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "indexof_n"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -128,14 +128,14 @@ fn indexof_n(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::from_array(positions)) } -fn lower(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn lower(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "lower"; ensure_args_count(span, name, params, args, 1)?; let s = ensure_string(name, ¶ms[0], &args[0])?; Ok(Value::String(s.to_lowercase().into())) } -fn replace(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn replace(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "replace"; ensure_args_count(span, name, params, args, 3)?; let s = ensure_string(name, ¶ms[0], &args[0])?; @@ -144,7 +144,7 @@ fn replace(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::String(s.replace(old.as_ref(), new.as_ref()).into())) } -fn split(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn split(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "replace"; ensure_args_count(span, name, params, args, 2)?; let s = ensure_string(name, ¶ms[0], &args[0])?; @@ -157,7 +157,7 @@ fn split(span: &Span, params: &[Ref], args: &[Value]) -> Result { )) } -fn sprintf(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn sprintf(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "sprintf"; ensure_args_count(span, name, params, args, 2)?; let fmt = ensure_string(name, ¶ms[0], &args[0])?; @@ -345,7 +345,12 @@ fn sprintf(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::String(s.into())) } -fn any_prefix_match(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn any_prefix_match( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "strings.any_prefix_match"; ensure_args_count(span, name, params, args, 2)?; @@ -376,7 +381,12 @@ fn any_prefix_match(span: &Span, params: &[Ref], args: &[Value]) -> Result )) } -fn any_suffix_match(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn any_suffix_match( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { let name = "strings.any_suffix_match"; ensure_args_count(span, name, params, args, 2)?; @@ -407,7 +417,7 @@ fn any_suffix_match(span: &Span, params: &[Ref], args: &[Value]) -> Result )) } -fn startswith(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn startswith(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "startswith"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -415,7 +425,7 @@ fn startswith(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn replace_n(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim"; ensure_args_count(span, name, params, args, 2)?; let obj = ensure_object(name, ¶ms[0], args[0].clone())?; @@ -438,14 +448,14 @@ fn replace_n(span: &Span, params: &[Ref], args: &[Value]) -> Result Ok(Value::String(s.clone())) } -fn reverse(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn reverse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "reverse"; ensure_args_count(span, name, params, args, 1)?; let s = ensure_string(name, ¶ms[0], &args[0])?; Ok(Value::String(s.chars().rev().collect::().into())) } -fn substring(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn substring(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "substring"; ensure_args_count(span, name, params, args, 3)?; let s = ensure_string(name, ¶ms[0], &args[0])?; @@ -470,7 +480,7 @@ fn substring(span: &Span, params: &[Ref], args: &[Value]) -> Result } } -fn trim(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn trim(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -478,7 +488,7 @@ fn trim(span: &Span, params: &[Ref], args: &[Value]) -> Result { Ok(Value::String(s1.trim_matches(|c| s2.contains(c)).into())) } -fn trim_left(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn trim_left(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim_left"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -488,7 +498,7 @@ fn trim_left(span: &Span, params: &[Ref], args: &[Value]) -> Result )) } -fn trim_prefix(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn trim_prefix(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim_prefix"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -499,7 +509,7 @@ fn trim_prefix(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn trim_right(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim_right"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -509,14 +519,14 @@ fn trim_right(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn trim_space(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim_space"; ensure_args_count(span, name, params, args, 1)?; let s = ensure_string(name, ¶ms[0], &args[0])?; Ok(Value::String(s.trim().into())) } -fn trim_suffix(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn trim_suffix(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trim_suffix"; ensure_args_count(span, name, params, args, 2)?; let s1 = ensure_string(name, ¶ms[0], &args[0])?; @@ -527,7 +537,7 @@ fn trim_suffix(span: &Span, params: &[Ref], args: &[Value]) -> Result], args: &[Value]) -> Result { +fn upper(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "upper"; ensure_args_count(span, name, params, args, 1)?; let s = ensure_string(name, ¶ms[0], &args[0])?; diff --git a/src/builtins/time.rs b/src/builtins/time.rs index 2b692fe..0291160 100644 --- a/src/builtins/time.rs +++ b/src/builtins/time.rs @@ -16,7 +16,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("time.now_ns", (now_ns, 0)); } -fn now_ns(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn now_ns(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "time.now_ns"; ensure_args_count(span, name, params, args, 0)?; diff --git a/src/builtins/tracing.rs b/src/builtins/tracing.rs index 9a8dc3b..2870107 100644 --- a/src/builtins/tracing.rs +++ b/src/builtins/tracing.rs @@ -17,7 +17,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { // Symbol analyzer must ensure that vars used by trace are defined before // the trace statement. Scheduler must ensure the above constraint. -fn trace(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn trace(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "trace"; ensure_args_count(span, name, params, args, 1)?; let msg = ensure_string(name, ¶ms[0], &args[0])?; diff --git a/src/builtins/types.rs b/src/builtins/types.rs index b58bb6a..bc11b0e 100644 --- a/src/builtins/types.rs +++ b/src/builtins/types.rs @@ -22,37 +22,37 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) { m.insert("type_name", (type_name, 1)); } -fn is_array(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_array(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_array", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Array(_)))) } -fn is_boolean(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_boolean(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_boolean", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Bool(_)))) } -fn is_null(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_null(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_null", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Null))) } -fn is_number(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_number(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_number", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Number(_)))) } -fn is_object(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_object(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_object", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Object(_)))) } -fn is_set(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_set(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_set", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::Set(_)))) } -fn is_string(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn is_string(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { ensure_args_count(span, "is_string", params, args, 1)?; Ok(Value::Bool(matches!(&args[0], Value::String(_)))) } @@ -70,7 +70,12 @@ pub fn get_type(value: &Value) -> &str { } } -pub fn type_name(span: &Span, params: &[Ref], args: &[Value]) -> Result { +pub fn type_name( + span: &Span, + params: &[Ref], + args: &[Value], + _strict: bool, +) -> Result { ensure_args_count(span, "type_name", params, args, 1)?; Ok(Value::String(get_type(&args[0]).into())) } diff --git a/src/builtins/units.rs b/src/builtins/units.rs index df67d1d..6598c65 100644 --- a/src/builtins/units.rs +++ b/src/builtins/units.rs @@ -66,7 +66,7 @@ fn two_exp(suffix: &str) -> Option { }) } -fn parse(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn parse(span: &Span, params: &[Ref], args: &[Value], _strict: bool) -> Result { let name = "units.parse"; ensure_args_count(span, name, params, args, 1)?; let string = ensure_string(name, ¶ms[0], &args[0])?; @@ -143,7 +143,7 @@ fn tenb_exp(suffix: &str) -> Option { }) } -fn parse_bytes(span: &Span, params: &[Ref], args: &[Value]) -> Result { +fn parse_bytes(span: &Span, params: &[Ref], args: &[Value], strict: bool) -> Result { let name = "units.parse_bytes"; ensure_args_count(span, name, params, args, 1)?; let string = ensure_string(name, ¶ms[0], &args[0])?; @@ -166,12 +166,15 @@ fn parse_bytes(span: &Span, params: &[Ref], args: &[Value]) -> Result (string, ""), }; - let v: Value = if number_part.starts_with('.') { + let v: Value = match if number_part.starts_with('.') { serde_json::from_str(format!("0{number_part}").as_str()) } else { serde_json::from_str(number_part) - } - .with_context(|| span.error("could not parse number"))?; + } { + Ok(v) => v, + Err(_) if strict => bail!(span.error("could not parse number")), + _ => return Ok(Value::Undefined), + }; let mut n = match v { Value::Number(n) => n.clone(), diff --git a/src/engine.rs b/src/engine.rs index 8cb61f2..10a826e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -71,6 +71,10 @@ impl Engine { &self.modules } + pub fn set_strict_builtin_errors(&mut self, b: bool) { + self.interpreter.set_strict_builtin_errors(b) + } + fn prepare_for_eval(&mut self, enable_tracing: bool) -> Result<()> { self.interpreter.set_traces(enable_tracing); diff --git a/src/interpreter.rs b/src/interpreter.rs index b83947c..79ab322 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -44,6 +44,7 @@ pub struct Interpreter { no_rules_lookup: bool, traces: Option>>, allow_deprecated: bool, + strict_builtin_errors: bool, } impl Default for Interpreter { @@ -130,6 +131,7 @@ impl Interpreter { no_rules_lookup: false, traces: None, allow_deprecated: true, + strict_builtin_errors: true, } } @@ -168,14 +170,18 @@ impl Interpreter { }; } + pub fn set_strict_builtin_errors(&mut self, b: bool) { + self.strict_builtin_errors = b; + } + pub fn set_input(&mut self, input: Value) { self.input = input; info!("input: {:#?}", self.input); } pub fn init_with_document(&mut self) -> Result<()> { - *Self::make_or_get_value_mut(&mut self.with_document, &["data"])? = Value::new_object(); - *Self::make_or_get_value_mut(&mut self.with_document, &["input"])? = Value::new_object(); + *Self::make_or_get_value_mut(&mut self.with_document, &["data"])? = self.init_data.clone(); + *Self::make_or_get_value_mut(&mut self.with_document, &["input"])? = self.input.clone(); Ok(()) } @@ -789,6 +795,7 @@ impl Interpreter { // OPA raises the error sometimes in static scenarios, but doesn't // raise in scenarios due to data/input (Expr::Array { .. }, _) | (Expr::Object { .. }, _) => Ok(false), + _ => { let expr_value = self.lookup_or_eval_expr(cache, expr)?; if expr_value == Value::Undefined { @@ -1096,18 +1103,28 @@ impl Interpreter { }; if path[0] == "input" || path[0] == "data" { + if path.len() > 1 { + let vref = + Self::make_or_get_value_mut(&mut self.with_document, &path[0..1])?; + match *vref { + Value::Object(_) => (), + _ => *vref = Value::new_object(), + } + } + *Self::make_or_get_value_mut(&mut self.with_document, &path[..])? = value; - } /* else if path.len() == 1 { - // TODO: handle var in current module. - } else { - // TODO: error about input, data - } */ + } + + /* else if path.len() == 1 { + // TODO: handle var in current module. + } else { + // TODO: error about input, data + } */ } self.data = self.with_document["data"].clone(); self.input = self.with_document["input"].clone(); self.processed.clear(); - (with_document, input, data, processed, with_functions) } else { ( @@ -1651,7 +1668,7 @@ impl Interpreter { } } - let v = builtin.0(span, params, &args[..])?; + let v = builtin.0(span, params, &args[..], self.strict_builtin_errors)?; // Handle trace function. // TODO: with modifier. @@ -1694,18 +1711,29 @@ impl Interpreter { _ => orig_fcn_path.clone(), }; + let empty = vec![]; let fcns_rules = match self.lookup_function_by_name(&fcn_path) { Some(r) => r, _ => { + if self.default_rules.get(&fcn_path).is_some() + || self + .default_rules + .get(&get_path_string(fcn, Some(&self.current_module_path))?) + .is_some() + { + // process default functions later. + &empty + } // Look up builtin function. - if let Ok(Some(builtin)) = self.lookup_builtin(span, &fcn_path) { + else if let Ok(Some(builtin)) = self.lookup_builtin(span, &fcn_path) { let r = self.eval_builtin_call(span, &fcn_path.clone(), *builtin, params); if orig_fcn_path != fcn_path { self.with_functions.insert(orig_fcn_path, fcn_path); } return r; + } else { + bail!(span.error(format!("could not find function {fcn_path}").as_str())); } - bail!(span.error(format!("could not find function {fcn_path}").as_str())); } }; @@ -1757,10 +1785,10 @@ impl Interpreter { let mut type_match = BTreeSet::new(); for (idx, a) in args.iter().enumerate() { - if self - .make_bindings(false, &mut type_match, &mut cache, a, ¶m_values[idx]) - .is_err() - { + let b = + self.make_bindings(false, &mut type_match, &mut cache, a, ¶m_values[idx]); + + if b.ok() != Some(true) { self.scopes = scopes; continue 'outer; } @@ -1799,7 +1827,9 @@ impl Interpreter { // If the function execution resulted in undefined, then propagate it. Value::Undefined => Value::Undefined, - _ => bail!("internal error: function did not return set {value:?}"), + + // Function returned a non set value + v => v.clone(), }; // Restore local variables for current context. @@ -1810,6 +1840,34 @@ impl Interpreter { } } + if results.is_empty() { + // Back up local variables of current function and empty + // the local variables of callee function. + let scopes = std::mem::take(&mut self.scopes); + if errors.is_empty() { + // Check if any default rules can be evaluated. + // TODO: with mod + let rules = match self.default_rules.get(&fcn_path).cloned() { + Some(rules) => Some(rules), + None => { + let fcn_path = get_path_string(fcn, Some(&self.current_module_path))?; + self.default_rules.get(&fcn_path).cloned() + } + }; + if let Some(rules) = rules { + for (rule, _) in rules.iter() { + if let Rule::Default { value, .. } = rule.as_ref() { + match self.eval_expr(value) { + Ok(v) => results.push(v), + Err(e) => errors.push(e), + } + } + } + } + } + self.scopes = scopes; + } + if results.is_empty() { if errors.is_empty() { return Ok(Value::Undefined); @@ -1937,15 +1995,28 @@ impl Interpreter { return Ok(v); } - // Evaluate rule corresponding to longest matching path. - for i in (1..fields.len() + 1).rev() { + if fields.is_empty() { + bail!(span.error("this results in recursive evaluation of data.")) + } + + // Find the rule to which the var being looked up corresponds to. This is the prefix for + // which rules exist. + let mut found = false; + for i in 1..fields.len() + 1 { let path = "data.".to_owned() + &fields[0..i].join("."); if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() { self.ensure_rule_evaluated(path)?; + found = true; break; } } + // TODO: emit this error only if the var can belong to a rego module; not data specified via json/yaml. + //if !no_error && !found { + // bail!(span.error("var is unsafe")); + //} + let _ = found; + Ok(Self::get_value_chained(self.data.clone(), fields)) } else if !self.modules.is_empty() { let path = Parser::get_path_ref_components(&self.module.clone().unwrap().package.refr)?; @@ -1960,10 +2031,8 @@ impl Interpreter { return Ok(v); } - // Add module prefix and ensure that any matching rule is evaluated. - let module_path = - Self::get_path_string(&self.current_module()?.package.refr, Some("data"))?; - let rule_path = module_path + "." + name.text(); + // Ensure that all the rules having common prefix (name) are evaluated. + let rule_path = "data.".to_owned() + &path.join("."); if !no_error && self.rules.get(&rule_path).is_none() @@ -1971,6 +2040,7 @@ impl Interpreter { { bail!(span.error("var is unsafe")); } + self.ensure_rule_evaluated(rule_path)?; let value = Self::get_value_chained(self.data.clone(), &path[..]); @@ -2318,9 +2388,18 @@ impl Interpreter { } if let Rule::Default { - span, refr, value, .. + span, + refr, + value, + args, + .. } = rule.as_ref() { + if !args.is_empty() { + // Non-zero function defaults are evaluated differently. + return Ok(()); + } + let scopes = std::mem::take(&mut self.scopes); let mut path = @@ -2588,8 +2667,10 @@ impl Interpreter { Expr::RefBrack { refr, .. } => refr, _ => refr, }; - let path = Self::get_path_string(refr, None)?; - let path = self.current_module_path.clone() + "." + &path; + //let path = Self::get_path_string(refr, None)?; + let path = get_root_var(refr)?; + let path = path.text(); + let path = self.current_module_path.clone() + "." + path; match self.rules.entry(path) { Entry::Occupied(o) => { o.into_mut().push(rule.clone()); @@ -2617,8 +2698,10 @@ impl Interpreter { _ => (refr, None), }; - let path = Self::get_path_string(refr, None)?; - let path = self.current_module_path.clone() + "." + &path; + //let path = Self::get_path_string(refr, None)?; + let path = get_root_var(refr)?; + let path = path.text(); + let path = self.current_module_path.clone() + "." + path; match self.default_rules.entry(path) { Entry::Occupied(o) => { for (_, i) in o.get() { diff --git a/src/parser.rs b/src/parser.rs index 942aac0..1618b85 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -209,7 +209,9 @@ impl<'source> Parser<'source> { match self.tok.0 { TokenKind::Ident if self.is_keyword(*span.text()) - || self.is_imported_future_keyword(*span.text()) => + || (self.is_imported_future_keyword(*span.text()) + // contains can be the name of a builtin even when a keyword + && *span.text() != "contains") => { Err(self.source.error( self.tok.1.line, @@ -1425,6 +1427,25 @@ impl<'source> Parser<'source> { self.expect("default", "while parsing default rule")?; let rule_ref = Ref::new(self.parse_rule_ref()?); + let mut args = vec![]; + if *self.token_text() == "(" { + self.next_token()?; + if *self.token_text() != ")" { + loop { + let arg = self.parse_ident()?; + if *arg.text() != "_" && args.iter().any(|a: &Span| *a.text() == *arg.text()) { + bail!(arg.error("repeating parameter name")); + } + args.push(arg); + if *self.token_text() == ")" || self.tok.0 == TokenKind::Eof { + break; + } + self.expect(",", "while parsing default rule parameters")?; + } + } + self.expect(")", "while parsing default rule parameters")?; + } + let op = match *self.token_text() { "=" => AssignOp::Eq, ":=" => AssignOp::ColEq, @@ -1443,6 +1464,7 @@ impl<'source> Parser<'source> { Ok(Rule::Default { span, refr: rule_ref, + args: args.into_iter().map(|a| Ref::new(Expr::Var(a))).collect(), op, value, }) diff --git a/src/scheduler.rs b/src/scheduler.rs index 7f632c9..9de2625 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -205,7 +205,8 @@ pub fn schedule( #[derive(Clone, Default, Debug)] pub struct Scope { - pub locals: BTreeSet, + pub locals: BTreeMap, + pub unscoped: BTreeSet, pub inputs: BTreeSet, } @@ -269,8 +270,24 @@ fn traverse(expr: &Ref, f: &mut dyn FnMut(&Ref) -> Result) -> Ok(()) } -fn var_exists(name: &SourceStr, parent_scopes: &[Scope]) -> bool { - parent_scopes.iter().rev().any(|s| s.locals.contains(name)) +fn var_exists(var: &Span, parent_scopes: &[Scope]) -> bool { + let name = var.source_str(); + + for pscope in parent_scopes.iter().rev() { + if pscope.unscoped.contains(&name) { + return true; + } + // Check parent scope vars defined using :=. + if let Some(s) = pscope.locals.get(&name) { + // Note: Since a rule cannot span multiple files, it is safe to check only + // the line numbers. + if s.line <= var.line { + // The variable was defined in parent scope prior to current comprehension. + return true; + } + } + } + false } fn gather_assigned_vars( @@ -285,19 +302,19 @@ fn gather_assigned_vars( // Record local var that can shadow input var. Var(v) if can_shadow => { - scope.locals.insert(v.source_str()); + scope.locals.insert(v.source_str(), v.clone()); Ok(false) } // Record input vars. - Var(v) if var_exists(&v.source_str(), parent_scopes) => { + Var(v) if var_exists(v, parent_scopes) => { scope.inputs.insert(v.source_str()); Ok(false) } // Record local var. Var(v) => { - scope.locals.insert(v.source_str()); + scope.unscoped.insert(v.source_str()); Ok(false) } @@ -309,11 +326,8 @@ fn gather_assigned_vars( fn gather_input_vars(expr: &Ref, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> { traverse(expr, &mut |e| match e.as_ref() { - Var(v) if var_exists(&v.source_str(), parent_scopes) => { - let var = v.source_str(); - if !scope.locals.contains(&var) { - scope.inputs.insert(var); - } + Var(v) if !scope.unscoped.contains(&v.source_str()) && var_exists(v, parent_scopes) => { + scope.inputs.insert(v.source_str()); Ok(false) } _ => Ok(true), @@ -322,14 +336,12 @@ fn gather_input_vars(expr: &Ref, parent_scopes: &[Scope], scope: &mut Scop fn gather_loop_vars(expr: &Ref, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> { traverse(expr, &mut |e| match e.as_ref() { - Var(v) if var_exists(&v.source_str(), parent_scopes) => Ok(false), + Var(v) if var_exists(v, parent_scopes) => Ok(false), RefBrack { index, .. } => { if let Var(v) = index.as_ref() { - if !matches!(*v.text(), "_" | "input" | "data") - && !var_exists(&v.source_str(), parent_scopes) - { + if !matches!(*v.text(), "_" | "input" | "data") && !var_exists(v, parent_scopes) { // Treat this as an index var. - scope.locals.insert(v.source_str()); + scope.unscoped.insert(v.source_str()); } } Ok(true) @@ -367,7 +379,7 @@ fn gather_vars( pub struct Analyzer { packages: BTreeMap, - locals: BTreeMap, Scope>, + scope_table: BTreeMap, Scope>, scopes: Vec, order: BTreeMap, Vec>, functions: FunctionTable, @@ -390,7 +402,7 @@ impl Analyzer { pub fn new() -> Analyzer { Analyzer { packages: BTreeMap::new(), - locals: BTreeMap::new(), + scope_table: BTreeMap::new(), scopes: vec![], order: BTreeMap::new(), functions: FunctionTable::new(), @@ -407,7 +419,7 @@ impl Analyzer { } Ok(Schedule { - scopes: self.locals, + scopes: self.scope_table, order: self.order, }) } @@ -421,7 +433,7 @@ impl Analyzer { self.analyze_query(None, None, query, Scope::default())?; Ok(Schedule { - scopes: self.locals, + scopes: self.scope_table, order: self.order, }) } @@ -441,7 +453,7 @@ impl Analyzer { .. } => get_root_var(refr)?, }; - scope.locals.insert(var); + scope.unscoped.insert(var); } } @@ -529,9 +541,12 @@ impl Analyzer { RuleHead::Set { key, .. } => (key.clone(), None, scope), RuleHead::Func { args, assign, .. } => { for a in args.iter() { - if let Var(v) = a.as_ref() { - scope.locals.insert(v.source_str()); - } + traverse(a, &mut |e| { + if let Var(v) = e.as_ref() { + scope.unscoped.insert(v.source_str()); + } + Ok(true) + })?; } (None, assign.as_ref().map(|a| a.value.clone()), scope) } @@ -549,7 +564,7 @@ impl Analyzer { for stmt in &query.stmts { match &stmt.literal { Literal::SomeVars { vars, .. } => vars.iter().for_each(|v| { - scope.locals.insert(v.source_str()); + scope.locals.insert(v.source_str(), v.clone()); }), Literal::SomeIn { key, @@ -597,8 +612,9 @@ impl Analyzer { } // Remove input vars that are shadowed. - for v in &scope.locals { + for v in scope.locals.keys() { scope.inputs.remove(v); + scope.unscoped.remove(v); } Ok(()) @@ -621,7 +637,7 @@ impl Analyzer { _ => false, }; - if scope.locals.contains(&name) + if scope.locals.contains_key(&name) || scope.unscoped.contains(&name) /*|| scope.inputs.contains(name) */ { if !is_extra_arg { @@ -637,7 +653,7 @@ impl Analyzer { RefBrack { refr, index, .. } => { if let Var(v) = index.as_ref() { let var = v.source_str(); - if scope.locals.contains(&var) { + if scope.locals.contains_key(&var) || scope.unscoped.contains(&var) { let (rb_used_vars, rb_comprs) = Self::gather_used_vars_comprs_index_vars( refr, scope, @@ -681,7 +697,7 @@ impl Analyzer { let compr_scope = match compr.as_ref() { Expr::ArrayCompr { query, term, .. } | Expr::SetCompr { query, term, .. } => { self.analyze_query(None, Some(term.clone()), query, Scope::default())?; - self.locals.get(query) + self.scope_table.get(query) } Expr::ObjectCompr { query, key, value, .. @@ -692,7 +708,7 @@ impl Analyzer { query, Scope::default(), )?; - self.locals.get(query) + self.scope_table.get(query) } _ => break, }; @@ -700,7 +716,7 @@ impl Analyzer { // Record vars used by the comprehension scope. if let Some(compr_scope) = compr_scope { for iv in &compr_scope.inputs { - if scope.locals.contains(iv) { + if scope.locals.contains_key(iv) || scope.unscoped.contains(iv) { // Record possible first use of current scope's local var. first_use.entry(iv.clone()).or_insert(compr.span().clone()); used_vars.push(iv.clone()); @@ -727,11 +743,13 @@ impl Analyzer { traverse(expr, &mut |e| match e.as_ref() { Var(v) => { let var = v.source_str(); - if scope.locals.contains(&var) { + if scope.locals.contains_key(&var) { if check_first_use { Self::check_first_use(v, first_use)?; } vars.push(var); + } else if scope.unscoped.contains(&var) { + vars.push(var); } Ok(false) } @@ -890,7 +908,7 @@ impl Analyzer { non_vars: &mut Vec>, ) -> Result<()> { traverse(expr, &mut |e| match e.as_ref() { - Var(v) if scope.locals.contains(&v.source_str()) => { + Var(v) if scope.locals.contains_key(&v.source_str()) => { vars.push(v.source_str()); Ok(false) } @@ -1012,8 +1030,8 @@ impl Analyzer { let mut extras_scope = Scope::default(); gather_assigned_vars(ea, false, &self.scopes, &mut extras_scope)?; - for var in &extras_scope.locals { - scope.locals.insert(var.clone()); + for var in &extras_scope.unscoped { + scope.unscoped.insert(var.clone()); } // Gather vars being used. @@ -1022,7 +1040,7 @@ impl Analyzer { &mut scope, &mut first_use, &mut definitions, - &Some(&extras_scope.locals), + &Some(&extras_scope.unscoped), )?; self.process_comprs( @@ -1032,8 +1050,8 @@ impl Analyzer { &mut used_vars, )?; - if !extras_scope.locals.is_empty() { - for var in extras_scope.locals { + if !extras_scope.unscoped.is_empty() { + for var in extras_scope.unscoped { definitions.push(Definition { var, used_vars: used_vars.clone(), @@ -1076,9 +1094,9 @@ impl Analyzer { self.scopes.push(scope.clone()); let mut e_scope = Scope::default(); if let Some(key) = key { - e_scope.locals.insert(key.source_str()); + e_scope.locals.insert(key.source_str(), key.clone()); } - e_scope.locals.insert(value.source_str()); + e_scope.locals.insert(value.source_str(), value.clone()); self.scopes.push(e_scope); // TODO: mark first use of key, value so that they cannot be := assigned @@ -1112,7 +1130,7 @@ impl Analyzer { } _ => (), } - self.locals.insert(query.clone(), scope); + self.scope_table.insert(query.clone(), scope); Ok(()) } diff --git a/tests/opa.passing b/tests/opa.passing index d6f0591..69768f4 100644 --- a/tests/opa.passing +++ b/tests/opa.passing @@ -10,33 +10,50 @@ bitsor bitsshiftleft bitsshiftright bitsxor +casts comparisonexpr completedoc compositebasedereference -dataderef +comprehensions +containskeyword cryptohmacequal -cryptohmacmd5 +cryptohmacmd5 cryptohmacsha1 cryptohmacsha256 cryptohmacsha512 cryptomd5 -cryptsha1 +cryptosha1 cryptosha256 +dataderef +disjunction +elsekeyword embeddedvirtualdoc evaltermexpr +every example fix1863 +functionerrors +functions globmatch +globquotemeta helloworld indexing +indirectreferences intersection invalidkeyerror jsonfilteridempotent +jwtencodesignheadererrors +jwtencodesignpayloaderrors nestedreferences +objectfilter objectfilteridempotent objectfilternonstringkey +objectget +objectkeys +objectremove objectremoveidempotent objectremovenonstringkey +partialdocconstants partialsetdoc rand regexfind @@ -57,6 +74,7 @@ trimprefix trimright trimspace trimsuffix +type typebuiltin typenamebuiltin undos diff --git a/tests/opa.rs b/tests/opa.rs index fbe2466..f93b380 100644 --- a/tests/opa.rs +++ b/tests/opa.rs @@ -16,18 +16,33 @@ const OPA_REPO: &str = "https://github.com/open-policy-agent/opa"; const OPA_BRANCH: &str = "v0.58.0"; #[derive(Serialize, Deserialize, PartialEq, Debug)] +#[serde(deny_unknown_fields)] struct TestCase { + #[serde(skip_serializing_if = "Option::is_none")] data: Option, + #[serde(skip_serializing_if = "Option::is_none")] input: Option, + #[serde(skip_serializing_if = "Option::is_none")] + input_term: Option, + #[serde(skip_serializing_if = "Option::is_none")] modules: Option>, note: String, query: String, + #[serde(skip_serializing_if = "Option::is_none")] sort_bindings: Option, + #[serde(skip_serializing_if = "Option::is_none")] want_result: Option, + #[serde(skip_serializing_if = "Option::is_none")] skip: Option, + #[serde(skip_serializing_if = "Option::is_none")] error: Option, + #[serde(skip_serializing_if = "Option::is_none")] traces: Option, + #[serde(skip_serializing_if = "Option::is_none")] + strict_error: Option, + #[serde(skip_serializing_if = "Option::is_none")] want_error: Option, + #[serde(skip_serializing_if = "Option::is_none")] want_error_code: Option, } @@ -45,11 +60,18 @@ fn eval_test_case(case: &TestCase) -> Result { if let Some(input) = &case.input { engine.set_input(input.clone()); } + if let Some(input_term) = &case.input_term { + let input = Value::from_json_str(&input_term)?; + engine.set_input(input); + } if let Some(modules) = &case.modules { for (idx, rego) in modules.iter().enumerate() { engine.add_policy(format!("rego{idx}.rego"), rego.clone())?; } } + + engine.set_strict_builtin_errors(case.strict_error.unwrap_or_default()); + let query_results = engine.eval_query(case.query.clone(), true)?; let mut values = vec![]; @@ -182,9 +204,11 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> { println!("\nOPA TESTSUITE STATUS"); println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL"); let (mut npass, mut nfail) = (0, 0); + let mut passing = vec![]; for (dir, (pass, fail)) in status { if fail == 0 { println!("\x1b[32m {dir:40}: {pass:4} {fail:4}\x1b[0m"); + passing.push(dir); } else { println!("\x1b[31m {dir:40}: {pass:4} {fail:4}\x1b[0m"); } @@ -193,6 +217,8 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> { } println!(); + std::fs::write("target/opa.passing", passing.join("\n"))?; + if npass == 0 && nfail == 0 { bail!("no matching tests found."); } else if nfail == 0 { diff --git a/tests/parser/mod.rs b/tests/parser/mod.rs index 317b3b7..eecf189 100644 --- a/tests/parser/mod.rs +++ b/tests/parser/mod.rs @@ -569,12 +569,14 @@ fn match_rule(r: &Rule, v: &Value) -> Result<()> { Rule::Default { span, refr, + args, op, value, } => { let obj = &v["default"]; match_span_opt(span, &obj["span"])?; match_expr(refr, &obj["refr"])?; + match_vec(span /*dummy*/, args, &obj["args"])?; match_assign_op(span, op, &obj["op"])?; match_expr(value, &obj["value"]) } diff --git a/tests/scheduler/analyzer/basic.yaml b/tests/scheduler/analyzer/basic.yaml index 562a271..a920f2d 100644 --- a/tests/scheduler/analyzer/basic.yaml +++ b/tests/scheduler/analyzer/basic.yaml @@ -37,9 +37,12 @@ cases: r = 1 rrr = "fun" scopes: - - locals: ["p", "y", "q", "x", "a", "b", "idx"] + - locals: ["x"] + unscoped: ["p", "y", "q", "a", "b", "idx"] inputs: ["r", "rrr"] - - locals: ["k", "r1"] + - locals: [] + unscoped: ["k", "r1"] inputs: ["a", "idx", "rrr"] - - locals: ["q", "idx1", "t"] + - locals: ["q"] + unscoped: ["idx1", "t"] inputs: ["rrr"] diff --git a/tests/scheduler/analyzer/mod.rs b/tests/scheduler/analyzer/mod.rs index cda57cf..0be7e57 100644 --- a/tests/scheduler/analyzer/mod.rs +++ b/tests/scheduler/analyzer/mod.rs @@ -12,6 +12,7 @@ use std::collections::BTreeSet; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct Scope { pub locals: BTreeSet, + pub unscoped: BTreeSet, pub inputs: BTreeSet, } @@ -27,8 +28,11 @@ struct YamlTest { cases: Vec, } -fn to_string_set(s: &BTreeSet) -> BTreeSet { - s.iter().map(|s| s.to_string()).collect() +fn to_string_set<'a, I>(itr: I) -> BTreeSet +where + I: std::iter::Iterator, +{ + itr.map(|s| s.to_string()).collect() } fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> { @@ -55,8 +59,18 @@ fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> { if idx > expected_scopes.len() { bail!("extra scope generated.") } - assert_eq!(to_string_set(&scope.locals), expected_scopes[idx].locals); - assert_eq!(to_string_set(&scope.inputs), expected_scopes[idx].inputs); + assert_eq!( + to_string_set(scope.locals.keys()), + expected_scopes[idx].locals + ); + assert_eq!( + to_string_set(scope.unscoped.iter()), + expected_scopes[idx].unscoped + ); + assert_eq!( + to_string_set(scope.inputs.iter()), + expected_scopes[idx].inputs + ); println!("scope {idx} matched.") }