Skip to content

Commit

Permalink
OPA Conformance
Browse files Browse the repository at this point in the history
- Disable default features of jsonschema to reduce binary size (microsoft#85)
- graph.reachable, graph.reachable_paths builtins.
- In progress walk builtin
Note: graph.reachable_paths is buggy in upstream OPA and its semantics
  are not well defined.
  open-policy-agent/opa#5871
  open-policy-agent/opa#6128
- Use correct feature for regex
- Ensure that regorus complies with all features disabled.

Signed-off-by: Anand Krishnamoorthi <[email protected]>
  • Loading branch information
anakrish authored Dec 31, 2023
1 parent 84bedda commit 2292774
Show file tree
Hide file tree
Showing 6 changed files with 201 additions and 6 deletions.
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ crypto = ["dep:constant_time_eq", "dep:hmac", "dep:hex", "dep:md-5", "dep:sha1",
deprecated = []
hex = ["dep:data-encoding"]
glob = ["dep:wax"]
graph = []
jsonschema = ["dep:jsonschema"]
regex = ["dep:regex"]
semver = ["dep:semver"]
uuid = ["dep:uuid"]
urlquery = ["dep:url"]
yaml = ["serde_yaml"]
full-opa = ["base64", "base64url", "crypto", "deprecated", "glob", "hex", "jsonschema", "regex", "semver", "uuid", "urlquery", "yaml"]
full-opa = ["base64", "base64url", "crypto", "deprecated", "glob", "graph", "hex", "jsonschema", "regex", "semver", "uuid", "urlquery", "yaml"]

[dependencies]
anyhow = {version = "1.0.66", features = ["backtrace"] }
Expand All @@ -42,15 +43,14 @@ sha1 = {version = "0.10.6", optional = true}
md-5 = {version = "0.10.6", optional = true}

data-encoding = { version = "2.4.0", optional = true }
jsonschema = { version = "0.17.1", optional = true }
scientific = { version = "0.5.2" }

regex = {version = "1.10.2", optional = true}
semver = {version = "1.0.20", optional = true}
wax = { version = "0.6.0", features = [], default-features = false, optional = true }
url = { version = "2.5.0", optional = true }
uuid = { version = "1.6.1", features = ["v4", "fast-rng"], optional = true }

jsonschema = { version = "0.17.1", default-features = false, optional = true }


[dev-dependencies]
Expand Down
6 changes: 4 additions & 2 deletions src/builtins/deprecated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License.

use crate::ast::{Expr, Ref};
use crate::builtins::regex::regex_match;
use crate::builtins::utils::{ensure_args_count, ensure_set};
use crate::builtins::BuiltinFcn;
use crate::lexer::Span;
Expand All @@ -13,6 +12,9 @@ use std::collections::HashMap;
use anyhow::{bail, Result};
use lazy_static::lazy_static;

#[cfg(feature = "regex")]
use crate::builtins::regex::regex_match;

#[rustfmt::skip]
lazy_static! {
pub static ref DEPRECATED: HashMap<&'static str, BuiltinFcn> = {
Expand All @@ -28,7 +30,7 @@ lazy_static! {
m.insert("cast_string", (cast_string, 1));
m.insert("set_diff", (set_diff, 2));

#[cfg(feature = "crypto")]
#[cfg(feature = "regex")]
m.insert("re_match", (regex_match, 2));
m
};
Expand Down
184 changes: 184 additions & 0 deletions src/builtins/graph.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

use crate::ast::{Expr, Ref};
use crate::builtins;
use crate::builtins::utils::{ensure_args_count, ensure_object};
use crate::lexer::Span;
use crate::value::Value;

use std::collections::{BTreeMap, BTreeSet, HashMap};

use anyhow::{bail, Result};

pub fn register(m: &mut HashMap<&'static str, builtins::BuiltinFcn>) {
m.insert("graph.reachable", (reachable, 2));
m.insert("graph.reachable_paths", (reachable_paths, 2));
m.insert("walk", (walk, 1));
}

fn reachable(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
let name = "graph.reachable";
ensure_args_count(span, name, params, args, 2)?;

let graph = ensure_object(name, &params[0], args[0].clone())?;
let mut worklist = vec![];

match &args[1] {
Value::Array(arr) => worklist.extend(arr.iter().cloned()),
Value::Set(set) => worklist.extend(set.iter().cloned()),
_ if strict => bail!(params[1].span().error("initial vertices must be array/set")),
_ => return Ok(Value::Undefined),
}

let mut reachable = BTreeSet::new();
while let Some(v) = worklist.pop() {
if reachable.contains(&v) {
continue;
}

match graph.get(&v) {
Some(Value::Array(arr)) => worklist.extend(arr.iter().cloned()),
Some(Value::Set(set)) => worklist.extend(set.iter().cloned()),
Some(_) => (),
_ => continue,
}

reachable.insert(v);
}

Ok(Value::from_set(reachable))
}

fn visit(
graph: &BTreeMap<Value, Value>,
visited: &mut BTreeSet<Value>,
node: &Value,
path: &mut Vec<Value>,
paths: &mut BTreeSet<Value>,
) -> Result<()> {
if let Value::String(s) = node {
if s.as_ref() == "" {
if !path.is_empty() {
paths.insert(Value::from_array(path.clone()));
}
return Ok(());
}
}

let neighbors = graph.get(node);
if neighbors.is_none() {
// Current node is not valid. Add path as is.
if !path.is_empty() {
paths.insert(Value::from_array(path.clone()));
}
return Ok(());
}

if visited.contains(node) {
paths.insert(Value::from_array(path.clone()));
} else {
path.push(node.clone());
visited.insert(node.clone());
let n = match neighbors {
Some(Value::Array(arr)) => {
for n in arr.iter().rev() {
visit(graph, visited, n, path, paths)?;
}
arr.len()
}
Some(Value::Set(set)) => {
for n in set.iter().rev() {
visit(graph, visited, n, path, paths)?;
}
set.len()
}
Some(&Value::Null) => 0,
_ => bail!(format!("neighbors for node `{node}` must be array/set.")),
};

if n == 0 {
// Current node has no neighbors.
if !path.is_empty() {
paths.insert(Value::from_array(path.clone()));
}
}

visited.remove(node);
path.pop();
}

Ok(())
}

fn reachable_paths(
span: &Span,
params: &[Ref<Expr>],
args: &[Value],
strict: bool,
) -> Result<Value> {
let name = "graph.reachable_paths";
ensure_args_count(span, name, params, args, 2)?;

let graph = ensure_object(name, &params[0], args[0].clone())?;
let mut visited = BTreeSet::new();
let mut path = vec![];
let mut paths = BTreeSet::new();

match &args[1] {
Value::Array(arr) => {
for node in arr.iter() {
visit(&graph, &mut visited, node, &mut path, &mut paths)?;
}
}
Value::Set(set) => {
for node in set.iter() {
visit(&graph, &mut visited, node, &mut path, &mut paths)?;
}
}
_ if strict => bail!(params[1].span().error("initial vertices must be array/set")),
_ => return Ok(Value::Undefined),
}

Ok(Value::from_set(paths))
}

fn walk_visit(path: &mut Vec<Value>, value: &Value, paths: &mut Vec<Value>) -> Result<()> {
{
let path = Value::from_array(path.clone());
paths.push(Value::from_array([path, value.clone()].into()));
}
match value {
Value::Array(arr) => {
for (idx, elem) in arr.iter().enumerate() {
path.push(Value::from(idx));
walk_visit(path, elem, paths)?;
path.pop();
}
}
Value::Set(set) => {
for elem in set.iter() {
path.push(elem.clone());
walk_visit(path, elem, paths)?;
path.pop();
}
}
Value::Object(obj) => {
for (key, value) in obj.iter() {
path.push(key.clone());
walk_visit(path, value, paths)?;
path.pop();
}
}
_ => (),
}
Ok(())
}

fn walk(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
let name = "walk";
ensure_args_count(span, name, params, args, 1)?;
let mut paths = vec![];
walk_visit(&mut vec![], &args[0], &mut paths)?;
Ok(Value::from_array(paths))
}
6 changes: 5 additions & 1 deletion src/builtins/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub mod deprecated;
mod encoding;
#[cfg(feature = "glob")]
mod glob;
#[cfg(feature = "graph")]
mod graph;
pub mod numbers;
mod objects;
#[cfg(feature = "regex")]
Expand Down Expand Up @@ -64,6 +66,9 @@ lazy_static! {
#[cfg(feature = "glob")]
glob::register(&mut m);

#[cfg(feature = "graph")]
graph::register(&mut m);

bitwise::register(&mut m);
conversions::register(&mut m);
//units::register(&mut m);
Expand All @@ -75,7 +80,6 @@ lazy_static! {

#[cfg(feature = "crypto")]
crypto::register(&mut m);
//graphs::register(&mut m);
//graphql::register(&mut m);
//http::register(&mut m);
//net::register(&mut m);
Expand Down
3 changes: 3 additions & 0 deletions src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1785,6 +1785,9 @@ impl Interpreter {
return Ok(Some(builtin));
}

// Mark as used when deprecated feature is not enabled.
std::convert::identity((span, self.allow_deprecated));

Ok(None)
}

Expand Down
2 changes: 2 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ fn get_extra_arg_impl(
} else {
return Ok(None);
}
#[cfg(not(feature = "deprecated"))]
return Ok(None);
}
};
if (n_args as usize) + 1 == params.len() {
Expand Down

0 comments on commit 2292774

Please sign in to comment.