mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
More OPA conformance; in-progress: ability to trace interpreter (#63)
Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
73ee18f002
commit
577e1aa8db
@@ -45,6 +45,7 @@ wax = { version = "0.6.0", features = [], default-features = false, optional = t
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
colored-diff = "0.2.3"
|
||||
serde_yaml = "0.9.16"
|
||||
test-generator = "0.3.1"
|
||||
walkdir = "2.3.2"
|
||||
|
||||
@@ -157,6 +157,11 @@ struct Cli {
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
env_logger::builder()
|
||||
.format_level(false)
|
||||
.format_timestamp(None)
|
||||
.init();
|
||||
|
||||
// Parse and dispatch command.
|
||||
let cli = Cli::parse();
|
||||
match cli.command {
|
||||
|
||||
@@ -16,5 +16,5 @@ if [ -f Cargo.toml ]; then
|
||||
fi
|
||||
|
||||
# Ensure that OPA conformance tests don't regress.
|
||||
cargo test --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
cargo test -r --test opa -- $(tr '\n' ' ' < tests/opa.passing)
|
||||
fi
|
||||
|
||||
@@ -73,6 +73,7 @@ fn cast_array(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
match &args[0] {
|
||||
Value::Array(_) => Ok(args[0].clone()),
|
||||
Value::Set(s) => Ok(Value::from_array(s.iter().cloned().collect())),
|
||||
_ if strict => bail!(params[0].span().error("array required")),
|
||||
_ => Ok(Value::Undefined),
|
||||
}
|
||||
@@ -113,6 +114,7 @@ fn cast_set(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) ->
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
match &args[0] {
|
||||
Value::Set(_) => Ok(args[0].clone()),
|
||||
Value::Array(a) => Ok(Value::from_set(a.iter().cloned().collect())),
|
||||
_ if strict => bail!(params[0].span().error("set required")),
|
||||
_ => Ok(Value::Undefined),
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
|
||||
m.insert("ceil", (ceil, 1));
|
||||
m.insert("floor", (floor, 1));
|
||||
m.insert("numbers.range", (range, 2));
|
||||
m.insert("numbers.range_step", (range_step, 3));
|
||||
m.insert("rand.intn", (intn, 2));
|
||||
m.insert("round", (round, 1));
|
||||
}
|
||||
@@ -29,6 +30,7 @@ pub fn arithmetic_operation(
|
||||
expr2: &Expr,
|
||||
v1: Value,
|
||||
v2: Value,
|
||||
strict: bool,
|
||||
) -> Result<Value> {
|
||||
let op_name = format!("{:?}", op).to_lowercase();
|
||||
let v1 = ensure_numeric(op_name.as_str(), expr1, &v1)?;
|
||||
@@ -38,9 +40,11 @@ pub fn arithmetic_operation(
|
||||
ArithOp::Add => v1.add(&v2)?,
|
||||
ArithOp::Sub => v1.sub(&v2)?,
|
||||
ArithOp::Mul => v1.mul(&v2)?,
|
||||
ArithOp::Div if v2 == Number::from(0u64) => bail!(span.error("divide by zero")),
|
||||
ArithOp::Div if strict && v2 == Number::from(0u64) => bail!(span.error("divide by zero")),
|
||||
ArithOp::Div if v2 == Number::from(0u64) => return Ok(Value::Undefined),
|
||||
ArithOp::Div => v1.divide(&v2)?,
|
||||
ArithOp::Mod if v2 == Number::from(0u64) => bail!(span.error("modulo by zero")),
|
||||
ArithOp::Mod if strict && v2 == Number::from(0u64) => bail!(span.error("modulo by zero")),
|
||||
ArithOp::Mod if v2 == Number::from(0u64) => return Ok(Value::Undefined),
|
||||
ArithOp::Mod if !v1.is_integer() || !v2.is_integer() => {
|
||||
bail!(span.error("modulo on floating-point number"))
|
||||
}
|
||||
@@ -69,25 +73,33 @@ fn floor(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
))
|
||||
}
|
||||
|
||||
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", ¶ms[0], &args[0].clone())?;
|
||||
let v2 = ensure_numeric("numbers.range", ¶ms[1], &args[1].clone())?;
|
||||
fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "numbers.range";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
let v1 = ensure_numeric(name, ¶ms[0], &args[0].clone())?;
|
||||
let v2 = ensure_numeric(name, ¶ms[1], &args[1].clone())?;
|
||||
|
||||
let (incr, num_elements) = match (v1.as_i64(), v2.as_i64()) {
|
||||
(Some(v1), Some(v2)) if v2 > v1 => (1, v2 + 1 - v1),
|
||||
(Some(v1), Some(v2)) => (-1, v1 + 1 - v2),
|
||||
_ => {
|
||||
// TODO: OPA returns undefined here.
|
||||
// Can we emit a warning?
|
||||
return Ok(Value::Undefined);
|
||||
}
|
||||
match v1.is_integer() {
|
||||
false if strict => bail!(params[0].span().error("operand must be integer")),
|
||||
false => return Ok(Value::Undefined),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match v2.is_integer() {
|
||||
false if strict => bail!(params[1].span().error("operand must be integer")),
|
||||
false => return Ok(Value::Undefined),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let (incr, num_elements) = match v2.sub(&v1)?.as_i64() {
|
||||
Some(v) if v >= 0 => (1i64, v as usize + 1),
|
||||
Some(v) => (-1i64, (-v + 1) as usize),
|
||||
_ => bail!(span.error("could not determine number of elements")),
|
||||
};
|
||||
|
||||
let mut values = Vec::with_capacity(num_elements as usize);
|
||||
|
||||
let mut values = Vec::with_capacity(num_elements);
|
||||
let mut v = v1;
|
||||
let incr = Number::from(incr as i64);
|
||||
let incr = Number::from(incr);
|
||||
while v != v2 {
|
||||
values.push(Value::from(v.clone()));
|
||||
v.add_assign(&incr)?;
|
||||
@@ -96,10 +108,51 @@ fn range(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
Ok(Value::from_array(values))
|
||||
}
|
||||
|
||||
fn range_step(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
||||
let name = "numbers.range_step";
|
||||
ensure_args_count(span, name, params, args, 3)?;
|
||||
let v1 = ensure_numeric(name, ¶ms[0], &args[0].clone())?;
|
||||
let v2 = ensure_numeric(name, ¶ms[1], &args[1].clone())?;
|
||||
let incr = ensure_numeric(name, ¶ms[2], &args[2].clone())?;
|
||||
|
||||
match v1.is_integer() {
|
||||
false if strict => bail!(params[0].span().error("operand must be integer")),
|
||||
false => return Ok(Value::Undefined),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
match v2.is_integer() {
|
||||
false if strict => bail!(params[1].span().error("operand must be integer")),
|
||||
false => return Ok(Value::Undefined),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
if strict && (!incr.is_integer() || incr <= Number::from(0u64)) {
|
||||
bail!(params[2].span().error("step must be a positive integer"))
|
||||
}
|
||||
|
||||
let (incr, num_elements) = match (v2.sub(&v1)?.as_i64(), incr.as_i64()) {
|
||||
(Some(v), Some(incr)) if v >= 0 => (incr, v / incr + 1),
|
||||
(Some(v), Some(incr)) => (-incr, -v / incr + 1),
|
||||
_ => bail!(span.error("could not determine number of elements")),
|
||||
};
|
||||
|
||||
let mut values = Vec::with_capacity(num_elements as usize);
|
||||
let incr = Number::from(incr);
|
||||
let mut v = v1;
|
||||
while (v <= v2 && incr.is_positive()) || (v >= v2 && !incr.is_positive()) {
|
||||
values.push(Value::from(v.clone()));
|
||||
v.add_assign(&incr)?;
|
||||
}
|
||||
|
||||
Ok(Value::from_array(values))
|
||||
}
|
||||
|
||||
fn round(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
ensure_args_count(span, "round", params, args, 1)?;
|
||||
let name = "round";
|
||||
ensure_args_count(span, name, params, args, 1)?;
|
||||
Ok(Value::from(
|
||||
ensure_numeric("round", ¶ms[0], &args[0])?.round(),
|
||||
ensure_numeric(name, ¶ms[0], &args[0])?.round(),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,18 @@ fn compare(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
|
||||
Ok(Value::from(result as i64))
|
||||
}
|
||||
|
||||
fn is_valid(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> 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, ¶ms[0], &args[0])?;
|
||||
Ok(Value::Bool(Version::parse(&v).is_ok()))
|
||||
Ok(Value::Bool(
|
||||
Version::parse(&if strict {
|
||||
ensure_string(name, ¶ms[0], &args[0])?
|
||||
} else {
|
||||
match &args[0] {
|
||||
Value::String(s) => s.clone(),
|
||||
_ => return Ok(Value::Bool(false)),
|
||||
}
|
||||
})
|
||||
.is_ok(),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -157,6 +157,41 @@ fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
|
||||
))
|
||||
}
|
||||
|
||||
fn to_string(v: &Value, unescape: bool) -> String {
|
||||
match v {
|
||||
Value::Null => "null".to_owned(),
|
||||
Value::Bool(b) => b.to_string(),
|
||||
Value::String(s) if unescape => serde_json::to_string(&s).unwrap_or(s.as_ref().to_string()),
|
||||
Value::String(s) => s.as_ref().to_string(),
|
||||
Value::Number(n) => n.format_decimal(),
|
||||
Value::Array(a) => {
|
||||
"[".to_owned()
|
||||
+ &a.iter()
|
||||
.map(|e| to_string(e, true))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
+ "]"
|
||||
}
|
||||
Value::Set(s) => {
|
||||
"{".to_owned()
|
||||
+ &s.iter()
|
||||
.map(|e| to_string(e, true))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
+ "}"
|
||||
}
|
||||
Value::Object(o) => {
|
||||
"{".to_owned()
|
||||
+ &o.iter()
|
||||
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
+ "}"
|
||||
}
|
||||
Value::Undefined => "#undefined".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
||||
let name = "sprintf";
|
||||
ensure_args_count(span, name, params, args, 2)?;
|
||||
@@ -219,6 +254,9 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
|
||||
// Handle Golang printing verbs.
|
||||
// https://pkg.go.dev/fmt
|
||||
match (verb, arg) {
|
||||
('s', Value::String(sv)) => s += sv.as_ref(),
|
||||
('s', v) => s += &to_string(v, false),
|
||||
|
||||
('v', _) => s += format!("{arg}").as_str(),
|
||||
('b', Value::Number(f)) if f.is_integer() => {
|
||||
let (sign, v) = get_sign_value(f);
|
||||
@@ -315,11 +353,6 @@ fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) ->
|
||||
bail!(args_span.error("floating-point number specified for format verb {verb}."));
|
||||
}
|
||||
|
||||
('s', Value::String(sv)) => s += sv.as_ref(),
|
||||
('s', _) => {
|
||||
bail!(args_span.error("invalid non string argument specified for format verb %s"));
|
||||
}
|
||||
|
||||
('+', _) if chars.next() == Some('v') => {
|
||||
bail!(args_span.error("Go-syntax fields names format verm %#v is not supported."));
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ impl Engine {
|
||||
self.interpreter.set_current_module(prev_module)?;
|
||||
}
|
||||
|
||||
self.interpreter.create_rule_prefixes()?;
|
||||
Ok(self.interpreter.get_data_mut().clone())
|
||||
}
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ impl Interpreter {
|
||||
module: None,
|
||||
schedule: None,
|
||||
current_module_path: String::default(),
|
||||
input: Value::Null,
|
||||
input: Value::Undefined,
|
||||
data: Value::new_object(),
|
||||
init_data: Value::new_object(),
|
||||
with_document: Value::new_object(),
|
||||
@@ -288,7 +288,11 @@ impl Interpreter {
|
||||
Value::String(s) => s.to_string(),
|
||||
_ => index.to_string(),
|
||||
};
|
||||
let ref_path = ref_path + "." + &index + "." + &path.join("");
|
||||
let ref_path = if path.is_empty() {
|
||||
ref_path + "." + &index
|
||||
} else {
|
||||
ref_path + "." + &index + "." + &path.join(".")
|
||||
};
|
||||
self.ensure_rule_evaluated(ref_path)?;
|
||||
}
|
||||
}
|
||||
@@ -483,7 +487,15 @@ impl Interpreter {
|
||||
(ArithOp::Sub, Value::Set(_), _) | (ArithOp::Sub, _, Value::Set(_)) => {
|
||||
builtins::sets::difference(lhs, rhs, lhs_value, rhs_value)
|
||||
}
|
||||
_ => builtins::numbers::arithmetic_operation(span, op, lhs, rhs, lhs_value, rhs_value),
|
||||
_ => builtins::numbers::arithmetic_operation(
|
||||
span,
|
||||
op,
|
||||
lhs,
|
||||
rhs,
|
||||
lhs_value,
|
||||
rhs_value,
|
||||
self.strict_builtin_errors,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,6 +1010,7 @@ impl Interpreter {
|
||||
)?,
|
||||
_ => self.eval_expr(expr)?,
|
||||
};
|
||||
|
||||
if let Some(ctx) = self.contexts.last_mut() {
|
||||
if let Some(result) = &mut ctx.result {
|
||||
result
|
||||
@@ -1011,14 +1024,10 @@ impl Interpreter {
|
||||
Literal::SomeVars { span, vars, .. } => {
|
||||
for var in vars {
|
||||
let name = var.source_str();
|
||||
if let Ok(variable) = self.add_variable_or(&name) {
|
||||
if variable != Value::Undefined {
|
||||
return Err(anyhow!(
|
||||
"duplicated definition of local variable {}",
|
||||
name
|
||||
));
|
||||
}
|
||||
if self.current_scope()?.get(&name).is_some() {
|
||||
bail!("duplicated definition of local variable {}", name);
|
||||
}
|
||||
self.add_variable_or(&name)?;
|
||||
}
|
||||
if let Some(ctx) = self.contexts.last_mut() {
|
||||
if let Some(result) = &mut ctx.result {
|
||||
@@ -1064,6 +1073,13 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
fn eval_stmt(&mut self, stmt: &LiteralStmt, stmts: &[&LiteralStmt]) -> Result<bool> {
|
||||
debug_new_group!(
|
||||
"eval_stmt {}:{} {}",
|
||||
stmt.span.line,
|
||||
stmt.span.col,
|
||||
stmt.span.text()
|
||||
);
|
||||
|
||||
let mut skip_exec = false;
|
||||
let saved_state = if !stmt.with_mods.is_empty() {
|
||||
// Save state;
|
||||
@@ -1185,6 +1201,9 @@ impl Interpreter {
|
||||
if loop_expr_value[&idx] != Value::Undefined {
|
||||
result = self.eval_stmts_in_loop(stmts, &loops[1..])? || result;
|
||||
return Ok(result);
|
||||
} else if idx != Value::Undefined {
|
||||
// The index is not valid for this collection.
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1240,11 +1259,8 @@ impl Interpreter {
|
||||
result = false;
|
||||
}
|
||||
_ => {
|
||||
return Err(loop_expr.span.source.error(
|
||||
loop_expr.span.line,
|
||||
loop_expr.span.col,
|
||||
"item cannot be indexed",
|
||||
));
|
||||
// The item is not a collection.
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1854,6 +1870,7 @@ impl Interpreter {
|
||||
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() {
|
||||
@@ -1967,6 +1984,7 @@ impl Interpreter {
|
||||
fn lookup_var(&mut self, span: &Span, fields: &[&str], no_error: bool) -> Result<Value> {
|
||||
let name = span.source_str();
|
||||
|
||||
debug_new_group!("lookup_var: name={name}, fields={fields:?}, no_error={no_error}");
|
||||
// Return local variable/argument.
|
||||
if let Some(v) = self.lookup_local_var(&name) {
|
||||
return Ok(Self::get_value_chained(v, fields));
|
||||
@@ -1992,6 +2010,7 @@ impl Interpreter {
|
||||
// If the rule has already been evaluated or specified via a with modifier,
|
||||
// use that value.
|
||||
if v != Value::Undefined {
|
||||
debug!("returning v = {v}");
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
@@ -2051,6 +2070,13 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
fn eval_expr(&mut self, expr: &ExprRef) -> Result<Value> {
|
||||
debug_new_group!(
|
||||
"eval_expr: {}:{} {}",
|
||||
expr.span().line,
|
||||
expr.span().col,
|
||||
expr.span().text()
|
||||
);
|
||||
|
||||
match expr.as_ref() {
|
||||
Expr::Null(_) => Ok(Value::Null),
|
||||
Expr::True(_) => Ok(Value::Bool(true)),
|
||||
@@ -2292,7 +2318,7 @@ impl Interpreter {
|
||||
comps.push(*v.text());
|
||||
expr = None;
|
||||
}
|
||||
_ => bail!("internal error: not a simple ref"),
|
||||
_ => bail!(format!("internal error: not a simplee ref {expr:?}")),
|
||||
}
|
||||
}
|
||||
if let Some(d) = document {
|
||||
@@ -2650,11 +2676,110 @@ impl Interpreter {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rule_path_components(mut refr: &Ref<Expr>) -> Result<Vec<Rc<str>>> {
|
||||
let mut components: Vec<Rc<str>> = vec![];
|
||||
loop {
|
||||
refr = match refr.as_ref() {
|
||||
Expr::Var(v) => {
|
||||
components.push((*v.text().as_ref()).into());
|
||||
break;
|
||||
}
|
||||
Expr::RefBrack { refr, index, .. } => {
|
||||
if let Expr::String(s) = index.as_ref() {
|
||||
components.push((*s.text().as_ref()).into());
|
||||
}
|
||||
refr
|
||||
}
|
||||
Expr::RefDot { refr, field, .. } => {
|
||||
components.push((*field.text().as_ref()).into());
|
||||
refr
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
components.reverse();
|
||||
Ok(components)
|
||||
}
|
||||
|
||||
pub fn create_rule_prefixes(&mut self) -> Result<()> {
|
||||
debug_new_group!("create_rule_prefixes");
|
||||
debug!("data before: {}", self.data);
|
||||
|
||||
for module in self.modules.clone() {
|
||||
let module_path = Self::get_rule_path_components(&module.package.refr)?;
|
||||
debug!("processing module {module_path:?}");
|
||||
|
||||
for rule in &module.policy {
|
||||
let mut rule_refr = Self::get_rule_refr(rule);
|
||||
debug!("rule refr: {}", rule_refr.span().text());
|
||||
debug!("rule : {:?}", rule);
|
||||
if let Rule::Spec {
|
||||
head:
|
||||
RuleHead::Set {
|
||||
refr, key: None, ..
|
||||
},
|
||||
..
|
||||
} = rule.as_ref()
|
||||
{
|
||||
rule_refr = match refr.as_ref() {
|
||||
Expr::RefDot { refr, .. } => refr,
|
||||
|
||||
_ => refr,
|
||||
}
|
||||
}
|
||||
|
||||
let mut prefix_path = module_path.clone();
|
||||
prefix_path.append(&mut Self::get_rule_path_components(rule_refr)?);
|
||||
let prefix_path: Vec<&str> = prefix_path[0..prefix_path.len() - 1]
|
||||
.iter()
|
||||
.map(|s| s.as_ref())
|
||||
.collect();
|
||||
|
||||
if Self::get_value_chained(self.data.clone(), &prefix_path) == Value::Undefined {
|
||||
self.update_data(
|
||||
rule_refr.span(),
|
||||
rule_refr,
|
||||
&prefix_path,
|
||||
Value::new_object(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("data after: {}", self.data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn record_rule(&mut self, refr: &Ref<Expr>, rule: Ref<Rule>) -> Result<()> {
|
||||
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());
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(vec![rule.clone()]);
|
||||
}
|
||||
}
|
||||
let path = Self::get_path_string(refr, None)?;
|
||||
let path = self.current_module_path.clone() + "." + &path;
|
||||
match self.rules.entry(path) {
|
||||
Entry::Occupied(o) => {
|
||||
o.into_mut().push(rule.clone());
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(vec![rule.clone()]);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn gather_rules(&mut self) -> Result<()> {
|
||||
for module in self.modules.clone() {
|
||||
let prev_module = self.set_current_module(Some(module.clone()))?;
|
||||
for rule in &module.policy {
|
||||
let refr = Self::get_rule_refr(rule);
|
||||
|
||||
if let Rule::Spec { .. } = rule.as_ref() {
|
||||
// Adjust refr to ensure simple ref.
|
||||
// TODO: refactor.
|
||||
@@ -2667,18 +2792,7 @@ impl Interpreter {
|
||||
Expr::RefBrack { refr, .. } => refr,
|
||||
_ => refr,
|
||||
};
|
||||
//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());
|
||||
}
|
||||
Entry::Vacant(v) => {
|
||||
v.insert(vec![rule.clone()]);
|
||||
}
|
||||
}
|
||||
self.record_rule(refr, rule.clone())?;
|
||||
} else if let Rule::Default { .. } = rule.as_ref() {
|
||||
let (refr, index) = match refr.as_ref() {
|
||||
// TODO: Validate the index
|
||||
@@ -2698,10 +2812,8 @@ impl Interpreter {
|
||||
_ => (refr, None),
|
||||
};
|
||||
|
||||
//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;
|
||||
let path = Self::get_path_string(refr, None)?;
|
||||
let path = self.current_module_path.clone() + "." + &path;
|
||||
match self.default_rules.entry(path) {
|
||||
Entry::Occupied(o) => {
|
||||
for (_, i) in o.get() {
|
||||
|
||||
@@ -282,6 +282,12 @@ impl Number {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_positive(&self) -> bool {
|
||||
match self {
|
||||
Big(b) => b.d.is_sign_positive(),
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_integers(a: &Number, b: &Number) -> Option<(BigInt, BigInt)> {
|
||||
match (a, b) {
|
||||
(Big(a), Big(b)) if a.d.is_integer() && b.d.is_integer() => {
|
||||
|
||||
71
src/utils.rs
71
src/utils.rs
@@ -9,6 +9,77 @@ use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)+) => {
|
||||
{
|
||||
if log::log_enabled!(log::Level::Debug) {
|
||||
print!("{}:{}:", file!(), line!());
|
||||
crate::utils::NESTING.with(|f| {
|
||||
print!("{}", " ".repeat(*f.borrow() as usize));
|
||||
});
|
||||
println!($($arg)+);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)+) => {};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) use debug;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[allow(unused)]
|
||||
macro_rules! debug_new_group {
|
||||
($($arg:tt)+) => {
|
||||
debug!($($arg)+);
|
||||
let _group = DebugNesting::new();
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
macro_rules! debug_new_group {
|
||||
($($arg:tt)+) => {};
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) use debug_new_group;
|
||||
|
||||
#[allow(unused)]
|
||||
pub struct DebugNesting {}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
thread_local!(pub static NESTING: std::cell::RefCell<u32> = std::cell::RefCell::new(1));
|
||||
|
||||
impl DebugNesting {
|
||||
#[cfg(debug_assertions)]
|
||||
#[allow(unused)]
|
||||
pub fn new() -> DebugNesting {
|
||||
NESTING.with(|f| {
|
||||
*f.borrow_mut() += 1;
|
||||
});
|
||||
DebugNesting {}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl Drop for DebugNesting {
|
||||
#[cfg(debug_assertions)]
|
||||
fn drop(&mut self) {
|
||||
NESTING.with(|f| {
|
||||
*f.borrow_mut() -= 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn drop(&mut self) {}
|
||||
}
|
||||
|
||||
pub fn get_path_string(refr: &Expr, document: Option<&str>) -> Result<String> {
|
||||
let mut comps: Vec<&str> = vec![];
|
||||
let mut expr = Some(refr);
|
||||
|
||||
@@ -25,11 +25,12 @@ pub enum Value {
|
||||
Number(Number),
|
||||
String(Rc<str>),
|
||||
Array(Rc<Vec<Value>>),
|
||||
Object(Rc<BTreeMap<Value, Value>>),
|
||||
|
||||
// Extra rego data type
|
||||
Set(Rc<BTreeSet<Value>>),
|
||||
|
||||
Object(Rc<BTreeMap<Value, Value>>),
|
||||
|
||||
// Indicate that a value is undefined
|
||||
Undefined,
|
||||
}
|
||||
|
||||
@@ -85,11 +85,14 @@ fn run_aci_tests(dir: &Path) -> Result<()> {
|
||||
println!("passed {:?}", duration);
|
||||
}
|
||||
Ok(actual) => {
|
||||
println!("failed {:?}", duration);
|
||||
println!("ACTUAL:");
|
||||
println!("{}", serde_json::to_string(&actual)?);
|
||||
println!("EXPECTED");
|
||||
println!("{}", serde_json::to_string(&case.want_result)?);
|
||||
println!(
|
||||
"DIFF {}",
|
||||
colored_diff::PrettyDifference {
|
||||
expected: &serde_yaml::to_string(&case.want_result)?,
|
||||
actual: &serde_yaml::to_string(&actual)?
|
||||
}
|
||||
);
|
||||
|
||||
nfailures += 1;
|
||||
}
|
||||
Err(e) => {
|
||||
|
||||
@@ -214,8 +214,8 @@ cases:
|
||||
0,
|
||||
"",
|
||||
[],
|
||||
set(),
|
||||
{},
|
||||
set()
|
||||
]
|
||||
|
||||
results = {
|
||||
@@ -241,35 +241,35 @@ cases:
|
||||
lt:
|
||||
object!:
|
||||
- key: null
|
||||
value: [false, 0, "", [], {}, {"set!":[]}]
|
||||
value: [false, 0, "", [], {"set!":[]}, {}]
|
||||
- key: false
|
||||
value: [0, "", [], {}, {"set!":[]}]
|
||||
value: [0, "", [], {"set!":[]}, {}]
|
||||
- key: 0
|
||||
value: ["", [], {}, {"set!":[]}]
|
||||
value: ["", [], {"set!":[]}, {}]
|
||||
- key: ""
|
||||
value: [[], {}, {"set!":[]}]
|
||||
value: [[], {"set!":[]}, {}]
|
||||
- key: []
|
||||
value: [{}, {"set!":[]}]
|
||||
- key: {}
|
||||
value: [{"set!":[]}]
|
||||
value: [{"set!":[]}, {}]
|
||||
- key: {"set!":[]}
|
||||
value: [{}]
|
||||
- key: {}
|
||||
value: []
|
||||
le:
|
||||
object!:
|
||||
- key: null
|
||||
value: [null, false, 0, "", [], {}, {"set!":[]}]
|
||||
value: [null, false, 0, "", [], {"set!":[]}, {}]
|
||||
- key: false
|
||||
value: [false, 0, "", [], {}, {"set!":[]}]
|
||||
value: [false, 0, "", [], {"set!":[]}, {}]
|
||||
- key: 0
|
||||
value: [0, "", [], {}, {"set!":[]}]
|
||||
value: [0, "", [], {"set!":[]}, {}]
|
||||
- key: ""
|
||||
value: ["", [], {}, {"set!":[]}]
|
||||
value: ["", [], {"set!":[]}, {}]
|
||||
- key: []
|
||||
value: [[], {}, {"set!":[]}]
|
||||
- key: {}
|
||||
value: [{}, {"set!":[]}]
|
||||
value: [[], {"set!":[]}, {}]
|
||||
- key: {"set!":[]}
|
||||
value: [{"set!":[]}]
|
||||
value: [{"set!":[]}, {}]
|
||||
- key: {}
|
||||
value: [{}]
|
||||
gt:
|
||||
object!:
|
||||
- key: null
|
||||
@@ -282,10 +282,10 @@ cases:
|
||||
value: [null, false, 0]
|
||||
- key: []
|
||||
value: [null, false, 0, ""]
|
||||
- key: {}
|
||||
value: [null, false, 0, "", []]
|
||||
- key: {"set!":[]}
|
||||
value: [null, false, 0, "", [], {}]
|
||||
value: [null, false, 0, "", []]
|
||||
- key: {}
|
||||
value: [null, false, 0, "", [], {"set!":[]}]
|
||||
ge:
|
||||
object!:
|
||||
- key: null
|
||||
@@ -298,10 +298,10 @@ cases:
|
||||
value: [null, false, 0, ""]
|
||||
- key: []
|
||||
value: [null, false, 0, "", []]
|
||||
- key: {}
|
||||
value: [null, false, 0, "", [], {}]
|
||||
- key: {"set!":[]}
|
||||
value: [null, false, 0, "", [], {}, {"set!":[]}]
|
||||
value: [null, false, 0, "", [], {"set!":[]}]
|
||||
- key: {}
|
||||
value: [null, false, 0, "", [], {"set!":[]}, {}]
|
||||
|
||||
- note: arrays
|
||||
data: {}
|
||||
|
||||
@@ -15,10 +15,6 @@ cases:
|
||||
# Single item range
|
||||
r5 = numbers.range(8, 8)
|
||||
|
||||
# Non-integer start and end result in Undefined.
|
||||
r6 = numbers.range(1.01, 5)
|
||||
r7 = numbers.range(1, 5.01)
|
||||
|
||||
y { false }
|
||||
r8 = numbers.range(y, 10)
|
||||
r9 = numbers.range(10, y)
|
||||
@@ -31,6 +27,24 @@ cases:
|
||||
r4: [-5, -4, -3, -2, -1]
|
||||
r5: [8]
|
||||
|
||||
- note: non integer start
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
r6 = numbers.range(1.01, 5)
|
||||
error: must be integer
|
||||
query: data
|
||||
|
||||
- note: non integer start
|
||||
data: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
r7 = numbers.range(1, 5.01)
|
||||
error: must be integer
|
||||
query: data
|
||||
|
||||
- note: less-args
|
||||
data: {}
|
||||
modules:
|
||||
|
||||
@@ -68,85 +68,17 @@ pub fn process_value(v: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
|
||||
fn display_values(c: &Value, e: &Value) -> Result<String> {
|
||||
Ok(format!(
|
||||
"\nleft = {}\nright = {}\n",
|
||||
serde_json::to_string_pretty(c)?,
|
||||
serde_json::to_string_pretty(e)?
|
||||
))
|
||||
}
|
||||
|
||||
// Helper function to match computed and expecte values.
|
||||
// On mismatch, prints the failing sub-value instead of the whole value.
|
||||
fn match_values_impl(computed: &Value, expected: &Value) -> Result<()> {
|
||||
match (&computed, &expected) {
|
||||
(Value::Array(a1), Value::Array(a2)) => {
|
||||
if a1.len() != a2.len() {
|
||||
bail!(
|
||||
"array length mismatch: {} != {}{}",
|
||||
a1.len(),
|
||||
a2.len(),
|
||||
display_values(computed, expected)?
|
||||
);
|
||||
}
|
||||
|
||||
for (idx, v1) in a1.iter().enumerate() {
|
||||
match_values_impl(v1, &a2[idx])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
(Value::Set(s1), Value::Set(s2)) => {
|
||||
if s1.len() != s2.len() {
|
||||
bail!(
|
||||
"set length mismatch: {} != {}{}",
|
||||
s1.len(),
|
||||
s2.len(),
|
||||
display_values(computed, expected)?
|
||||
);
|
||||
}
|
||||
|
||||
let mut itr2 = s2.iter();
|
||||
for v1 in s1.iter() {
|
||||
match_values_impl(v1, itr2.next().unwrap())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
(Value::Object(o1), Value::Object(o2)) => {
|
||||
if o1.len() != o2.len() {
|
||||
bail!(
|
||||
"object length mismatch: {} != {}{}",
|
||||
o1.len(),
|
||||
o2.len(),
|
||||
display_values(computed, expected)?
|
||||
);
|
||||
}
|
||||
|
||||
let mut itr2 = o2.iter();
|
||||
for (k1, v1) in o1.iter() {
|
||||
let (k2, v2) = itr2.next().unwrap();
|
||||
match_values_impl(k1, k2)?;
|
||||
match_values_impl(v1, v2)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
(Value::Number(n1), Value::Number(n2)) if n1 == n2 => Ok(()),
|
||||
(Value::String(s1), Value::String(s2)) if s1 == s2 => Ok(()),
|
||||
(Value::Bool(b1), Value::Bool(b2)) if b1 == b2 => Ok(()),
|
||||
(Value::Null, Value::Null) => Ok(()),
|
||||
(Value::Undefined, Value::Undefined) => Ok(()),
|
||||
|
||||
_ => bail!("value mismatch: {}", display_values(computed, expected)?),
|
||||
}
|
||||
}
|
||||
|
||||
fn match_values(computed: &Value, expected: &Value) -> Result<()> {
|
||||
match match_values_impl(computed, expected) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => bail!("\nmismatch in {}{}", display_values(computed, expected)?, e),
|
||||
if computed != expected {
|
||||
panic!(
|
||||
"{}",
|
||||
colored_diff::PrettyDifference {
|
||||
expected: &serde_yaml::to_string(&expected)?,
|
||||
actual: &serde_yaml::to_string(&computed)?
|
||||
}
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_output(computed_results: &[Value], expected_results: &[Value]) -> Result<()> {
|
||||
|
||||
@@ -25,6 +25,7 @@ cryptomd5
|
||||
cryptosha1
|
||||
cryptosha256
|
||||
dataderef
|
||||
defaultkeyword
|
||||
disjunction
|
||||
elsekeyword
|
||||
embeddedvirtualdoc
|
||||
@@ -44,7 +45,10 @@ invalidkeyerror
|
||||
jsonfilteridempotent
|
||||
jwtencodesignheadererrors
|
||||
jwtencodesignpayloaderrors
|
||||
negation
|
||||
nestedreferences
|
||||
numbersrange
|
||||
numbersrangestep
|
||||
objectfilter
|
||||
objectfilteridempotent
|
||||
objectfilternonstringkey
|
||||
@@ -55,6 +59,7 @@ objectremoveidempotent
|
||||
objectremovenonstringkey
|
||||
partialdocconstants
|
||||
partialsetdoc
|
||||
planner-ir
|
||||
rand
|
||||
regexfind
|
||||
regexfindallstringsubmatch
|
||||
@@ -65,9 +70,13 @@ regexreplace
|
||||
regexsplit
|
||||
replacen
|
||||
semvercompare
|
||||
semverisvalid
|
||||
sets
|
||||
sprintf
|
||||
subset
|
||||
toarray
|
||||
topdowndynamicdispatch
|
||||
toset
|
||||
trim
|
||||
trimleft
|
||||
trimprefix
|
||||
@@ -79,4 +88,6 @@ typebuiltin
|
||||
typenamebuiltin
|
||||
undos
|
||||
union
|
||||
units
|
||||
units
|
||||
varreferences
|
||||
virtualdocs
|
||||
@@ -145,7 +145,9 @@ fn run_opa_tests(opa_tests_dir: String, folders: &[String]) -> Result<()> {
|
||||
println!("\n{} failed.", case.note);
|
||||
println!("{}", serde_yaml::to_string(&case)?);
|
||||
match &r {
|
||||
Ok(actual) => println!("GOT\n{}", serde_yaml::to_string(&actual)?),
|
||||
Ok(actual) => {
|
||||
println!("GOT\n{}", serde_yaml::to_string(&actual)?);
|
||||
}
|
||||
Err(e) => println!("ERROR: {e}"),
|
||||
}
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ fn non_string_key() -> Result<()> {
|
||||
"false": null,
|
||||
"3.141592653589793": null,
|
||||
"[true,null,3.141592653589793]": null,
|
||||
"{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"<undefined>\\\"\":null}": null,
|
||||
"[false,true,3.141592653589793]": null,
|
||||
"{\"null\":null,\"false\":null,\"3.141592653589793\":null,\"[true,null,3.141592653589793]\":null,\"[false,true,3.141592653589793]\":null,\"\\\"<undefined>\\\"\":null}": null,
|
||||
"\"<undefined>\"": null
|
||||
}"#;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user