diff --git a/biscuit-auth/examples/testcases.rs b/biscuit-auth/examples/testcases.rs index 482dbb2a..bf5fe1b3 100644 --- a/biscuit-auth/examples/testcases.rs +++ b/biscuit-auth/examples/testcases.rs @@ -1378,6 +1378,9 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult { check if {1, 2, 3}.intersection({1, 2}).contains(1); // chained method calls with unary method check if {1, 2, 3}.intersection({1, 2}).length() === 2; + + // empty set literal + check if {,}.length() === 0; "#) .build_with_rng(&root, SymbolTable::default(), &mut rng) .unwrap(); diff --git a/biscuit-auth/examples/third_party.rs b/biscuit-auth/examples/third_party.rs index f7fe6b72..dcd2bd68 100644 --- a/biscuit-auth/examples/third_party.rs +++ b/biscuit-auth/examples/third_party.rs @@ -1,7 +1,9 @@ +use std::time::Duration; + use biscuit_auth::{ builder::{Algorithm, AuthorizerBuilder, BlockBuilder}, builder_ext::AuthorizerExt, - datalog::SymbolTable, + datalog::{RunLimits, SymbolTable}, Biscuit, KeyPair, }; use rand::{prelude::StdRng, SeedableRng}; @@ -36,6 +38,10 @@ fn main() { let mut authorizer = AuthorizerBuilder::new() .allow_all() + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) .build(&biscuit1) .unwrap(); @@ -44,6 +50,10 @@ fn main() { let mut authorizer = AuthorizerBuilder::new() .allow_all() + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) .build(&biscuit2) .unwrap(); diff --git a/biscuit-auth/samples/README.md b/biscuit-auth/samples/README.md index 0d378295..4a7ad09e 100644 --- a/biscuit-auth/samples/README.md +++ b/biscuit-auth/samples/README.md @@ -1321,6 +1321,7 @@ check if {1, 2}.intersection({2, 3}) === {2}; check if {1, 2}.union({2, 3}) === {1, 2, 3}; check if {1, 2, 3}.intersection({1, 2}).contains(1); check if {1, 2, 3}.intersection({1, 2}).length() === 2; +check if {,}.length() === 0; ``` ### validation @@ -1331,7 +1332,7 @@ allow if true; ``` revocation ids: -- `d0420227266e3583a42dfaa0e38550d99f681d150dd18856f3af9a697bc9c5c8bf06b4b0fe5b9df0377d1b963574e2fd210a0a76a8b0756a65f640c602bebd07` +- `fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505` authorizer world: ``` @@ -1372,6 +1373,7 @@ World { "check if true", "check if true === true", "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", "check if {1, 2, 3}.intersection({1, 2}).contains(1)", "check if {1, 2, 3}.intersection({1, 2}).length() === 2", "check if {1, 2} === {1, 2}", diff --git a/biscuit-auth/samples/samples.json b/biscuit-auth/samples/samples.json index 539d5e18..6a713179 100644 --- a/biscuit-auth/samples/samples.json +++ b/biscuit-auth/samples/samples.json @@ -1276,7 +1276,7 @@ ], "public_keys": [], "external_key": null, - "code": "check if true;\ncheck if !false;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if {1, 2}.contains(2);\ncheck if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z);\ncheck if {false, true}.contains(true);\ncheck if {\"abc\", \"def\"}.contains(\"abc\");\ncheck if {hex:12ab, hex:34de}.contains(hex:34de);\ncheck if {1, 2}.contains({2});\ncheck if {1, 2} === {1, 2};\ncheck if {1, 2}.intersection({2, 3}) === {2};\ncheck if {1, 2}.union({2, 3}) === {1, 2, 3};\ncheck if {1, 2, 3}.intersection({1, 2}).contains(1);\ncheck if {1, 2, 3}.intersection({1, 2}).length() === 2;\n", + "code": "check if true;\ncheck if !false;\ncheck if true === true;\ncheck if false === false;\ncheck if 1 < 2;\ncheck if 2 > 1;\ncheck if 1 <= 2;\ncheck if 1 <= 1;\ncheck if 2 >= 1;\ncheck if 2 >= 2;\ncheck if 3 === 3;\ncheck if 1 + 2 * 3 - 4 / 2 === 5;\ncheck if \"hello world\".starts_with(\"hello\"), \"hello world\".ends_with(\"world\");\ncheck if \"aaabde\".matches(\"a*c?.e\");\ncheck if \"aaabde\".contains(\"abd\");\ncheck if \"aaabde\" === \"aaa\" + \"b\" + \"de\";\ncheck if \"abcD12\" === \"abcD12\";\ncheck if \"abcD12\".length() === 6;\ncheck if \"é\".length() === 2;\ncheck if 2019-12-04T09:46:41Z < 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z > 2019-12-04T09:46:41Z;\ncheck if 2019-12-04T09:46:41Z <= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2019-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z >= 2020-12-04T09:46:41Z;\ncheck if 2020-12-04T09:46:41Z === 2020-12-04T09:46:41Z;\ncheck if hex:12ab === hex:12ab;\ncheck if {1, 2}.contains(2);\ncheck if {2019-12-04T09:46:41Z, 2020-12-04T09:46:41Z}.contains(2020-12-04T09:46:41Z);\ncheck if {false, true}.contains(true);\ncheck if {\"abc\", \"def\"}.contains(\"abc\");\ncheck if {hex:12ab, hex:34de}.contains(hex:34de);\ncheck if {1, 2}.contains({2});\ncheck if {1, 2} === {1, 2};\ncheck if {1, 2}.intersection({2, 3}) === {2};\ncheck if {1, 2}.union({2, 3}) === {1, 2, 3};\ncheck if {1, 2, 3}.intersection({1, 2}).contains(1);\ncheck if {1, 2, 3}.intersection({1, 2}).length() === 2;\ncheck if {,}.length() === 0;\n", "version": 3 } ], @@ -1317,6 +1317,7 @@ "check if true", "check if true === true", "check if {\"abc\", \"def\"}.contains(\"abc\")", + "check if {,}.length() === 0", "check if {1, 2, 3}.intersection({1, 2}).contains(1)", "check if {1, 2, 3}.intersection({1, 2}).length() === 2", "check if {1, 2} === {1, 2}", @@ -1339,7 +1340,7 @@ }, "authorizer_code": "allow if true;\n", "revocation_ids": [ - "d0420227266e3583a42dfaa0e38550d99f681d150dd18856f3af9a697bc9c5c8bf06b4b0fe5b9df0377d1b963574e2fd210a0a76a8b0756a65f640c602bebd07" + "fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505" ] } } diff --git a/biscuit-auth/samples/test017_expressions.bc b/biscuit-auth/samples/test017_expressions.bc index 7be7a4fd..5b28d360 100644 Binary files a/biscuit-auth/samples/test017_expressions.bc and b/biscuit-auth/samples/test017_expressions.bc differ diff --git a/biscuit-auth/src/datalog/symbol.rs b/biscuit-auth/src/datalog/symbol.rs index 996804a8..01c7af7c 100644 --- a/biscuit-auth/src/datalog/symbol.rs +++ b/biscuit-auth/src/datalog/symbol.rs @@ -198,11 +198,15 @@ impl SymbolTable { } } Term::Set(s) => { - let terms = s - .iter() - .map(|term| self.print_term(term)) - .collect::>(); - format!("{{{}}}", terms.join(", ")) + if s.is_empty() { + "{,}".to_string() + } else { + let terms = s + .iter() + .map(|term| self.print_term(term)) + .collect::>(); + format!("{{{}}}", terms.join(", ")) + } } Term::Null => "null".to_string(), Term::Array(a) => { diff --git a/biscuit-auth/src/token/authorizer.rs b/biscuit-auth/src/token/authorizer.rs index 077cfafd..8c96d305 100644 --- a/biscuit-auth/src/token/authorizer.rs +++ b/biscuit-auth/src/token/authorizer.rs @@ -1093,7 +1093,7 @@ mod tests { ) .unwrap() .limits(AuthorizerLimits { - max_time: Duration::from_millis(10), //Set 10 milliseconds as the maximum time allowed for the authorization due to "cheap" worker on GitHub Actions + max_time: Duration::from_secs(10), //Set 10 seconds as the maximum time allowed for the authorization due to "cheap" worker on GitHub Actions ..Default::default() }) .build(&biscuit2) diff --git a/biscuit-auth/src/token/builder.rs b/biscuit-auth/src/token/builder.rs index aa80194a..2f33ea63 100644 --- a/biscuit-auth/src/token/builder.rs +++ b/biscuit-auth/src/token/builder.rs @@ -333,4 +333,8 @@ check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc }) ); } + #[test] + fn empty_set_display() { + assert_eq!(Term::Set(BTreeSet::new()).to_string(), "{,}"); + } } diff --git a/biscuit-auth/src/token/builder/term.rs b/biscuit-auth/src/token/builder/term.rs index e1e6e4e7..a8459e4c 100644 --- a/biscuit-auth/src/token/builder/term.rs +++ b/biscuit-auth/src/token/builder/term.rs @@ -359,8 +359,12 @@ impl fmt::Display for Term { } } Term::Set(s) => { - let terms = s.iter().map(|term| term.to_string()).collect::>(); - write!(f, "{{{}}}", terms.join(", ")) + if s.is_empty() { + write!(f, "{{,}}") + } else { + let terms = s.iter().map(|term| term.to_string()).collect::>(); + write!(f, "{{{}}}", terms.join(", ")) + } } Term::Parameter(s) => { write!(f, "{{{}}}", s) diff --git a/biscuit-auth/src/token/mod.rs b/biscuit-auth/src/token/mod.rs index 6a963200..85800076 100644 --- a/biscuit-auth/src/token/mod.rs +++ b/biscuit-auth/src/token/mod.rs @@ -1384,6 +1384,10 @@ mod tests { .check("check if bytes($0), { hex:00000000, hex:0102AB }.contains($0)") .unwrap() .allow_all() + .limits(AuthorizerLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) .build(&biscuit2) .unwrap(); diff --git a/biscuit-auth/tests/macros.rs b/biscuit-auth/tests/macros.rs index 1696724f..9b0079e3 100644 --- a/biscuit-auth/tests/macros.rs +++ b/biscuit-auth/tests/macros.rs @@ -75,7 +75,13 @@ fn authorizer_macro() { "# ); - let authorizer = b.build_unauthenticated().unwrap(); + let authorizer = b + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) + .build_unauthenticated() + .unwrap(); assert_eq!( authorizer.dump_code(), r#"appended(true); @@ -95,6 +101,10 @@ allow if true; #[test] fn authorizer_macro_trailing_comma() { let a = authorizer!(r#"fact("test", {my_key});"#, my_key = "my_value",) + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) .build_unauthenticated() .unwrap(); assert_eq!( @@ -261,6 +271,10 @@ fn json() { $value.get("id") == $id, $value.get("roles").contains("admin");"# ) + .limits(RunLimits { + max_time: Duration::from_secs(10), + ..Default::default() + }) .build(&biscuit) .unwrap(); assert_eq!( diff --git a/biscuit-parser/src/builder.rs b/biscuit-parser/src/builder.rs index 54646234..27c9de07 100644 --- a/biscuit-parser/src/builder.rs +++ b/biscuit-parser/src/builder.rs @@ -622,30 +622,26 @@ pub fn pred>(name: &str, terms: &[I]) -> Predicate { } /// creates a rule -pub fn rule, P: AsRef>( - head_name: &str, - head_terms: &[T], - predicates: &[P], -) -> Rule { +pub fn rule>(head_name: &str, head_terms: &[T], predicates: &[Predicate]) -> Rule { Rule::new( pred(head_name, head_terms), - predicates.iter().map(|p| p.as_ref().clone()).collect(), + predicates.to_vec(), Vec::new(), vec![], ) } /// creates a rule with constraints -pub fn constrained_rule, P: AsRef, E: AsRef>( +pub fn constrained_rule>( head_name: &str, head_terms: &[T], - predicates: &[P], - expressions: &[E], + predicates: &[Predicate], + expressions: &[Expression], ) -> Rule { Rule::new( pred(head_name, head_terms), - predicates.iter().map(|p| p.as_ref().clone()).collect(), - expressions.iter().map(|c| c.as_ref().clone()).collect(), + predicates.to_vec(), + expressions.to_vec(), vec![], ) } diff --git a/biscuit-parser/src/parser.rs b/biscuit-parser/src/parser.rs index 649c65c2..d17d02b1 100644 --- a/biscuit-parser/src/parser.rs +++ b/biscuit-parser/src/parser.rs @@ -202,78 +202,6 @@ pub fn rule_inner(i: &str) -> IResult<&str, builder::Rule, Error> { Ok((i, rule)) } -/* -impl TryFrom<&str> for builder::Fact { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(fact(value).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Rule { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(rule(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Fact { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(fact(s).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Rule { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(rule(s).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Check { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(check(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Check { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(check(s).finish().map(|(_, o)| o)?) - } -} - -impl TryFrom<&str> for builder::Policy { - type Error = error::Token; - - fn try_from(value: &str) -> Result { - Ok(policy(value).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Policy { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(policy(s).finish().map(|(_, o)| o)?) - } -} - -impl FromStr for builder::Predicate { - type Err = error::Token; - - fn from_str(s: &str) -> Result { - Ok(predicate(s).finish().map(|(_, o)| o)?) - } -}*/ fn predicate(i: &str) -> IResult<&str, builder::Predicate, Error> { let (i, _) = space0(i)?; @@ -864,8 +792,16 @@ fn null(i: &str) -> IResult<&str, builder::Term, Error> { } fn set(i: &str) -> IResult<&str, builder::Term, Error> { + alt((empty_set, non_empty_set))(i) +} + +fn empty_set(i: &str) -> IResult<&str, builder::Term, Error> { + tag("{,}")(i).map(|(i, _)| (i, builder::set(BTreeSet::new()))) +} + +fn non_empty_set(i: &str) -> IResult<&str, builder::Term, Error> { let (i, _) = preceded(space0, char('{'))(i)?; - let (i, mut list) = cut(separated_list0(preceded(space0, char(',')), term_in_set))(i)?; + let (i, mut list) = cut(separated_list1(preceded(space0, char(',')), term_in_set))(i)?; let mut set = BTreeSet::new(); @@ -960,7 +896,7 @@ fn term(i: &str) -> IResult<&str, builder::Term, Error> { preceded( space0, alt(( - parameter, string, date, variable, integer, bytes, boolean, null, set, array, parse_map, + parameter, string, date, variable, integer, bytes, boolean, null, array, parse_map, set, )), )(i) } @@ -1327,7 +1263,12 @@ where #[cfg(test)] mod tests { - use crate::builder::{self, array, int, var, Binary, Op, Unary}; + use nom::error::ErrorKind; + + use crate::{ + builder::{self, array, int, var, Binary, CheckKind, Op, Unary}, + parser::Error, + }; #[test] fn name() { @@ -1983,7 +1924,7 @@ mod tests { )) ); } - /* + #[test] fn rule() { assert_eq!( @@ -1996,7 +1937,7 @@ mod tests { &[ builder::pred("resource", &[builder::variable("0")]), builder::pred("operation", &[builder::string("read")]), - ] + ], ) )) ); @@ -2013,9 +1954,7 @@ mod tests { "", builder::constrained_rule( "valid_date", - &[ - builder::string("file1"), - ], + &[builder::string("file1")], &[ builder::pred("time", &[builder::variable("0")]), builder::pred("resource", &[builder::string("file1")]), @@ -2067,9 +2006,9 @@ mod tests { assert_eq!( super::rule("right($0, $test) <- resource($0), operation(\"read\")"), Err( nom::Err::Failure(Error { - input: "right($0, $test)", + input: "right($0, $test) <- resource($0), operation(\"read\")", code: ErrorKind::Satisfy, - message: Some("rule head contains variables that are not used in predicates of the rule's body: $test".to_string()), + message: Some("the rule contains variables that are not bound by predicates in the rule's body: $test".to_string()), })) ); } @@ -2082,6 +2021,7 @@ mod tests { Ok(( "", builder::Check { + kind: builder::CheckKind::One, queries: vec![ builder::rule( "query", @@ -2139,12 +2079,9 @@ mod tests { #[test] fn expression() { use super::Expr; - use crate::datalog::SymbolTable; use builder::{date, int, string, var, Binary, Op, Term}; use std::time::{Duration, SystemTime}; - let mut syms = SymbolTable::new(); - let input = " -1 "; println!("parsing: {}", input); let res = super::expr(input); @@ -2152,8 +2089,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); let input = " $0 <= 2019-12-04T09:46:41+00:00"; println!("parsing: {}", input); @@ -2174,9 +2109,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); - let input = " 1 < $test + 2 "; println!("parsing: {}", input); let res = super::expr(input); @@ -2198,8 +2130,6 @@ mod tests { let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); let input = " 2 < $test && $var2.starts_with(\"test\") && true "; println!("parsing: {}", input); @@ -2209,40 +2139,37 @@ mod tests { Ok(( " ", Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( - Op::Binary(Binary::And), + Op::Binary(Binary::LazyAnd), Box::new(Expr::Binary( Op::Binary(Binary::LessThan), Box::new(Expr::Value(int(2))), Box::new(Expr::Value(var("test"))), )), - Box::new(Expr::Binary( - Op::Binary(Binary::Prefix), - Box::new(Expr::Value(var("var2"))), - Box::new(Expr::Value(string("test"))), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Binary( + Op::Binary(Binary::Prefix), + Box::new(Expr::Value(var("var2"))), + Box::new(Expr::Value(string("test"))), + )) )), )), - Box::new(Expr::Value(Term::Bool(true))), + Box::new(Expr::Closure( + vec![], + Box::new(Expr::Value(Term::Bool(true))), + )) ) )) ); - let ops = res.unwrap().1.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops }.convert(&mut syms); - println!("print: {}", e.print(&syms).unwrap()); - - //panic!(); } #[test] fn parens() { - use crate::datalog::{SymbolTable, TemporarySymbolTable}; use builder::{int, Binary, Op, Unary}; - use std::collections::HashMap; - - let mut syms = SymbolTable::new(); let input = " 1 + 2 * 3 "; println!("parsing: {}", input); @@ -2250,15 +2177,6 @@ mod tests { let ops = res.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops: ops.clone() }.convert(&mut syms); - - let printed = e.print(&syms).unwrap(); - println!("print: {}", e.print(&syms).unwrap()); - let h = HashMap::new(); - let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) - .unwrap(); - println!("evaluates to: {:?}", result); assert_eq!( ops, @@ -2270,8 +2188,6 @@ mod tests { Op::Binary(Binary::Add), ] ); - assert_eq!(&printed, "1 + 2 * 3"); - assert_eq!(result, datalog::Term::Integer(7)); let input = " (1 + 2) * 3 "; println!("parsing: {}", input); @@ -2279,15 +2195,6 @@ mod tests { let ops = res.opcodes(); println!("ops: {:#?}", ops); - let e = builder::Expression { ops: ops.clone() }.convert(&mut syms); - - let printed = e.print(&syms).unwrap(); - println!("print: {}", e.print(&syms).unwrap()); - let h = HashMap::new(); - let result = e - .evaluate(&h, &mut TemporarySymbolTable::new(&syms)) - .unwrap(); - println!("evaluates to: {:?}", result); assert_eq!( ops, @@ -2300,8 +2207,6 @@ mod tests { Op::Binary(Binary::Mul), ] ); - assert_eq!(&printed, "(1 + 2) * 3"); - assert_eq!(result, datalog::Term::Integer(9)); } #[test] @@ -2319,7 +2224,7 @@ mod tests { rule_head($var0) <- fact($var0, $var1), 1 < 2; // line comment - check if 1 == 2; + check if 1 === 2; allow if rule_head("string"); @@ -2361,6 +2266,7 @@ mod tests { let expected_checks = vec![ Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2375,6 +2281,7 @@ mod tests { )], }, Check { + kind: CheckKind::One, queries: vec![ rule("query", empty_terms, &[pred("fact", &[int(5678)])]), constrained_rule( @@ -2392,6 +2299,7 @@ mod tests { ], }, Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2468,7 +2376,7 @@ mod tests { fact2(1234); rule_head($var0) <- fact($var0, $var1), 1 < 2; // line comment - check if 1 == 2; /* + check if 1 === 2; /* other comment */ check if @@ -2504,6 +2412,7 @@ mod tests { let expected_checks = vec![ Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2518,6 +2427,7 @@ mod tests { )], }, Check { + kind: CheckKind::One, queries: vec![ rule("query", empty_terms, &[pred("fact", &[int(5678)])]), constrained_rule( @@ -2535,6 +2445,7 @@ mod tests { ], }, Check { + kind: CheckKind::One, queries: vec![constrained_rule( "query", empty_terms, @@ -2567,8 +2478,7 @@ mod tests { result.checks.drain(..).map(|(_, r)| r).collect::>(), expected_checks ); - }*/ - + } #[test] fn chained_calls() { @@ -2659,4 +2569,18 @@ mod tests { )) ); } + + #[test] + fn empty_set_map() { + use builder::{map, set}; + + assert_eq!( + super::expr("{,}").map(|(i, o)| (i, o.opcodes())), + Ok(("", vec![Op::Value(set(Default::default()))],)) + ); + assert_eq!( + super::expr("{}").map(|(i, o)| (i, o.opcodes())), + Ok(("", vec![Op::Value(map(Default::default()))],)) + ); + } }