feat: Update to OPA v1.2.0 (#373)

Regorus now defaults to rego v1. `import rego.v1` is no longer needed.
Additionally, `future` keywords are automatically imported.

See
https://www.openpolicyagent.org/docs/latest/v0-upgrade/#changes-to-rego-in-opa-v10
to understand the differences between rego v1 and v0.

BREAKING CHANGE:

v0 style policies will error out by default. To enable v0 behavior, call engine.set_rego_v0(true) before
loading policies.

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-03-10 11:56:01 -07:00
committed by GitHub
parent cbd772623a
commit c963e477a3
36 changed files with 244 additions and 91 deletions

View File

@@ -83,8 +83,8 @@ fn parse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Re
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""),
};
@@ -161,8 +161,8 @@ fn parse_bytes(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool)
bail!(span.error("spaces not allowed in resource strings"));
}
let (number_part, suffix) = match string.find(|c: char| c.is_alphabetic()) {
Some(p) => (&string[0..p], &string[p..]),
let (number_part, suffix) = match string.rfind(|c: char| c.is_ascii_digit()) {
Some(p) => (&string[0..p + 1], &string[p + 1..]),
_ => (string, ""),
};

View File

@@ -37,33 +37,37 @@ impl Engine {
modules: vec![],
interpreter: Interpreter::new(),
prepared: false,
rego_v1: false,
rego_v1: true,
}
}
/// Turn rego.v1 on/off for subsequently added policies.
///
/// Explicit import rego.v1 is not needed if set.
/// Enable rego v0.
///
/// Note that regorus now defaults to v1.
/// ```
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
///
/// engine.set_rego_v1(true);
/// // Enable v0 for old style policies.
/// engine.set_rego_v0(true);
///
/// engine.add_policy(
/// "test.rego".to_string(),
/// r#"
/// package test
/// allow if true # if keyword is automatically imported
///
/// allow { # v0 syntax does not require if keyword
/// 1 < 2
/// }
/// "#.to_string())?;
///
/// # Ok(())
/// # }
/// ```
///
pub fn set_rego_v1(&mut self, rego_v1: bool) {
self.rego_v1 = rego_v1;
pub fn set_rego_v0(&mut self, rego_v0: bool) {
self.rego_v1 = !rego_v0;
}
/// Add a policy.
@@ -114,6 +118,8 @@ impl Engine {
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
/// // framework.rego does not conform to v1.
/// engine.set_rego_v0(true);
///
/// let package = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
@@ -139,6 +145,8 @@ impl Engine {
/// # use regorus::*;
/// # fn main() -> anyhow::Result<()> {
/// let mut engine = Engine::new();
/// // framework.rego does not conform to v1.
/// engine.set_rego_v0(true);
///
/// let _ = engine.add_policy_from_file("tests/aci/framework.rego")?;
///
@@ -414,6 +422,7 @@ impl Engine {
/// let mut engine = Engine::new();
///
/// // Add policies
/// engine.set_rego_v0(true);
/// engine.add_policy_from_file("tests/aci/framework.rego")?;
/// engine.add_policy_from_file("tests/aci/api.rego")?;
/// engine.add_policy_from_file("tests/aci/policy.rego")?;
@@ -739,7 +748,7 @@ impl Engine {
/// engine.add_policy(
/// "policy.rego".to_string(),
/// r#"package invalid
/// x = y {
/// x = y if {
/// # y = do_magic(2)
/// do_magic(2, y) # y is supplied as an out parameter.
/// }
@@ -775,7 +784,7 @@ impl Engine {
/// r#"
/// package test # Line 2
///
/// x = y { # Line 4
/// x = y if { # Line 4
/// input.a > 2 # Line 5
/// y = 5 # Line 6
/// }

View File

@@ -19,7 +19,7 @@ pub struct Parser<'source> {
tok: Token,
line: u32,
end: u32,
future_keywords: BTreeMap<String, Span>,
future_keywords: BTreeMap<String, Option<Span>>,
rego_v1: bool,
}
@@ -41,13 +41,13 @@ impl<'source> Parser<'source> {
}
pub fn enable_rego_v1(&mut self) -> Result<()> {
self.turn_on_rego_v1(self.tok.1.clone())
self.turn_on_rego_v1(&None)
}
fn turn_on_rego_v1(&mut self, span: Span) -> Result<()> {
fn turn_on_rego_v1(&mut self, span: &Option<Span>) -> Result<()> {
self.rego_v1 = true;
for kw in FUTURE_KEYWORDS {
self.set_future_keyword(kw, &span)?;
self.set_future_keyword(kw, span)?;
}
Ok(())
}
@@ -97,9 +97,9 @@ impl<'source> Parser<'source> {
}
}
pub fn set_future_keyword(&mut self, kw: &str, span: &Span) -> Result<()> {
match &self.future_keywords.get(kw) {
Some(s) if self.rego_v1 => Err(self.source.error(
pub fn set_future_keyword(&mut self, kw: &str, span: &Option<Span>) -> Result<()> {
match (span, self.future_keywords.get(kw)) {
(Some(span), Some(Some(s))) if self.rego_v1 => Err(self.source.error(
span.line,
span.col,
format!(
@@ -155,11 +155,11 @@ impl<'source> Parser<'source> {
fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result<bool> {
if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" {
match comps.len() - 2 {
1 => self.set_future_keyword(comps[2].text(), &comps[2])?,
1 => self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))?,
0 => {
let span = &comps[1];
for kw in FUTURE_KEYWORDS.iter() {
self.set_future_keyword(kw, span)?;
self.set_future_keyword(kw, &Some(span.clone()))?;
}
}
_ => {
@@ -1709,7 +1709,7 @@ impl<'source> Parser<'source> {
let is_future_kw =
if comps.len() == 2 && comps[0].text() == "rego" && comps[1].text() == "v1" {
self.turn_on_rego_v1(span.clone())?;
self.turn_on_rego_v1(&Some(span.clone()))?;
true
} else {
self.handle_import_future_keywords(&comps)?

View File

@@ -141,6 +141,7 @@ pub fn eval_file(
strict: bool,
) -> Result<(Vec<Value>, Vec<String>)> {
let mut engine: Engine = Engine::new();
engine.set_rego_v0(true);
engine.set_strict_builtin_errors(strict);
engine.set_gather_prints(true);