diff --git a/Cargo.toml b/Cargo.toml index 09cdd2a7..a9744f5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } @@ -42,7 +43,6 @@ 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} @@ -50,7 +50,7 @@ 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] diff --git a/src/builtins/deprecated.rs b/src/builtins/deprecated.rs index df139397..f3c71346 100644 --- a/src/builtins/deprecated.rs +++ b/src/builtins/deprecated.rs @@ -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; @@ -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> = { @@ -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 }; diff --git a/src/builtins/graph.rs b/src/builtins/graph.rs new file mode 100644 index 00000000..0335512a --- /dev/null +++ b/src/builtins/graph.rs @@ -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], args: &[Value], strict: bool) -> Result { + let name = "graph.reachable"; + ensure_args_count(span, name, params, args, 2)?; + + let graph = ensure_object(name, ¶ms[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, + visited: &mut BTreeSet, + node: &Value, + path: &mut Vec, + paths: &mut BTreeSet, +) -> 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], + args: &[Value], + strict: bool, +) -> Result { + let name = "graph.reachable_paths"; + ensure_args_count(span, name, params, args, 2)?; + + let graph = ensure_object(name, ¶ms[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, paths: &mut Vec) -> 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], args: &[Value], _strict: bool) -> Result { + 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)) +} diff --git a/src/builtins/mod.rs b/src/builtins/mod.rs index 4d58c14b..750470d6 100644 --- a/src/builtins/mod.rs +++ b/src/builtins/mod.rs @@ -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")] @@ -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); @@ -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); diff --git a/src/interpreter.rs b/src/interpreter.rs index e67cbc4c..bf55d483 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -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) } diff --git a/src/utils.rs b/src/utils.rs index 4670fde1..68e36636 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -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() {