mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Compare commits
7 Commits
copilot/fi
...
copilot/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6e156e8a | ||
|
|
5ae1d8abf2 | ||
|
|
03c4275855 | ||
|
|
78f226f957 | ||
|
|
bec159a580 | ||
|
|
117671d959 | ||
|
|
8617c79da5 |
@@ -4,9 +4,9 @@
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::builtins::{self, BuiltinFcn};
|
||||
use crate::compiled_policy::CompiledPolicyData;
|
||||
#[cfg(feature = "azure_policy")]
|
||||
use crate::compiled_policy::TargetInfo;
|
||||
use crate::compiled_policy::{CompiledPolicyData, DefaultRuleInfo};
|
||||
use crate::compiler::destructuring_planner::{
|
||||
AssignmentPlan, BindingPlan, DestructuringPlan, WildcardSide,
|
||||
};
|
||||
@@ -1724,6 +1724,9 @@ impl Interpreter {
|
||||
// For now, we restrict constant refs to those that contain only simple literals.
|
||||
fn is_constant_ref(mut expr: &Ref<Expr>) -> Result<bool> {
|
||||
loop {
|
||||
if Self::is_simple_literal(expr)? {
|
||||
return Ok(true);
|
||||
}
|
||||
match expr.as_ref() {
|
||||
Expr::Var { .. } => break,
|
||||
Expr::RefDot { refr, .. } => expr = refr,
|
||||
@@ -1747,6 +1750,30 @@ impl Interpreter {
|
||||
))
|
||||
}
|
||||
|
||||
fn is_constant_key_expr(&self, expr: &Ref<Expr>) -> Result<bool> {
|
||||
if Self::is_simple_literal(expr)? {
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
match expr.as_ref() {
|
||||
Expr::Var { span, .. } => {
|
||||
// A variable that is not currently bound in any active local scope behaves like
|
||||
// a stable global/package reference for this evaluation.
|
||||
let is_bound = self
|
||||
.scopes
|
||||
.iter()
|
||||
.rev()
|
||||
.any(|scope| scope.contains_key(&span.source_str()));
|
||||
Ok(!is_bound)
|
||||
}
|
||||
Expr::RefDot { refr, .. } => self.is_constant_key_expr(refr),
|
||||
Expr::RefBrack { refr, index, .. } => {
|
||||
Ok(self.is_constant_key_expr(refr)? && self.is_constant_key_expr(index)?)
|
||||
}
|
||||
_ => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
// A rule's output expression is constant if it does not contain local variables.
|
||||
// For now, we restrict output expressions to those that contain only simple literals.
|
||||
fn is_constant_output(key_expr: &Option<Ref<Expr>>, output_expr: &Ref<Expr>) -> Result<bool> {
|
||||
@@ -1797,7 +1824,12 @@ impl Interpreter {
|
||||
comps.pop();
|
||||
output
|
||||
} else {
|
||||
// Rule's constness is determined only by its ref.
|
||||
// Implicit-true partial object rules can vary with each successful key binding.
|
||||
if let Some(ke) = &key_expr {
|
||||
if !is_old_style_set && !self.is_constant_key_expr(ke)? {
|
||||
is_const_rule = false;
|
||||
}
|
||||
}
|
||||
Value::Bool(true)
|
||||
};
|
||||
|
||||
@@ -2942,6 +2974,41 @@ impl Interpreter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_rules_for_path(&self, path: &str) -> Option<Vec<DefaultRuleInfo>> {
|
||||
if let Some(rules) = self.compiled_policy.default_rules.get(path) {
|
||||
return Some(rules.clone());
|
||||
}
|
||||
|
||||
let (parent_path, index) = path.rsplit_once('.')?;
|
||||
let rules = self.compiled_policy.default_rules.get(parent_path)?;
|
||||
let matches = rules
|
||||
.iter()
|
||||
.filter(|(_, rule_index)| Self::default_rule_index_matches(rule_index, index))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if matches.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(matches)
|
||||
}
|
||||
}
|
||||
|
||||
fn has_default_rules_for_path(&self, path: &str) -> bool {
|
||||
self.default_rules_for_path(path).is_some()
|
||||
}
|
||||
|
||||
fn default_rule_index_matches(index: &Option<String>, path_component: &str) -> bool {
|
||||
match index.as_deref() {
|
||||
Some(index) if index == path_component => true,
|
||||
Some(index) => index
|
||||
.strip_prefix('"')
|
||||
.and_then(|index| index.strip_suffix('"'))
|
||||
.is_some_and(|index| index == path_component),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_rule_evaluated(&mut self, path: String) -> Result<()> {
|
||||
self.check_execution_time()?;
|
||||
let mut matched = false;
|
||||
@@ -2956,9 +3023,9 @@ impl Interpreter {
|
||||
}
|
||||
|
||||
// Evaluate the associated default rules after non-default rules
|
||||
if let Some(rules) = self.compiled_policy.default_rules.get(&path) {
|
||||
if let Some(rules) = self.default_rules_for_path(&path) {
|
||||
matched = true;
|
||||
for (r, _) in rules.clone() {
|
||||
for (r, _) in rules {
|
||||
if !self.processed.contains(&r) {
|
||||
let module = self.get_rule_module(&r)?;
|
||||
let prev_module = self.set_current_module(Some(module))?;
|
||||
@@ -3049,10 +3116,7 @@ impl Interpreter {
|
||||
let prefix = fields.iter().take(i).copied().collect::<Vec<_>>();
|
||||
let prefix_path = format!("data.{}", prefix.join("."));
|
||||
if self.compiled_policy.rules.contains_key(&prefix_path)
|
||||
|| self
|
||||
.compiled_policy
|
||||
.default_rules
|
||||
.contains_key(&prefix_path)
|
||||
|| self.has_default_rules_for_path(&prefix_path)
|
||||
{
|
||||
self.ensure_rule_evaluated(prefix_path)?;
|
||||
break;
|
||||
@@ -3076,7 +3140,7 @@ impl Interpreter {
|
||||
|
||||
if !no_error
|
||||
&& !self.compiled_policy.rules.contains_key(&rule_path)
|
||||
&& !self.compiled_policy.default_rules.contains_key(&rule_path)
|
||||
&& !self.has_default_rules_for_path(&rule_path)
|
||||
&& !self.compiled_policy.imports.contains_key(&rule_path)
|
||||
{
|
||||
bail!(span.error(&format!(
|
||||
@@ -3099,7 +3163,7 @@ impl Interpreter {
|
||||
};
|
||||
|
||||
if self.compiled_policy.rules.contains_key(&path)
|
||||
|| self.compiled_policy.default_rules.contains_key(&path)
|
||||
|| self.has_default_rules_for_path(&path)
|
||||
{
|
||||
self.ensure_rule_evaluated(path)?;
|
||||
found = true;
|
||||
@@ -3646,7 +3710,7 @@ impl Interpreter {
|
||||
self.data = Value::Undefined;
|
||||
self.ensure_loop_var_values_capacity();
|
||||
|
||||
let default_rules = self.compiled_policy.default_rules.get(rule_path).cloned();
|
||||
let default_rules = self.default_rules_for_path(rule_path);
|
||||
|
||||
if let Some(rules) = default_rules {
|
||||
for (rule, _) in rules {
|
||||
|
||||
@@ -181,7 +181,7 @@ impl<'a> Compiler<'a> {
|
||||
}
|
||||
|
||||
fn evaluate_default_rule(&mut self, rule_path: &str) -> Option<u16> {
|
||||
if !self.policy.inner.default_rules.contains_key(rule_path) {
|
||||
if !self.may_have_default_rule(rule_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -200,6 +200,33 @@ impl<'a> Compiler<'a> {
|
||||
None
|
||||
}
|
||||
|
||||
fn may_have_default_rule(&self, rule_path: &str) -> bool {
|
||||
if self.policy.inner.default_rules.contains_key(rule_path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let Some((parent_path, index)) = rule_path.rsplit_once('.') else {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.policy
|
||||
.inner
|
||||
.default_rules
|
||||
.get(parent_path)
|
||||
.is_some_and(|rules| {
|
||||
rules
|
||||
.iter()
|
||||
.any(|(_, rule_index)| match rule_index.as_deref() {
|
||||
Some(rule_index) if rule_index == index => true,
|
||||
Some(rule_index) => rule_index
|
||||
.strip_prefix('"')
|
||||
.and_then(|rule_index| rule_index.strip_suffix('"'))
|
||||
.is_some_and(|rule_index| rule_index == index),
|
||||
None => false,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_destructuring_blocks(&self, rule_index: u16) -> Vec<Option<u32>> {
|
||||
self.rule_definition_destructuring_patterns[rule_index as usize].clone()
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
)]
|
||||
|
||||
use super::{CompilationContext, Compiler, CompilerError, ContextType, Result, WorklistEntry};
|
||||
use crate::ast::{Expr, ExprRef, Rule, RuleHead};
|
||||
use crate::ast::{AssignOp, Expr, ExprRef, Rule, RuleHead};
|
||||
use crate::compiler::destructuring_planner::plans::BindingPlan;
|
||||
use crate::lexer::Span;
|
||||
use crate::rvm::program::{Program, RuleType};
|
||||
@@ -52,14 +52,29 @@ impl<'a> Compiler<'a> {
|
||||
let rule_types: BTreeSet<RuleType> = definitions
|
||||
.iter()
|
||||
.map(|def| {
|
||||
if let Rule::Spec { head, .. } = def.as_ref() {
|
||||
if let Rule::Spec { head, bodies, .. } = def.as_ref() {
|
||||
match head {
|
||||
RuleHead::Set { .. } => RuleType::PartialSet,
|
||||
RuleHead::Compr { refr, assign, .. } => match refr.as_ref() {
|
||||
crate::ast::Expr::RefBrack { .. } if assign.is_some() => {
|
||||
// Variable-key bracket heads emit one object entry per successful
|
||||
// binding, so they must compile as partial objects.
|
||||
crate::ast::Expr::RefBrack { index, .. }
|
||||
if super::expressions::try_eval_const(index.as_ref()).is_none() =>
|
||||
{
|
||||
RuleType::PartialObject
|
||||
}
|
||||
crate::ast::Expr::RefBrack { .. } => RuleType::PartialSet,
|
||||
crate::ast::Expr::RefBrack { .. }
|
||||
if matches!(
|
||||
assign.as_ref().map(|assign| &assign.op),
|
||||
Some(AssignOp::Eq)
|
||||
) =>
|
||||
{
|
||||
RuleType::PartialObject
|
||||
}
|
||||
crate::ast::Expr::RefBrack { .. } if bodies.is_empty() => {
|
||||
RuleType::PartialObject
|
||||
}
|
||||
crate::ast::Expr::RefBrack { .. } => RuleType::Complete,
|
||||
_ => RuleType::Complete,
|
||||
},
|
||||
_ => RuleType::Complete,
|
||||
|
||||
@@ -454,7 +454,19 @@ impl RegoVM {
|
||||
let mut obj_value = self.take_register(obj)?;
|
||||
|
||||
if let Ok(obj_mut) = obj_value.as_object_mut() {
|
||||
obj_mut.insert(key_value, value_value);
|
||||
match obj_mut.get(&key_value) {
|
||||
Some(existing_value) if existing_value != &value_value => {
|
||||
self.set_register(obj, obj_value)?;
|
||||
return Err(VmError::RuleMultipleOutputs { pc: self.pc });
|
||||
}
|
||||
Some(_) => {
|
||||
self.set_register(obj, obj_value)?;
|
||||
return Ok(InstructionOutcome::Continue);
|
||||
}
|
||||
None => {
|
||||
obj_mut.insert(key_value, value_value);
|
||||
}
|
||||
}
|
||||
self.set_register(obj, obj_value)?;
|
||||
} else {
|
||||
let offending = obj_value.clone();
|
||||
|
||||
@@ -209,6 +209,9 @@ pub enum VmError {
|
||||
#[error("Rule-data conflict: {message} (pc={pc})")]
|
||||
RuleDataConflict { message: String, pc: usize },
|
||||
|
||||
#[error("rules must not produce multiple outputs (pc={pc})")]
|
||||
RuleMultipleOutputs { pc: usize },
|
||||
|
||||
#[error("Arithmetic error: {message} (pc={pc})")]
|
||||
ArithmeticError { message: String, pc: usize },
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ use super::execution_model::{
|
||||
use super::machine::RegoVM;
|
||||
|
||||
impl RegoVM {
|
||||
/// Returns true if the error represents a resource-limit violation that
|
||||
/// must never be silently absorbed by rule evaluation.
|
||||
/// Returns true if the error must never be silently absorbed by rule
|
||||
/// evaluation backtracking, including resource-limit failures and semantic
|
||||
/// rule consistency errors.
|
||||
pub(super) const fn is_fatal_vm_error(err: &VmError) -> bool {
|
||||
matches!(
|
||||
err,
|
||||
@@ -26,6 +27,7 @@ impl RegoVM {
|
||||
| VmError::MemoryLimitExceeded { .. }
|
||||
| VmError::RegexSizeLimitExceeded { .. }
|
||||
| VmError::InstructionLimitExceeded { .. }
|
||||
| VmError::RuleMultipleOutputs { .. }
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -341,6 +341,35 @@ cases:
|
||||
result: false
|
||||
reasons: []
|
||||
|
||||
- note: default_rule_with_object_key
|
||||
data: {}
|
||||
input: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
default config["timeout"] := 30
|
||||
config["timeout"] := val if {
|
||||
val := input.val
|
||||
}
|
||||
query: data.test.config.timeout
|
||||
want_result: 30
|
||||
|
||||
- note: default_rule_with_object_key_override
|
||||
data: {}
|
||||
input:
|
||||
val: 60
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
default config["timeout"] := 30
|
||||
config["timeout"] := val if {
|
||||
val := input.val
|
||||
}
|
||||
query: data.test.config.timeout
|
||||
want_result: 60
|
||||
|
||||
- note: default_only_rule_with_package_query
|
||||
data: {}
|
||||
modules:
|
||||
|
||||
242
tests/interpreter/cases/rule/partial_object_iteration.yaml
Normal file
242
tests/interpreter/cases/rule/partial_object_iteration.yaml
Normal file
@@ -0,0 +1,242 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: partial_object_iteration_some_in_object_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_some_in_array_v1
|
||||
data: {}
|
||||
input:
|
||||
arr: ["FOO", "BAR", "BAZ"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[v] if {
|
||||
some _, v in input.arr
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_with_filter_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
k != "BAR"
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_input_lookup_future_keywords
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
violations[k] if {
|
||||
input.x[k]
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_multiple_bodies_collect_all_keys_v1
|
||||
data: {}
|
||||
input:
|
||||
primary:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
secondary:
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.primary
|
||||
}
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.secondary
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: constant_key_implicit_true_rule_is_complete_v1
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
other: false
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p["x"] if {
|
||||
input.enabled
|
||||
}
|
||||
|
||||
p["x"] if {
|
||||
input.other
|
||||
}
|
||||
query: data.test.p.x
|
||||
want_result: true
|
||||
|
||||
- note: partial_object_duplicate_keys_same_value_are_ok_v1
|
||||
data: {}
|
||||
input:
|
||||
arr: ["FOO", "FOO", "BAR"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[v] if {
|
||||
some _, v in input.arr
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
violations:
|
||||
BAR: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_duplicate_keys_different_values_error_v1
|
||||
data: {}
|
||||
input:
|
||||
entries:
|
||||
- k: "FOO"
|
||||
v: 1
|
||||
- k: "FOO"
|
||||
v: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] := v if {
|
||||
some entry in input.entries
|
||||
k := entry.k
|
||||
v := entry.v
|
||||
}
|
||||
query: data.test.violations
|
||||
error: "rules must not produce multiple outputs"
|
||||
|
||||
- note: partial_object_and_partial_set_iteration_coexist_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
|
||||
seen contains k if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
query: data.test
|
||||
want_result:
|
||||
seen:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_key_bound_in_outer_scope_v1
|
||||
data: {}
|
||||
input:
|
||||
outer:
|
||||
FOO: [1, 2]
|
||||
BAR: [3]
|
||||
BAZ: []
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, arr in input.outer
|
||||
some _ in arr
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
FOO: true
|
||||
|
||||
- note: complete_rule_same_value_definitions_still_work_v1
|
||||
data: {}
|
||||
input:
|
||||
role: "superuser"
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
allowed if {
|
||||
input.role == "admin"
|
||||
}
|
||||
|
||||
allowed if {
|
||||
input.role == "superuser"
|
||||
}
|
||||
query: data.test.allowed
|
||||
want_result: true
|
||||
@@ -48,18 +48,34 @@ cases:
|
||||
want_result: true
|
||||
|
||||
- note: default_rule_with_object_key
|
||||
skip: true # TODO: Fix rule type classification for config["timeout"] - should be Complete, not PartialObject
|
||||
data: {}
|
||||
input: {}
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
default config["timeout"] := 30
|
||||
config["timeout"] := 60 if {
|
||||
false # This will fail
|
||||
config["timeout"] := val if {
|
||||
val := input.val
|
||||
}
|
||||
query: data.test.config.timeout
|
||||
want_result: 30
|
||||
|
||||
- note: default_rule_with_object_key_override
|
||||
data: {}
|
||||
input:
|
||||
val: 60
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
default config["timeout"] := 30
|
||||
config["timeout"] := val if {
|
||||
val := input.val
|
||||
}
|
||||
query: data.test.config.timeout
|
||||
want_result: 60
|
||||
|
||||
- note: default_rule_complex_value
|
||||
data: {}
|
||||
modules:
|
||||
|
||||
241
tests/rvm/rego/cases/partial_object_rules.yaml
Normal file
241
tests/rvm/rego/cases/partial_object_rules.yaml
Normal file
@@ -0,0 +1,241 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
cases:
|
||||
- note: partial_object_iteration_some_in_object_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_some_in_array_v1
|
||||
data: {}
|
||||
input:
|
||||
arr: ["FOO", "BAR", "BAZ"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[v] if {
|
||||
some _, v in input.arr
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_with_filter_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
k != "BAR"
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_iteration_input_lookup_future_keywords
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import future.keywords.if
|
||||
|
||||
violations[k] if {
|
||||
input.x[k]
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_multiple_bodies_collect_all_keys_v1
|
||||
data: {}
|
||||
input:
|
||||
primary:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
secondary:
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.primary
|
||||
}
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.secondary
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: constant_key_implicit_true_rule_is_complete_v1
|
||||
data: {}
|
||||
input:
|
||||
enabled: true
|
||||
other: false
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
p["x"] if {
|
||||
input.enabled
|
||||
}
|
||||
|
||||
p["x"] if {
|
||||
input.other
|
||||
}
|
||||
query: data.test.p.x
|
||||
want_result: true
|
||||
|
||||
- note: partial_object_duplicate_keys_same_value_are_ok_v1
|
||||
data: {}
|
||||
input:
|
||||
arr: ["FOO", "FOO", "BAR"]
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[v] if {
|
||||
some _, v in input.arr
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_duplicate_keys_different_values_error_v1
|
||||
data: {}
|
||||
input:
|
||||
entries:
|
||||
- k: "FOO"
|
||||
v: 1
|
||||
- k: "FOO"
|
||||
v: 2
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] := v if {
|
||||
some entry in input.entries
|
||||
k := entry.k
|
||||
v := entry.v
|
||||
}
|
||||
query: data.test.violations
|
||||
want_error: "multiple outputs"
|
||||
|
||||
- note: partial_object_and_partial_set_iteration_coexist_v1
|
||||
data: {}
|
||||
input:
|
||||
x:
|
||||
FOO: 1
|
||||
BAR: 2
|
||||
BAZ: 3
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
|
||||
seen contains k if {
|
||||
some k, _ in input.x
|
||||
}
|
||||
|
||||
main := {
|
||||
"seen": seen,
|
||||
"violations": violations,
|
||||
}
|
||||
query: data.test.main
|
||||
want_result:
|
||||
seen:
|
||||
set!: ["BAR", "BAZ", "FOO"]
|
||||
violations:
|
||||
BAR: true
|
||||
BAZ: true
|
||||
FOO: true
|
||||
|
||||
- note: partial_object_key_bound_in_outer_scope_v1
|
||||
data: {}
|
||||
input:
|
||||
outer:
|
||||
FOO: [1, 2]
|
||||
BAR: [3]
|
||||
BAZ: []
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
violations[k] if {
|
||||
some k, arr in input.outer
|
||||
some _ in arr
|
||||
}
|
||||
query: data.test.violations
|
||||
want_result:
|
||||
BAR: true
|
||||
FOO: true
|
||||
|
||||
- note: complete_rule_same_value_definitions_still_work_v1
|
||||
data: {}
|
||||
input:
|
||||
role: "superuser"
|
||||
modules:
|
||||
- |
|
||||
package test
|
||||
import rego.v1
|
||||
|
||||
allowed if {
|
||||
input.role == "admin"
|
||||
}
|
||||
|
||||
allowed if {
|
||||
input.role == "superuser"
|
||||
}
|
||||
query: data.test.allowed
|
||||
want_result: true
|
||||
@@ -6,9 +6,9 @@
|
||||
# Covers dynamic keys, collisions, non-string keys, and template validation
|
||||
|
||||
cases:
|
||||
- note: object_key_collision_overwrite
|
||||
description: Setting same key twice should overwrite the value
|
||||
example_rego: "{\"key\": 1, \"key\": 2}"
|
||||
- note: object_key_collision_conflict
|
||||
description: Setting same key twice with different values should raise a rule output conflict
|
||||
example_rego: "p[\"key\"] = value { value := [1, 2][_] }"
|
||||
literals:
|
||||
- {}
|
||||
- "key"
|
||||
@@ -26,9 +26,31 @@ cases:
|
||||
- "Load { dest: 2, literal_idx: 2 }" # value 1
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Load { dest: 3, literal_idx: 3 }" # value 2
|
||||
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Overwrite
|
||||
- "ObjectSet { obj: 0, key: 1, value: 3 }" # Conflict
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"key": 2}
|
||||
want_error: "multiple outputs"
|
||||
|
||||
- note: object_key_duplicate_same_value
|
||||
description: Setting same key twice with the same value should succeed
|
||||
example_rego: "p[\"key\"] := 1 if { some _ in [0, 1] }"
|
||||
literals:
|
||||
- {}
|
||||
- "key"
|
||||
- 1
|
||||
instruction_params:
|
||||
object_create_params:
|
||||
- dest: 0
|
||||
template_literal_idx: 0
|
||||
literal_key_fields: []
|
||||
fields: []
|
||||
instructions:
|
||||
- "ObjectCreate { params_index: 0 }"
|
||||
- "Load { dest: 1, literal_idx: 1 }" # key
|
||||
- "Load { dest: 2, literal_idx: 2 }" # value
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "ObjectSet { obj: 0, key: 1, value: 2 }"
|
||||
- "Return { value: 0 }"
|
||||
want_result: {"key": 1}
|
||||
|
||||
- note: object_dynamic_key_generation
|
||||
description: Generate object keys dynamically from loop iteration
|
||||
|
||||
Reference in New Issue
Block a user