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
[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 <COMMAND>
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 <COMMAND>
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

View File

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

View File

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

View File

@@ -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<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)?;
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)?;
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)?;
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)?;
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)?;
Ok(match &args[0] {
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)?;
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));
}
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";
ensure_args_count(span, name, params, args, 2)?;
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))
}
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";
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))
}
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";
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));
}
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";
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";
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";
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";
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";
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";
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));
}
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";
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<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));
}
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";
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";
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()))
}
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";
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()))
}
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";
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()))
}
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";
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()))
}
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";
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()))
}
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";
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()))
}
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";
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
// the print statement. Scheduler must ensure the above constraint.
// 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 {
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();
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<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)?;
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)?;
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";
ensure_args_count(span, name, params, args, 2)?;
let s1 = ensure_set(name, &params[0], args[0].clone())?;
let s2 = ensure_set(name, &params[1], args[1].clone())?;
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";
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")]
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";
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")]
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";
ensure_args_count(span, name, params, args, 1)?;
Ok(Value::String(
@@ -58,14 +68,24 @@ fn yaml_marshal(span: &Span, params: &[Ref<Expr>], args: &[Value]) -> Result<Val
}
#[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";
ensure_args_count(span, name, params, args, 1)?;
let yaml_str = ensure_string(name, &params[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<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";
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()))
}
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";
ensure_args_count(span, name, params, args, 1)?;
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";
ensure_args_count(span, name, params, args, 1)?;
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")))
}
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";
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[..])))
}
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";
ensure_args_count(span, name, params, args, 1)?;

View File

@@ -38,7 +38,7 @@ use std::collections::HashMap;
use anyhow::Result;
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")]
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)?;
Ok(Value::from(
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)?;
Ok(Value::from(
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)?;
Ok(Value::from(
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)?;
let v1 = ensure_numeric("numbers.range", &params[0], &args[0].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))
}
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)?;
Ok(Value::from(
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";
ensure_args_count(span, fcn, params, args, 2)?;
let _ = ensure_string(fcn, &params[0], &args[0])?;

View File

@@ -122,7 +122,7 @@ fn merge_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";
ensure_args_count(span, name, params, args, 2)?;
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))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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))
}
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";
ensure_args_count(span, name, params, args, 3)?;
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";
ensure_args_count(span, name, params, args, 1)?;
let obj = ensure_object(name, &params[0], args[0].clone())?;
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";
ensure_args_count(span, name, params, args, 2)?;
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";
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));
}
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";
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";
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";
ensure_args_count(span, name, params, args, 1)?;
Ok(ensure_string(name, &params[0], &args[0])
.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";
ensure_args_count(span, name, params, args, 2)?;
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)))
}
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";
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 4)?;
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));
}
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";
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))
}
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";
ensure_args_count(span, name, params, args, 1)?;
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()))
}
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";
ensure_args_count(span, name, params, args, 1)?;
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))
}
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";
ensure_args_count(span, name, params, args, 1)?;
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));
}
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";
ensure_args_count(span, name, params, args, 2)?;
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()))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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())))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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)]
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";
ensure_args_count(span, name, params, args, 2)?;
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))
}
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";
ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?;
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";
ensure_args_count(span, name, params, args, 3)?;
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()))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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()))
}
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";
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";
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";
ensure_args_count(span, name, params, args, 2)?;
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())))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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()))
}
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";
ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?;
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";
ensure_args_count(span, name, params, args, 3)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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()))
}
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 1)?;
let s = ensure_string(name, &params[0], &args[0])?;
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";
ensure_args_count(span, name, params, args, 2)?;
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";
ensure_args_count(span, name, params, args, 1)?;
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));
}
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";
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
// 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";
ensure_args_count(span, name, params, args, 1)?;
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));
}
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)?;
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)?;
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)?;
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)?;
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)?;
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)?;
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)?;
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)?;
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";
ensure_args_count(span, name, params, args, 1)?;
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";
ensure_args_count(span, name, params, args, 1)?;
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, ""),
};
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(),

View File

@@ -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);

View File

@@ -44,6 +44,7 @@ pub struct Interpreter {
no_rules_lookup: bool,
traces: Option<Vec<std::rc::Rc<str>>>,
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, &param_values[idx])
.is_err()
{
let b =
self.make_bindings(false, &mut type_match, &mut cache, a, &param_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() {

View File

@@ -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,
})

View File

@@ -205,7 +205,8 @@ pub fn schedule<Str: Clone + std::cmp::Ord + std::fmt::Debug>(
#[derive(Clone, Default, Debug)]
pub struct Scope {
pub locals: BTreeSet<SourceStr>,
pub locals: BTreeMap<SourceStr, Span>,
pub unscoped: BTreeSet<SourceStr>,
pub inputs: BTreeSet<SourceStr>,
}
@@ -269,8 +270,24 @@ fn traverse(expr: &Ref<Expr>, f: &mut dyn FnMut(&Ref<Expr>) -> Result<bool>) ->
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<Expr>, 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<Expr>, parent_scopes: &[Scope], scope: &mut Scop
fn gather_loop_vars(expr: &Ref<Expr>, 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<String, Scope>,
locals: BTreeMap<Ref<Query>, Scope>,
scope_table: BTreeMap<Ref<Query>, Scope>,
scopes: Vec<Scope>,
order: BTreeMap<Ref<Query>, Vec<u16>>,
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<Ref<Expr>>,
) -> 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(())
}

View File

@@ -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

View File

@@ -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<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
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>>,
note: String,
query: String,
#[serde(skip_serializing_if = "Option::is_none")]
sort_bindings: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
want_result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
skip: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
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>,
#[serde(skip_serializing_if = "Option::is_none")]
want_error_code: Option<String>,
}
@@ -45,11 +60,18 @@ fn eval_test_case(case: &TestCase) -> Result<Value> {
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 {

View File

@@ -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"])
}

View File

@@ -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"]

View File

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