More OPA conformant semantics (#62)

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2023-12-11 09:00:06 -08:00
committed by GitHub
parent 8a73b4bef9
commit 73ee18f002
32 changed files with 542 additions and 205 deletions

View File

@@ -15,32 +15,33 @@ Regorus is available as a library that can be easily integrated into your Rust p
## Getting Started ## 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 Check that the regorus example program is working
$ target/release/examples/regorus $ regorus
Usage: regorus <COMMAND> Usage: regorus <COMMAND>
Commands: Commands:
eval Evaluate a Rego Query eval Evaluate a Rego Query
lex Tokenize a Rego policy 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) help Print this message or the help of the given subcommand(s)
Options: Options:
-h, --help Print help -h, --help Print help
-V, --version Print version -V, --version Print versionUsage: regorus <COMMAND>
First, let's evaluate a simple Rego expression `1*2+3` 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 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)): 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) Finally, evaluate real-world [policies](tests/aci/) used in Azure Container Instances (ACI)
target/release/examples/regorus eval -d tests/aci/framework.rego \ regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x
-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
## ACI Policies ## 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`: 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 \ regorus eval -b tests/aci -d tests/aci/data.json -i tests/aci/input.json data.policy.mount_overlay=x
-b tests/aci \
-d tests/aci/data.json \
-i tests/aci/input.json \
data.framework.mount_overlay=x
Verify that [OPA](https://github.com/open-policy-agent/opa/releases) produces the same output 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 ## Performance
To check how fast Regorus runs on your system, first install a tool like [hyperfine](https://github.com/sharkdp/hyperfine). 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, 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" $ hyperfine "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 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] 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 Range (min … max): 4.4 ms … 6.0 ms 422 runs

View File

@@ -142,7 +142,7 @@ enum RegorusCommand {
verbose: bool, verbose: bool,
}, },
/// Parse q Rego policy. /// Parse a Rego policy.
Parse { Parse {
/// Rego policy file. /// Rego policy file.
file: String, file: String,

View File

@@ -321,6 +321,7 @@ pub enum Rule {
Default { Default {
span: Span, span: Span,
refr: Ref<Expr>, refr: Ref<Expr>,
args: Vec<Ref<Expr>>,
op: AssignOp, op: AssignOp,
value: Ref<Expr>, value: Ref<Expr>,
}, },

View File

@@ -21,7 +21,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("sum", (sum, 1)); m.insert("sum", (sum, 1));
} }
fn count(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn count(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "count", params, args, 1)?; ensure_args_count(span, "count", params, args, 1)?;
Ok(Value::from(Number::from(match &args[0] { Ok(Value::from(Number::from(match &args[0] {
@@ -38,7 +38,7 @@ fn count(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}))) })))
} }
fn max(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn max(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "max", params, args, 1)?; ensure_args_count(span, "max", params, args, 1)?;
Ok(match &args[0] { Ok(match &args[0] {
@@ -53,7 +53,7 @@ fn max(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn min(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn min(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "min", params, args, 1)?; ensure_args_count(span, "min", params, args, 1)?;
Ok(match &args[0] { Ok(match &args[0] {
@@ -68,7 +68,7 @@ fn min(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn product(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn product(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "product", params, args, 1)?; ensure_args_count(span, "product", params, args, 1)?;
let mut v = Number::from(1_u64); let mut v = Number::from(1_u64);
@@ -93,7 +93,7 @@ fn product(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
})) }))
} }
fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "sort", params, args, 1)?; ensure_args_count(span, "sort", params, args, 1)?;
Ok(match &args[0] { Ok(match &args[0] {
Value::Array(a) => { Value::Array(a) => {
@@ -110,7 +110,7 @@ fn sort(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn sum(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn sum(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "sum", params, args, 1)?; ensure_args_count(span, "sum", params, args, 1)?;
let mut v = Number::from(0_u64); let mut v = Number::from(0_u64);

View File

@@ -18,7 +18,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("array.slice", (slice, 3)); m.insert("array.slice", (slice, 3));
} }
fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "array.concat"; let name = "array.concat";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let mut v1 = ensure_array(name, &params[0], args[0].clone())?; let mut v1 = ensure_array(name, &params[0], args[0].clone())?;
@@ -28,7 +28,7 @@ fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::Array(v1)) Ok(Value::Array(v1))
} }
fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "array.reverse"; let name = "array.reverse";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -37,7 +37,7 @@ fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::Array(v1)) Ok(Value::Array(v1))
} }
fn slice(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn slice(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "array.slice"; let name = "array.slice";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;

View File

@@ -21,7 +21,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("bits.xor", (xor, 2)); m.insert("bits.xor", (xor, 2));
} }
fn and(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn and(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.and"; let name = "bits.and";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -34,7 +34,7 @@ fn and(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn lsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn lsh(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.lsh"; let name = "bits.lsh";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -47,7 +47,7 @@ fn lsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn negate(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn negate(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.negate"; let name = "bits.negate";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -59,7 +59,7 @@ fn negate(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn or(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn or(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.or"; let name = "bits.or";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -72,7 +72,7 @@ fn or(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn rsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn rsh(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.rsh"; let name = "bits.rsh";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -85,7 +85,7 @@ fn rsh(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn xor(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn xor(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "bits.xor"; let name = "bits.xor";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;

View File

@@ -15,12 +15,13 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("to_number", (to_number, 1)); m.insert("to_number", (to_number, 1));
} }
fn to_number(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn to_number(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "to_number"; let name = "to_number";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let span = params[0].span(); let span = params[0].span();
Ok(match &args[0] { Ok(match &args[0] {
Value::Null => Value::from(0u64),
Value::Bool(true) => Value::from(1u64), Value::Bool(true) => Value::from(1u64),
Value::Bool(false) => Value::from(0u64), Value::Bool(false) => Value::from(0u64),
Value::Number(_) => args[0].clone(), Value::Number(_) => args[0].clone(),
@@ -34,7 +35,9 @@ fn to_number(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
} }
}, },
_ => { _ => {
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())
);
} }
}) })
} }

View File

@@ -28,7 +28,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("crypto.sha256", (crypto_sha256, 1)); m.insert("crypto.sha256", (crypto_sha256, 1));
} }
fn hmac_equal_fixed_time(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn hmac_equal_fixed_time(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "crypto.hmac.equal"; let name = "crypto.hmac.equal";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -41,7 +46,7 @@ fn hmac_equal_fixed_time(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> R
))) )))
} }
fn hmac_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn hmac_md5(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.md5"; let name = "crypto.hmac.md5";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -57,7 +62,7 @@ fn hmac_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
Ok(Value::String(hex::encode(result.into_bytes()).into())) Ok(Value::String(hex::encode(result.into_bytes()).into()))
} }
fn hmac_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn hmac_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.sha1"; let name = "crypto.hmac.sha1";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -73,7 +78,7 @@ fn hmac_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
Ok(Value::String(hex::encode(result.into_bytes()).into())) Ok(Value::String(hex::encode(result.into_bytes()).into()))
} }
fn hmac_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn hmac_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.sha256"; let name = "crypto.hmac.sha256";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -89,7 +94,7 @@ fn hmac_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
Ok(Value::String(hex::encode(result.into_bytes()).into())) Ok(Value::String(hex::encode(result.into_bytes()).into()))
} }
fn hmac_sha512(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn hmac_sha512(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.hmac.sha512"; let name = "crypto.hmac.sha512";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -105,7 +110,7 @@ fn hmac_sha512(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
Ok(Value::String(hex::encode(result.into_bytes()).into())) Ok(Value::String(hex::encode(result.into_bytes()).into()))
} }
fn crypto_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn crypto_md5(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.md5"; let name = "crypto.md5";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -119,7 +124,7 @@ fn crypto_md5(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value
Ok(Value::String(hex::encode(result).into())) Ok(Value::String(hex::encode(result).into()))
} }
fn crypto_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn crypto_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "crypto.sha1"; let name = "crypto.sha1";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -133,7 +138,12 @@ fn crypto_sha1(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
Ok(Value::String(hex::encode(result).into())) Ok(Value::String(hex::encode(result).into()))
} }
fn crypto_sha256(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn crypto_sha256(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "crypto.sha256"; let name = "crypto.sha256";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;

View File

@@ -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 // Symbol analyzer must ensure that vars used by print are defined before
// the print statement. Scheduler must ensure the above constraint. // the print statement. Scheduler must ensure the above constraint.
// Additionally interpreter must allow undefined inputs to print. // Additionally interpreter must allow undefined inputs to print.
fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn print(span: &Span, _params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
if args.len() > MAX_ARGS as usize { if args.len() > MAX_ARGS as usize {
bail!(span.error("print supports up to 100 arguments")); bail!(span.error("print supports up to 100 arguments"));
} }

View File

@@ -19,7 +19,13 @@ lazy_static! {
let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new(); let mut m : HashMap<&'static str, BuiltinFcn> = HashMap::new();
m.insert("all", (all, 1)); 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)); m.insert("set_diff", (set_diff, 2));
#[cfg(feature = "crypto")] #[cfg(feature = "crypto")]
@@ -28,7 +34,7 @@ lazy_static! {
}; };
} }
fn all(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn all(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "all", params, args, 1)?; ensure_args_count(span, "all", params, args, 1)?;
Ok(Value::Bool(match &args[0] { Ok(Value::Bool(match &args[0] {
@@ -41,7 +47,7 @@ fn all(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
})) }))
} }
fn any(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn any(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "any", params, args, 1)?; ensure_args_count(span, "any", params, args, 1)?;
Ok(Value::Bool(match &args[0] { Ok(Value::Bool(match &args[0] {
@@ -54,10 +60,70 @@ fn any(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
})) }))
} }
fn set_diff(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn set_diff(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "set_diff"; let name = "set_diff";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_set(name, &params[0], args[0].clone())?; let s1 = ensure_set(name, &params[0], args[0].clone())?;
let s2 = ensure_set(name, &params[1], args[1].clone())?; let s2 = ensure_set(name, &params[1], args[1].clone())?;
Ok(Value::from_set(s1.difference(&s2).cloned().collect())) Ok(Value::from_set(s1.difference(&s2).cloned().collect()))
} }
fn cast_array(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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<Expr>], args: &[Value], strict: bool) -> Result<Value> {
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),
}
}

View File

@@ -26,7 +26,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
} }
} }
fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn base64_decode(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "base64.decode"; let name = "base64.decode";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -38,7 +43,12 @@ fn base64_decode(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
fn yaml_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn yaml_is_valid(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "yaml.is_valid"; let name = "yaml.is_valid";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -47,7 +57,7 @@ fn yaml_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "yaml.marshal"; let name = "yaml.marshal";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String( Ok(Value::String(
@@ -58,14 +68,24 @@ fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Val
} }
#[cfg(feature = "yaml")] #[cfg(feature = "yaml")]
fn yaml_unmarshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn yaml_unmarshal(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "yaml.unmarshal"; let name = "yaml.unmarshal";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let yaml_str = ensure_string(name, &params[0], &args[0])?; let yaml_str = ensure_string(name, &params[0], &args[0])?;
Value::from_yaml_str(&yaml_str).with_context(|| span.error("could not deserialize yaml.")) Value::from_yaml_str(&yaml_str).with_context(|| span.error("could not deserialize yaml."))
} }
fn json_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn json_is_valid(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "json.is_valid"; let name = "json.is_valid";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
@@ -73,7 +93,7 @@ fn json_is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
Ok(Value::Bool(Value::from_json_str(&json_str).is_ok())) Ok(Value::Bool(Value::from_json_str(&json_str).is_ok()))
} }
fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "json.marshal"; let name = "json.marshal";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String( Ok(Value::String(
@@ -83,7 +103,12 @@ fn json_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Val
)) ))
} }
fn json_unmarshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn json_unmarshal(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "json.unmarshal"; let name = "json.unmarshal";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let json_str = ensure_string(name, &params[0], &args[0])?; let json_str = ensure_string(name, &params[0], &args[0])?;

View File

@@ -55,7 +55,7 @@ fn make_glob<'a>(pattern: &'a str, span: &'a Span) -> Result<Glob<'a>> {
Glob::new(pattern).or_else(|_| bail!(span.error("invalid glob"))) Glob::new(pattern).or_else(|_| bail!(span.error("invalid glob")))
} }
fn glob_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn glob_match(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "glob.match"; let name = "glob.match";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
@@ -101,7 +101,7 @@ fn glob_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value
Ok(Value::Bool(glob.is_match(&value[..]))) Ok(Value::Bool(glob.is_match(&value[..])))
} }
fn quote_meta(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn quote_meta(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "glob.quote_meta"; let name = "glob.quote_meta";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;

View File

@@ -38,7 +38,7 @@ use std::collections::HashMap;
use anyhow::Result; use anyhow::Result;
use lazy_static::lazy_static; use lazy_static::lazy_static;
pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value]) -> Result<Value>, u8); pub type BuiltinFcn = (fn(&Span, &[Ref<Expr>], &[Value], bool) -> Result<Value>, u8);
#[cfg(feature = "deprecated")] #[cfg(feature = "deprecated")]
pub use deprecated::DEPRECATED; pub use deprecated::DEPRECATED;

View File

@@ -48,28 +48,28 @@ pub fn arithmetic_operation(
})) }))
} }
fn abs(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn abs(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "abs", params, args, 1)?; ensure_args_count(span, "abs", params, args, 1)?;
Ok(Value::from( Ok(Value::from(
ensure_numeric("abs", &params[0], &args[0])?.abs(), ensure_numeric("abs", &params[0], &args[0])?.abs(),
)) ))
} }
fn ceil(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn ceil(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "ceil", params, args, 1)?; ensure_args_count(span, "ceil", params, args, 1)?;
Ok(Value::from( Ok(Value::from(
ensure_numeric("ceil", &params[0], &args[0])?.ceil(), ensure_numeric("ceil", &params[0], &args[0])?.ceil(),
)) ))
} }
fn floor(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn floor(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "floor", params, args, 1)?; ensure_args_count(span, "floor", params, args, 1)?;
Ok(Value::from( Ok(Value::from(
ensure_numeric("floor", &params[0], &args[0])?.floor(), ensure_numeric("floor", &params[0], &args[0])?.floor(),
)) ))
} }
fn range(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "numbers.range", params, args, 2)?; ensure_args_count(span, "numbers.range", params, args, 2)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0].clone())?; let v1 = ensure_numeric("numbers.range", &params[0], &args[0].clone())?;
let v2 = ensure_numeric("numbers.range", &params[1], &args[1].clone())?; let v2 = ensure_numeric("numbers.range", &params[1], &args[1].clone())?;
@@ -96,14 +96,14 @@ fn range(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::from_array(values)) Ok(Value::from_array(values))
} }
fn round(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "round", params, args, 1)?; ensure_args_count(span, "round", params, args, 1)?;
Ok(Value::from( Ok(Value::from(
ensure_numeric("round", &params[0], &args[0])?.round(), ensure_numeric("round", &params[0], &args[0])?.round(),
)) ))
} }
fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn intn(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let fcn = "rand.intn"; let fcn = "rand.intn";
ensure_args_count(span, fcn, params, args, 2)?; ensure_args_count(span, fcn, params, args, 2)?;
let _ = ensure_string(fcn, &params[0], &args[0])?; let _ = ensure_string(fcn, &params[0], &args[0])?;

View File

@@ -122,7 +122,7 @@ fn merge_filters(
Ok(filters) Ok(filters)
} }
fn json_filter(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn json_filter(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "json.filter"; let name = "json.filter";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
ensure_object(name, &params[0], args[0].clone())?; ensure_object(name, &params[0], args[0].clone())?;
@@ -136,7 +136,7 @@ fn json_filter(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
Ok(json_filter_impl(&args[0], &filters)) Ok(json_filter_impl(&args[0], &filters))
} }
fn filter(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn filter(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "object.filter"; let name = "object.filter";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let mut obj = ensure_object(name, &params[0], args[0].clone())?; let mut obj = ensure_object(name, &params[0], args[0].clone())?;
@@ -154,7 +154,7 @@ fn filter(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::Object(obj)) Ok(Value::Object(obj))
} }
fn get(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn get(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "object.get"; let name = "object.get";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
let obj = ensure_object(name, &params[0], args[0].clone())?; let obj = ensure_object(name, &params[0], args[0].clone())?;
@@ -179,14 +179,14 @@ fn get(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
}) })
} }
fn keys(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn keys(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "object.keys"; let name = "object.keys";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let obj = ensure_object(name, &params[0], args[0].clone())?; let obj = ensure_object(name, &params[0], args[0].clone())?;
Ok(Value::from_set(obj.keys().cloned().collect())) Ok(Value::from_set(obj.keys().cloned().collect()))
} }
fn remove(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn remove(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "object.remove"; let name = "object.remove";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let mut obj = ensure_object(name, &params[0], args[0].clone())?; let mut obj = ensure_object(name, &params[0], args[0].clone())?;
@@ -225,7 +225,7 @@ fn is_subset(sup: &Value, sub: &Value) -> bool {
} }
} }
fn subset(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn subset(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "object.subset"; let name = "object.subset";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;

View File

@@ -26,7 +26,12 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("regex.template_match", (regex_template_match, 4)); m.insert("regex.template_match", (regex_template_match, 4));
} }
fn find_all_string_submatch_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn find_all_string_submatch_n(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.find_all_string_submatch_n"; let name = "regex.find_all_string_submatch_n";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
@@ -68,7 +73,7 @@ fn find_all_string_submatch_n(span: &Span, params: &[Ref<Expr>], args: &[Value])
)) ))
} }
fn find_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn find_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.find_n"; let name = "regex.find_n";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
@@ -98,14 +103,19 @@ fn find_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
)) ))
} }
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.is_valid"; let name = "regex.is_valid";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
Ok(ensure_string(name, &params[0], &args[0]) Ok(ensure_string(name, &params[0], &args[0])
.map_or(Value::Bool(false), |p| Value::Bool(Regex::new(&p).is_ok()))) .map_or(Value::Bool(false), |p| Value::Bool(Regex::new(&p).is_ok())))
} }
pub fn regex_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { pub fn regex_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.match"; let name = "regex.match";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let pattern = ensure_string(name, &params[0], &args[0])?; let pattern = ensure_string(name, &params[0], &args[0])?;
@@ -116,7 +126,12 @@ pub fn regex_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<
Ok(Value::Bool(pattern.is_match(&value))) Ok(Value::Bool(pattern.is_match(&value)))
} }
fn regex_replace(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn regex_replace(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.replace"; let name = "regex.replace";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
@@ -135,7 +150,7 @@ fn regex_replace(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Va
)) ))
} }
fn regex_split(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn regex_split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "regex.split"; let name = "regex.split";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let pattern = ensure_string(name, &params[0], &args[0])?; let pattern = ensure_string(name, &params[0], &args[0])?;
@@ -151,7 +166,12 @@ fn regex_split(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
)) ))
} }
fn regex_template_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn regex_template_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "regex.template_match"; let name = "regex.template_match";
ensure_args_count(span, name, params, args, 4)?; ensure_args_count(span, name, params, args, 4)?;
let template = ensure_string(name, &params[0], &args[0])?; let template = ensure_string(name, &params[0], &args[0])?;

View File

@@ -19,7 +19,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("semver.is_valid", (is_valid, 1)); m.insert("semver.is_valid", (is_valid, 1));
} }
fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "semver.compare"; let name = "semver.compare";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -35,7 +35,7 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::from(result as i64)) Ok(Value::from(result as i64))
} }
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "semver.is_valid"; let name = "semver.is_valid";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let v = ensure_string(name, &params[0], &args[0])?; let v = ensure_string(name, &params[0], &args[0])?;

View File

@@ -34,7 +34,12 @@ pub fn difference(expr1: &Expr, expr2: &Expr, v1: Value, v2: Value) -> Result<Va
Ok(Value::from_set(s1.difference(&s2).cloned().collect())) Ok(Value::from_set(s1.difference(&s2).cloned().collect()))
} }
fn intersection_of_set_of_sets(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn intersection_of_set_of_sets(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "intersection"; let name = "intersection";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let set = ensure_set(name, &params[0], args[0].clone())?; let set = ensure_set(name, &params[0], args[0].clone())?;
@@ -61,7 +66,12 @@ fn intersection_of_set_of_sets(span: &Span, params: &[Ref<Expr>], args: &[Value]
Ok(Value::from_set(res)) Ok(Value::from_set(res))
} }
fn union_of_set_of_sets(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn union_of_set_of_sets(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "union"; let name = "union";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let set = ensure_set(name, &params[0], args[0].clone())?; let set = ensure_set(name, &params[0], args[0].clone())?;

View File

@@ -42,7 +42,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("upper", (upper, 1)); m.insert("upper", (upper, 1));
} }
fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "concat"; let name = "concat";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let delimiter = ensure_string(name, &params[0], &args[0])?; let delimiter = ensure_string(name, &params[0], &args[0])?;
@@ -50,7 +50,7 @@ fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::String(collection.join(&delimiter).into())) Ok(Value::String(collection.join(&delimiter).into()))
} }
fn contains(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn contains(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "contains"; let name = "contains";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -58,7 +58,7 @@ fn contains(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
Ok(Value::Bool(s1.contains(s2.as_ref()))) Ok(Value::Bool(s1.contains(s2.as_ref())))
} }
fn endswith(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn endswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "endswith"; let name = "endswith";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -76,7 +76,7 @@ fn format_number(n: &Number, base: u64) -> String {
} }
} }
fn format_int(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn format_int(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "format_int"; let name = "format_int";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let mut n = ensure_numeric(name, &params[0], &args[0])?; let mut n = ensure_numeric(name, &params[0], &args[0])?;
@@ -97,7 +97,7 @@ fn format_int(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value
)) ))
} }
fn indexof(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn indexof(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "indexof"; let name = "indexof";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -109,7 +109,7 @@ fn indexof(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
} }
#[allow(dead_code)] #[allow(dead_code)]
fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "indexof_n"; let name = "indexof_n";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -128,14 +128,14 @@ fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
Ok(Value::from_array(positions)) Ok(Value::from_array(positions))
} }
fn lower(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn lower(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "lower"; let name = "lower";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
Ok(Value::String(s.to_lowercase().into())) Ok(Value::String(s.to_lowercase().into()))
} }
fn replace(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn replace(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "replace"; let name = "replace";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
@@ -144,7 +144,7 @@ fn replace(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::String(s.replace(old.as_ref(), new.as_ref()).into())) Ok(Value::String(s.replace(old.as_ref(), new.as_ref()).into()))
} }
fn split(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "replace"; let name = "replace";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
@@ -157,7 +157,7 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
)) ))
} }
fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "sprintf"; let name = "sprintf";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let fmt = ensure_string(name, &params[0], &args[0])?; let fmt = ensure_string(name, &params[0], &args[0])?;
@@ -345,7 +345,12 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::String(s.into())) Ok(Value::String(s.into()))
} }
fn any_prefix_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn any_prefix_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "strings.any_prefix_match"; let name = "strings.any_prefix_match";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -376,7 +381,12 @@ fn any_prefix_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result
)) ))
} }
fn any_suffix_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn any_suffix_match(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
let name = "strings.any_suffix_match"; let name = "strings.any_suffix_match";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
@@ -407,7 +417,7 @@ fn any_suffix_match(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result
)) ))
} }
fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "startswith"; let name = "startswith";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -415,7 +425,7 @@ fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value
Ok(Value::Bool(s1.starts_with(s2.as_ref()))) Ok(Value::Bool(s1.starts_with(s2.as_ref())))
} }
fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim"; let name = "trim";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let obj = ensure_object(name, &params[0], args[0].clone())?; let obj = ensure_object(name, &params[0], args[0].clone())?;
@@ -438,14 +448,14 @@ fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
Ok(Value::String(s.clone())) Ok(Value::String(s.clone()))
} }
fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "reverse"; let name = "reverse";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
Ok(Value::String(s.chars().rev().collect::<String>().into())) Ok(Value::String(s.chars().rev().collect::<String>().into()))
} }
fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "substring"; let name = "substring";
ensure_args_count(span, name, params, args, 3)?; ensure_args_count(span, name, params, args, 3)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
@@ -470,7 +480,7 @@ fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
} }
} }
fn trim(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim"; let name = "trim";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -478,7 +488,7 @@ fn trim(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> {
Ok(Value::String(s1.trim_matches(|c| s2.contains(c)).into())) Ok(Value::String(s1.trim_matches(|c| s2.contains(c)).into()))
} }
fn trim_left(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim_left(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim_left"; let name = "trim_left";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -488,7 +498,7 @@ fn trim_left(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value>
)) ))
} }
fn trim_prefix(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim_prefix(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim_prefix"; let name = "trim_prefix";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -499,7 +509,7 @@ fn trim_prefix(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
})) }))
} }
fn trim_right(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim_right(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim_right"; let name = "trim_right";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -509,14 +519,14 @@ fn trim_right(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value
)) ))
} }
fn trim_space(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim_space(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim_space"; let name = "trim_space";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;
Ok(Value::String(s.trim().into())) Ok(Value::String(s.trim().into()))
} }
fn trim_suffix(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trim_suffix(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trim_suffix"; let name = "trim_suffix";
ensure_args_count(span, name, params, args, 2)?; ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_string(name, &params[0], &args[0])?; let s1 = ensure_string(name, &params[0], &args[0])?;
@@ -527,7 +537,7 @@ fn trim_suffix(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
})) }))
} }
fn upper(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn upper(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "upper"; let name = "upper";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?; let s = ensure_string(name, &params[0], &args[0])?;

View File

@@ -16,7 +16,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("time.now_ns", (now_ns, 0)); m.insert("time.now_ns", (now_ns, 0));
} }
fn now_ns(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn now_ns(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "time.now_ns"; let name = "time.now_ns";
ensure_args_count(span, name, params, args, 0)?; ensure_args_count(span, name, params, args, 0)?;

View File

@@ -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 // Symbol analyzer must ensure that vars used by trace are defined before
// the trace statement. Scheduler must ensure the above constraint. // the trace statement. Scheduler must ensure the above constraint.
fn trace(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn trace(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "trace"; let name = "trace";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let msg = ensure_string(name, &params[0], &args[0])?; let msg = ensure_string(name, &params[0], &args[0])?;

View File

@@ -22,37 +22,37 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("type_name", (type_name, 1)); m.insert("type_name", (type_name, 1));
} }
fn is_array(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_array(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_array", params, args, 1)?; ensure_args_count(span, "is_array", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Array(_)))) Ok(Value::Bool(matches!(&args[0], Value::Array(_))))
} }
fn is_boolean(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_boolean(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_boolean", params, args, 1)?; ensure_args_count(span, "is_boolean", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Bool(_)))) Ok(Value::Bool(matches!(&args[0], Value::Bool(_))))
} }
fn is_null(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_null(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_null", params, args, 1)?; ensure_args_count(span, "is_null", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Null))) Ok(Value::Bool(matches!(&args[0], Value::Null)))
} }
fn is_number(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_number(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_number", params, args, 1)?; ensure_args_count(span, "is_number", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Number(_)))) Ok(Value::Bool(matches!(&args[0], Value::Number(_))))
} }
fn is_object(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_object(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_object", params, args, 1)?; ensure_args_count(span, "is_object", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Object(_)))) Ok(Value::Bool(matches!(&args[0], Value::Object(_))))
} }
fn is_set(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_set(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_set", params, args, 1)?; ensure_args_count(span, "is_set", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::Set(_)))) Ok(Value::Bool(matches!(&args[0], Value::Set(_))))
} }
fn is_string(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn is_string(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
ensure_args_count(span, "is_string", params, args, 1)?; ensure_args_count(span, "is_string", params, args, 1)?;
Ok(Value::Bool(matches!(&args[0], Value::String(_)))) 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<Expr>], args: &[Value]) -> Result<Value> { pub fn type_name(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
_strict: bool,
) -> Result<Value> {
ensure_args_count(span, "type_name", params, args, 1)?; ensure_args_count(span, "type_name", params, args, 1)?;
Ok(Value::String(get_type(&args[0]).into())) Ok(Value::String(get_type(&args[0]).into()))
} }

View File

@@ -66,7 +66,7 @@ fn two_exp(suffix: &str) -> Option<i32> {
}) })
} }
fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "units.parse"; let name = "units.parse";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let string = ensure_string(name, &params[0], &args[0])?; let string = ensure_string(name, &params[0], &args[0])?;
@@ -143,7 +143,7 @@ fn tenb_exp(suffix: &str) -> Option<i32> {
}) })
} }
fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Value> { fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "units.parse_bytes"; let name = "units.parse_bytes";
ensure_args_count(span, name, params, args, 1)?; ensure_args_count(span, name, params, args, 1)?;
let string = ensure_string(name, &params[0], &args[0])?; let string = ensure_string(name, &params[0], &args[0])?;
@@ -166,12 +166,15 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Valu
_ => (string, ""), _ => (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()) serde_json::from_str(format!("0{number_part}").as_str())
} else { } else {
serde_json::from_str(number_part) 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 { let mut n = match v {
Value::Number(n) => n.clone(), Value::Number(n) => n.clone(),

View File

@@ -71,6 +71,10 @@ impl Engine {
&self.modules &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<()> { fn prepare_for_eval(&mut self, enable_tracing: bool) -> Result<()> {
self.interpreter.set_traces(enable_tracing); self.interpreter.set_traces(enable_tracing);

View File

@@ -44,6 +44,7 @@ pub struct Interpreter {
no_rules_lookup: bool, no_rules_lookup: bool,
traces: Option<Vec<std::rc::Rc<str>>>, traces: Option<Vec<std::rc::Rc<str>>>,
allow_deprecated: bool, allow_deprecated: bool,
strict_builtin_errors: bool,
} }
impl Default for Interpreter { impl Default for Interpreter {
@@ -130,6 +131,7 @@ impl Interpreter {
no_rules_lookup: false, no_rules_lookup: false,
traces: None, traces: None,
allow_deprecated: true, 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) { pub fn set_input(&mut self, input: Value) {
self.input = input; self.input = input;
info!("input: {:#?}", self.input); info!("input: {:#?}", self.input);
} }
pub fn init_with_document(&mut self) -> Result<()> { 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, &["data"])? = self.init_data.clone();
*Self::make_or_get_value_mut(&mut self.with_document, &["input"])? = Value::new_object(); *Self::make_or_get_value_mut(&mut self.with_document, &["input"])? = self.input.clone();
Ok(()) Ok(())
} }
@@ -789,6 +795,7 @@ impl Interpreter {
// OPA raises the error sometimes in static scenarios, but doesn't // OPA raises the error sometimes in static scenarios, but doesn't
// raise in scenarios due to data/input // raise in scenarios due to data/input
(Expr::Array { .. }, _) | (Expr::Object { .. }, _) => Ok(false), (Expr::Array { .. }, _) | (Expr::Object { .. }, _) => Ok(false),
_ => { _ => {
let expr_value = self.lookup_or_eval_expr(cache, expr)?; let expr_value = self.lookup_or_eval_expr(cache, expr)?;
if expr_value == Value::Undefined { if expr_value == Value::Undefined {
@@ -1096,18 +1103,28 @@ impl Interpreter {
}; };
if path[0] == "input" || path[0] == "data" { 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; *Self::make_or_get_value_mut(&mut self.with_document, &path[..])? = value;
} /* else if path.len() == 1 { }
// TODO: handle var in current module.
} else { /* else if path.len() == 1 {
// TODO: error about input, data // TODO: handle var in current module.
} */ } else {
// TODO: error about input, data
} */
} }
self.data = self.with_document["data"].clone(); self.data = self.with_document["data"].clone();
self.input = self.with_document["input"].clone(); self.input = self.with_document["input"].clone();
self.processed.clear(); self.processed.clear();
(with_document, input, data, processed, with_functions) (with_document, input, data, processed, with_functions)
} else { } 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. // Handle trace function.
// TODO: with modifier. // TODO: with modifier.
@@ -1694,18 +1711,29 @@ impl Interpreter {
_ => orig_fcn_path.clone(), _ => orig_fcn_path.clone(),
}; };
let empty = vec![];
let fcns_rules = match self.lookup_function_by_name(&fcn_path) { let fcns_rules = match self.lookup_function_by_name(&fcn_path) {
Some(r) => r, 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. // 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); let r = self.eval_builtin_call(span, &fcn_path.clone(), *builtin, params);
if orig_fcn_path != fcn_path { if orig_fcn_path != fcn_path {
self.with_functions.insert(orig_fcn_path, fcn_path); self.with_functions.insert(orig_fcn_path, fcn_path);
} }
return r; 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(); let mut type_match = BTreeSet::new();
for (idx, a) in args.iter().enumerate() { for (idx, a) in args.iter().enumerate() {
if self let b =
.make_bindings(false, &mut type_match, &mut cache, a, &param_values[idx]) self.make_bindings(false, &mut type_match, &mut cache, a, &param_values[idx]);
.is_err()
{ if b.ok() != Some(true) {
self.scopes = scopes; self.scopes = scopes;
continue 'outer; continue 'outer;
} }
@@ -1799,7 +1827,9 @@ impl Interpreter {
// If the function execution resulted in undefined, then propagate it. // If the function execution resulted in undefined, then propagate it.
Value::Undefined => Value::Undefined, 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. // 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 results.is_empty() {
if errors.is_empty() { if errors.is_empty() {
return Ok(Value::Undefined); return Ok(Value::Undefined);
@@ -1937,15 +1995,28 @@ impl Interpreter {
return Ok(v); return Ok(v);
} }
// Evaluate rule corresponding to longest matching path. if fields.is_empty() {
for i in (1..fields.len() + 1).rev() { 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("."); let path = "data.".to_owned() + &fields[0..i].join(".");
if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() { if self.rules.get(&path).is_some() || self.default_rules.get(&path).is_some() {
self.ensure_rule_evaluated(path)?; self.ensure_rule_evaluated(path)?;
found = true;
break; 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)) Ok(Self::get_value_chained(self.data.clone(), fields))
} else if !self.modules.is_empty() { } else if !self.modules.is_empty() {
let path = Parser::get_path_ref_components(&self.module.clone().unwrap().package.refr)?; let path = Parser::get_path_ref_components(&self.module.clone().unwrap().package.refr)?;
@@ -1960,10 +2031,8 @@ impl Interpreter {
return Ok(v); return Ok(v);
} }
// Add module prefix and ensure that any matching rule is evaluated. // Ensure that all the rules having common prefix (name) are evaluated.
let module_path = let rule_path = "data.".to_owned() + &path.join(".");
Self::get_path_string(&self.current_module()?.package.refr, Some("data"))?;
let rule_path = module_path + "." + name.text();
if !no_error if !no_error
&& self.rules.get(&rule_path).is_none() && self.rules.get(&rule_path).is_none()
@@ -1971,6 +2040,7 @@ impl Interpreter {
{ {
bail!(span.error("var is unsafe")); bail!(span.error("var is unsafe"));
} }
self.ensure_rule_evaluated(rule_path)?; self.ensure_rule_evaluated(rule_path)?;
let value = Self::get_value_chained(self.data.clone(), &path[..]); let value = Self::get_value_chained(self.data.clone(), &path[..]);
@@ -2318,9 +2388,18 @@ impl Interpreter {
} }
if let Rule::Default { if let Rule::Default {
span, refr, value, .. span,
refr,
value,
args,
..
} = rule.as_ref() } = 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 scopes = std::mem::take(&mut self.scopes);
let mut path = let mut path =
@@ -2588,8 +2667,10 @@ impl Interpreter {
Expr::RefBrack { refr, .. } => refr, Expr::RefBrack { refr, .. } => refr,
_ => refr, _ => refr,
}; };
let path = Self::get_path_string(refr, None)?; //let path = Self::get_path_string(refr, None)?;
let path = self.current_module_path.clone() + "." + &path; let path = get_root_var(refr)?;
let path = path.text();
let path = self.current_module_path.clone() + "." + path;
match self.rules.entry(path) { match self.rules.entry(path) {
Entry::Occupied(o) => { Entry::Occupied(o) => {
o.into_mut().push(rule.clone()); o.into_mut().push(rule.clone());
@@ -2617,8 +2698,10 @@ impl Interpreter {
_ => (refr, None), _ => (refr, None),
}; };
let path = Self::get_path_string(refr, None)?; //let path = Self::get_path_string(refr, None)?;
let path = self.current_module_path.clone() + "." + &path; let path = get_root_var(refr)?;
let path = path.text();
let path = self.current_module_path.clone() + "." + path;
match self.default_rules.entry(path) { match self.default_rules.entry(path) {
Entry::Occupied(o) => { Entry::Occupied(o) => {
for (_, i) in o.get() { for (_, i) in o.get() {

View File

@@ -209,7 +209,9 @@ impl<'source> Parser<'source> {
match self.tok.0 { match self.tok.0 {
TokenKind::Ident TokenKind::Ident
if self.is_keyword(*span.text()) 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( Err(self.source.error(
self.tok.1.line, self.tok.1.line,
@@ -1425,6 +1427,25 @@ impl<'source> Parser<'source> {
self.expect("default", "while parsing default rule")?; self.expect("default", "while parsing default rule")?;
let rule_ref = Ref::new(self.parse_rule_ref()?); 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() { let op = match *self.token_text() {
"=" => AssignOp::Eq, "=" => AssignOp::Eq,
":=" => AssignOp::ColEq, ":=" => AssignOp::ColEq,
@@ -1443,6 +1464,7 @@ impl<'source> Parser<'source> {
Ok(Rule::Default { Ok(Rule::Default {
span, span,
refr: rule_ref, refr: rule_ref,
args: args.into_iter().map(|a| Ref::new(Expr::Var(a))).collect(),
op, op,
value, value,
}) })

View File

@@ -205,7 +205,8 @@ pub fn schedule<Str: Clone + std::cmp::Ord + std::fmt::Debug>(
#[derive(Clone, Default, Debug)] #[derive(Clone, Default, Debug)]
pub struct Scope { pub struct Scope {
pub locals: BTreeSet<SourceStr>, pub locals: BTreeMap<SourceStr, Span>,
pub unscoped: BTreeSet<SourceStr>,
pub inputs: BTreeSet<SourceStr>, pub inputs: BTreeSet<SourceStr>,
} }
@@ -269,8 +270,24 @@ fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>) ->
Ok(()) Ok(())
} }
fn var_exists(name: &SourceStr, parent_scopes: &[Scope]) -> bool { fn var_exists(var: &Span, parent_scopes: &[Scope]) -> bool {
parent_scopes.iter().rev().any(|s| s.locals.contains(name)) 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( fn gather_assigned_vars(
@@ -285,19 +302,19 @@ fn gather_assigned_vars(
// Record local var that can shadow input var. // Record local var that can shadow input var.
Var(v) if can_shadow => { Var(v) if can_shadow => {
scope.locals.insert(v.source_str()); scope.locals.insert(v.source_str(), v.clone());
Ok(false) Ok(false)
} }
// Record input vars. // 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()); scope.inputs.insert(v.source_str());
Ok(false) Ok(false)
} }
// Record local var. // Record local var.
Var(v) => { Var(v) => {
scope.locals.insert(v.source_str()); scope.unscoped.insert(v.source_str());
Ok(false) Ok(false)
} }
@@ -309,11 +326,8 @@ fn gather_assigned_vars(
fn gather_input_vars(expr: &Ref<Expr>, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> { fn gather_input_vars(expr: &Ref<Expr>, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> {
traverse(expr, &mut |e| match e.as_ref() { traverse(expr, &mut |e| match e.as_ref() {
Var(v) if var_exists(&v.source_str(), parent_scopes) => { Var(v) if !scope.unscoped.contains(&v.source_str()) && var_exists(v, parent_scopes) => {
let var = v.source_str(); scope.inputs.insert(v.source_str());
if !scope.locals.contains(&var) {
scope.inputs.insert(var);
}
Ok(false) Ok(false)
} }
_ => Ok(true), _ => Ok(true),
@@ -322,14 +336,12 @@ fn gather_input_vars(expr: &Ref<Expr>, parent_scopes: &[Scope], scope: &mut Scop
fn gather_loop_vars(expr: &Ref<Expr>, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> { fn gather_loop_vars(expr: &Ref<Expr>, parent_scopes: &[Scope], scope: &mut Scope) -> Result<()> {
traverse(expr, &mut |e| match e.as_ref() { 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, .. } => { RefBrack { index, .. } => {
if let Var(v) = index.as_ref() { if let Var(v) = index.as_ref() {
if !matches!(*v.text(), "_" | "input" | "data") if !matches!(*v.text(), "_" | "input" | "data") && !var_exists(v, parent_scopes) {
&& !var_exists(&v.source_str(), parent_scopes)
{
// Treat this as an index var. // Treat this as an index var.
scope.locals.insert(v.source_str()); scope.unscoped.insert(v.source_str());
} }
} }
Ok(true) Ok(true)
@@ -367,7 +379,7 @@ fn gather_vars(
pub struct Analyzer { pub struct Analyzer {
packages: BTreeMap<String, Scope>, packages: BTreeMap<String, Scope>,
locals: BTreeMap<Ref<Query>, Scope>, scope_table: BTreeMap<Ref<Query>, Scope>,
scopes: Vec<Scope>, scopes: Vec<Scope>,
order: BTreeMap<Ref<Query>, Vec<u16>>, order: BTreeMap<Ref<Query>, Vec<u16>>,
functions: FunctionTable, functions: FunctionTable,
@@ -390,7 +402,7 @@ impl Analyzer {
pub fn new() -> Analyzer { pub fn new() -> Analyzer {
Analyzer { Analyzer {
packages: BTreeMap::new(), packages: BTreeMap::new(),
locals: BTreeMap::new(), scope_table: BTreeMap::new(),
scopes: vec![], scopes: vec![],
order: BTreeMap::new(), order: BTreeMap::new(),
functions: FunctionTable::new(), functions: FunctionTable::new(),
@@ -407,7 +419,7 @@ impl Analyzer {
} }
Ok(Schedule { Ok(Schedule {
scopes: self.locals, scopes: self.scope_table,
order: self.order, order: self.order,
}) })
} }
@@ -421,7 +433,7 @@ impl Analyzer {
self.analyze_query(None, None, query, Scope::default())?; self.analyze_query(None, None, query, Scope::default())?;
Ok(Schedule { Ok(Schedule {
scopes: self.locals, scopes: self.scope_table,
order: self.order, order: self.order,
}) })
} }
@@ -441,7 +453,7 @@ impl Analyzer {
.. ..
} => get_root_var(refr)?, } => 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::Set { key, .. } => (key.clone(), None, scope),
RuleHead::Func { args, assign, .. } => { RuleHead::Func { args, assign, .. } => {
for a in args.iter() { for a in args.iter() {
if let Var(v) = a.as_ref() { traverse(a, &mut |e| {
scope.locals.insert(v.source_str()); 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) (None, assign.as_ref().map(|a| a.value.clone()), scope)
} }
@@ -549,7 +564,7 @@ impl Analyzer {
for stmt in &query.stmts { for stmt in &query.stmts {
match &stmt.literal { match &stmt.literal {
Literal::SomeVars { vars, .. } => vars.iter().for_each(|v| { Literal::SomeVars { vars, .. } => vars.iter().for_each(|v| {
scope.locals.insert(v.source_str()); scope.locals.insert(v.source_str(), v.clone());
}), }),
Literal::SomeIn { Literal::SomeIn {
key, key,
@@ -597,8 +612,9 @@ impl Analyzer {
} }
// Remove input vars that are shadowed. // Remove input vars that are shadowed.
for v in &scope.locals { for v in scope.locals.keys() {
scope.inputs.remove(v); scope.inputs.remove(v);
scope.unscoped.remove(v);
} }
Ok(()) Ok(())
@@ -621,7 +637,7 @@ impl Analyzer {
_ => false, _ => false,
}; };
if scope.locals.contains(&name) if scope.locals.contains_key(&name) || scope.unscoped.contains(&name)
/*|| scope.inputs.contains(name) */ /*|| scope.inputs.contains(name) */
{ {
if !is_extra_arg { if !is_extra_arg {
@@ -637,7 +653,7 @@ impl Analyzer {
RefBrack { refr, index, .. } => { RefBrack { refr, index, .. } => {
if let Var(v) = index.as_ref() { if let Var(v) = index.as_ref() {
let var = v.source_str(); 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( let (rb_used_vars, rb_comprs) = Self::gather_used_vars_comprs_index_vars(
refr, refr,
scope, scope,
@@ -681,7 +697,7 @@ impl Analyzer {
let compr_scope = match compr.as_ref() { let compr_scope = match compr.as_ref() {
Expr::ArrayCompr { query, term, .. } | Expr::SetCompr { query, term, .. } => { Expr::ArrayCompr { query, term, .. } | Expr::SetCompr { query, term, .. } => {
self.analyze_query(None, Some(term.clone()), query, Scope::default())?; self.analyze_query(None, Some(term.clone()), query, Scope::default())?;
self.locals.get(query) self.scope_table.get(query)
} }
Expr::ObjectCompr { Expr::ObjectCompr {
query, key, value, .. query, key, value, ..
@@ -692,7 +708,7 @@ impl Analyzer {
query, query,
Scope::default(), Scope::default(),
)?; )?;
self.locals.get(query) self.scope_table.get(query)
} }
_ => break, _ => break,
}; };
@@ -700,7 +716,7 @@ impl Analyzer {
// Record vars used by the comprehension scope. // Record vars used by the comprehension scope.
if let Some(compr_scope) = compr_scope { if let Some(compr_scope) = compr_scope {
for iv in &compr_scope.inputs { 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. // Record possible first use of current scope's local var.
first_use.entry(iv.clone()).or_insert(compr.span().clone()); first_use.entry(iv.clone()).or_insert(compr.span().clone());
used_vars.push(iv.clone()); used_vars.push(iv.clone());
@@ -727,11 +743,13 @@ impl Analyzer {
traverse(expr, &mut |e| match e.as_ref() { traverse(expr, &mut |e| match e.as_ref() {
Var(v) => { Var(v) => {
let var = v.source_str(); let var = v.source_str();
if scope.locals.contains(&var) { if scope.locals.contains_key(&var) {
if check_first_use { if check_first_use {
Self::check_first_use(v, first_use)?; Self::check_first_use(v, first_use)?;
} }
vars.push(var); vars.push(var);
} else if scope.unscoped.contains(&var) {
vars.push(var);
} }
Ok(false) Ok(false)
} }
@@ -890,7 +908,7 @@ impl Analyzer {
non_vars: &mut Vec<Ref<Expr>>, non_vars: &mut Vec<Ref<Expr>>,
) -> Result<()> { ) -> Result<()> {
traverse(expr, &mut |e| match e.as_ref() { 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()); vars.push(v.source_str());
Ok(false) Ok(false)
} }
@@ -1012,8 +1030,8 @@ impl Analyzer {
let mut extras_scope = Scope::default(); let mut extras_scope = Scope::default();
gather_assigned_vars(ea, false, &self.scopes, &mut extras_scope)?; gather_assigned_vars(ea, false, &self.scopes, &mut extras_scope)?;
for var in &extras_scope.locals { for var in &extras_scope.unscoped {
scope.locals.insert(var.clone()); scope.unscoped.insert(var.clone());
} }
// Gather vars being used. // Gather vars being used.
@@ -1022,7 +1040,7 @@ impl Analyzer {
&mut scope, &mut scope,
&mut first_use, &mut first_use,
&mut definitions, &mut definitions,
&Some(&extras_scope.locals), &Some(&extras_scope.unscoped),
)?; )?;
self.process_comprs( self.process_comprs(
@@ -1032,8 +1050,8 @@ impl Analyzer {
&mut used_vars, &mut used_vars,
)?; )?;
if !extras_scope.locals.is_empty() { if !extras_scope.unscoped.is_empty() {
for var in extras_scope.locals { for var in extras_scope.unscoped {
definitions.push(Definition { definitions.push(Definition {
var, var,
used_vars: used_vars.clone(), used_vars: used_vars.clone(),
@@ -1076,9 +1094,9 @@ impl Analyzer {
self.scopes.push(scope.clone()); self.scopes.push(scope.clone());
let mut e_scope = Scope::default(); let mut e_scope = Scope::default();
if let Some(key) = key { 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); self.scopes.push(e_scope);
// TODO: mark first use of key, value so that they cannot be := assigned // 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(()) Ok(())
} }

View File

@@ -10,33 +10,50 @@ bitsor
bitsshiftleft bitsshiftleft
bitsshiftright bitsshiftright
bitsxor bitsxor
casts
comparisonexpr comparisonexpr
completedoc completedoc
compositebasedereference compositebasedereference
dataderef comprehensions
containskeyword
cryptohmacequal cryptohmacequal
cryptohmacmd5 cryptohmacmd5
cryptohmacsha1 cryptohmacsha1
cryptohmacsha256 cryptohmacsha256
cryptohmacsha512 cryptohmacsha512
cryptomd5 cryptomd5
cryptsha1 cryptosha1
cryptosha256 cryptosha256
dataderef
disjunction
elsekeyword
embeddedvirtualdoc embeddedvirtualdoc
evaltermexpr evaltermexpr
every
example example
fix1863 fix1863
functionerrors
functions
globmatch globmatch
globquotemeta
helloworld helloworld
indexing indexing
indirectreferences
intersection intersection
invalidkeyerror invalidkeyerror
jsonfilteridempotent jsonfilteridempotent
jwtencodesignheadererrors
jwtencodesignpayloaderrors
nestedreferences nestedreferences
objectfilter
objectfilteridempotent objectfilteridempotent
objectfilternonstringkey objectfilternonstringkey
objectget
objectkeys
objectremove
objectremoveidempotent objectremoveidempotent
objectremovenonstringkey objectremovenonstringkey
partialdocconstants
partialsetdoc partialsetdoc
rand rand
regexfind regexfind
@@ -57,6 +74,7 @@ trimprefix
trimright trimright
trimspace trimspace
trimsuffix trimsuffix
type
typebuiltin typebuiltin
typenamebuiltin typenamebuiltin
undos undos

View File

@@ -16,18 +16,33 @@ const OPA_REPO: &str = "https://github.com/open-policy-agent/opa";
const OPA_BRANCH: &str = "v0.58.0"; const OPA_BRANCH: &str = "v0.58.0";
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(deny_unknown_fields)]
struct TestCase { struct TestCase {
#[serde(skip_serializing_if = "Option::is_none")]
data: Option<Value>, data: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
input: Option<Value>, input: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
input_term: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
modules: Option<Vec<String>>, modules: Option<Vec<String>>,
note: String, note: String,
query: String, query: String,
#[serde(skip_serializing_if = "Option::is_none")]
sort_bindings: Option<bool>, sort_bindings: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
want_result: Option<Value>, want_result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
skip: Option<bool>, skip: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>, error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
traces: Option<bool>, traces: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
strict_error: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
want_error: Option<String>, want_error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
want_error_code: Option<String>, want_error_code: Option<String>,
} }
@@ -45,11 +60,18 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
if let Some(input) = &case.input { if let Some(input) = &case.input {
engine.set_input(input.clone()); 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 { if let Some(modules) = &case.modules {
for (idx, rego) in modules.iter().enumerate() { for (idx, rego) in modules.iter().enumerate() {
engine.add_policy(format!("rego{idx}.rego"), rego.clone())?; 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 query_results = engine.eval_query(case.query.clone(), true)?;
let mut values = vec![]; let mut values = vec![];
@@ -182,9 +204,11 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
println!("\nOPA TESTSUITE STATUS"); println!("\nOPA TESTSUITE STATUS");
println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL"); println!(" {:40} {:4} {:4}", "FOLDER", "PASS", "FAIL");
let (mut npass, mut nfail) = (0, 0); let (mut npass, mut nfail) = (0, 0);
let mut passing = vec![];
for (dir, (pass, fail)) in status { for (dir, (pass, fail)) in status {
if fail == 0 { if fail == 0 {
println!("\x1b[32m {dir:40}: {pass:4} {fail:4}\x1b[0m"); println!("\x1b[32m {dir:40}: {pass:4} {fail:4}\x1b[0m");
passing.push(dir);
} else { } else {
println!("\x1b[31m {dir:40}: {pass:4} {fail:4}\x1b[0m"); 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!(); println!();
std::fs::write("target/opa.passing", passing.join("\n"))?;
if npass == 0 && nfail == 0 { if npass == 0 && nfail == 0 {
bail!("no matching tests found."); bail!("no matching tests found.");
} else if nfail == 0 { } else if nfail == 0 {

View File

@@ -569,12 +569,14 @@ fn match_rule(r: &Rule, v: &Value) -> Result<()> {
Rule::Default { Rule::Default {
span, span,
refr, refr,
args,
op, op,
value, value,
} => { } => {
let obj = &v["default"]; let obj = &v["default"];
match_span_opt(span, &obj["span"])?; match_span_opt(span, &obj["span"])?;
match_expr(refr, &obj["refr"])?; match_expr(refr, &obj["refr"])?;
match_vec(span /*dummy*/, args, &obj["args"])?;
match_assign_op(span, op, &obj["op"])?; match_assign_op(span, op, &obj["op"])?;
match_expr(value, &obj["value"]) match_expr(value, &obj["value"])
} }

View File

@@ -37,9 +37,12 @@ cases:
r = 1 r = 1
rrr = "fun" rrr = "fun"
scopes: scopes:
- locals: ["p", "y", "q", "x", "a", "b", "idx"] - locals: ["x"]
unscoped: ["p", "y", "q", "a", "b", "idx"]
inputs: ["r", "rrr"] inputs: ["r", "rrr"]
- locals: ["k", "r1"] - locals: []
unscoped: ["k", "r1"]
inputs: ["a", "idx", "rrr"] inputs: ["a", "idx", "rrr"]
- locals: ["q", "idx1", "t"] - locals: ["q"]
unscoped: ["idx1", "t"]
inputs: ["rrr"] inputs: ["rrr"]

View File

@@ -12,6 +12,7 @@ use std::collections::BTreeSet;
#[derive(Serialize, Deserialize, PartialEq, Debug)] #[derive(Serialize, Deserialize, PartialEq, Debug)]
struct Scope { struct Scope {
pub locals: BTreeSet<String>, pub locals: BTreeSet<String>,
pub unscoped: BTreeSet<String>,
pub inputs: BTreeSet<String>, pub inputs: BTreeSet<String>,
} }
@@ -27,8 +28,11 @@ struct YamlTest {
cases: Vec<TestCase>, cases: Vec<TestCase>,
} }
fn to_string_set(s: &BTreeSet<SourceStr>) -> BTreeSet<String> { fn to_string_set<'a, I>(itr: I) -> BTreeSet<String>
s.iter().map(|s| s.to_string()).collect() where
I: std::iter::Iterator<Item = &'a SourceStr>,
{
itr.map(|s| s.to_string()).collect()
} }
fn analyze_file(regos: &[String], expected_scopes: &[Scope]) -> Result<()> { 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() { if idx > expected_scopes.len() {
bail!("extra scope generated.") bail!("extra scope generated.")
} }
assert_eq!(to_string_set(&scope.locals), expected_scopes[idx].locals); assert_eq!(
assert_eq!(to_string_set(&scope.inputs), expected_scopes[idx].inputs); 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.") println!("scope {idx} matched.")
} }