Skip to content

Commit b852ee0

Browse files
committed
disambiguate empty sets from empty maps
according to the grammar, `{}` could either mean *empty set* or *empty map*. Due to how the parser was written, it was always an empty set, and there was no way to have an empty map literal. This is an issue because empty maps would still be displayed as `{}`, and maps have a different api than sets, resulting in evaluation errors. This commit introduces `{,}` as the empty set literal, while `{}` is the empty map. The goal here is to be consistent with JSON, the reason why we chose the current syntax for arrays and maps.
1 parent fc0d069 commit b852ee0

File tree

8 files changed

+52
-12
lines changed

8 files changed

+52
-12
lines changed

biscuit-auth/examples/testcases.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1378,6 +1378,9 @@ fn expressions(target: &str, root: &KeyPair, test: bool) -> TestResult {
13781378
check if {1, 2, 3}.intersection({1, 2}).contains(1);
13791379
// chained method calls with unary method
13801380
check if {1, 2, 3}.intersection({1, 2}).length() === 2;
1381+
1382+
// empty set literal
1383+
check if {,}.length() === 0;
13811384
"#)
13821385
.build_with_rng(&root, SymbolTable::default(), &mut rng)
13831386
.unwrap();

biscuit-auth/samples/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1321,6 +1321,7 @@ check if {1, 2}.intersection({2, 3}) === {2};
13211321
check if {1, 2}.union({2, 3}) === {1, 2, 3};
13221322
check if {1, 2, 3}.intersection({1, 2}).contains(1);
13231323
check if {1, 2, 3}.intersection({1, 2}).length() === 2;
1324+
check if {,}.length() === 0;
13241325
```
13251326

13261327
### validation
@@ -1331,7 +1332,7 @@ allow if true;
13311332
```
13321333

13331334
revocation ids:
1334-
- `d0420227266e3583a42dfaa0e38550d99f681d150dd18856f3af9a697bc9c5c8bf06b4b0fe5b9df0377d1b963574e2fd210a0a76a8b0756a65f640c602bebd07`
1335+
- `fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505`
13351336

13361337
authorizer world:
13371338
```
@@ -1372,6 +1373,7 @@ World {
13721373
"check if true",
13731374
"check if true === true",
13741375
"check if {\"abc\", \"def\"}.contains(\"abc\")",
1376+
"check if {,}.length() === 0",
13751377
"check if {1, 2, 3}.intersection({1, 2}).contains(1)",
13761378
"check if {1, 2, 3}.intersection({1, 2}).length() === 2",
13771379
"check if {1, 2} === {1, 2}",

biscuit-auth/samples/samples.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,7 +1276,7 @@
12761276
],
12771277
"public_keys": [],
12781278
"external_key": null,
1279-
"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",
1279+
"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",
12801280
"version": 3
12811281
}
12821282
],
@@ -1317,6 +1317,7 @@
13171317
"check if true",
13181318
"check if true === true",
13191319
"check if {\"abc\", \"def\"}.contains(\"abc\")",
1320+
"check if {,}.length() === 0",
13201321
"check if {1, 2, 3}.intersection({1, 2}).contains(1)",
13211322
"check if {1, 2, 3}.intersection({1, 2}).length() === 2",
13221323
"check if {1, 2} === {1, 2}",
@@ -1339,7 +1340,7 @@
13391340
},
13401341
"authorizer_code": "allow if true;\n",
13411342
"revocation_ids": [
1342-
"d0420227266e3583a42dfaa0e38550d99f681d150dd18856f3af9a697bc9c5c8bf06b4b0fe5b9df0377d1b963574e2fd210a0a76a8b0756a65f640c602bebd07"
1343+
"fa358e4e3bea896415b1859e6cd347e64e1918fb86e31ae3fe208628321576a47f7a269760357e291c827ec9cbe322074f6860a546207a64e133c83a214bb505"
13431344
]
13441345
}
13451346
}
34 Bytes
Binary file not shown.

biscuit-auth/src/datalog/symbol.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,15 @@ impl SymbolTable {
198198
}
199199
}
200200
Term::Set(s) => {
201-
let terms = s
202-
.iter()
203-
.map(|term| self.print_term(term))
204-
.collect::<Vec<_>>();
205-
format!("{{{}}}", terms.join(", "))
201+
if s.is_empty() {
202+
"{,}".to_string()
203+
} else {
204+
let terms = s
205+
.iter()
206+
.map(|term| self.print_term(term))
207+
.collect::<Vec<_>>();
208+
format!("{{{}}}", terms.join(", "))
209+
}
206210
}
207211
Term::Null => "null".to_string(),
208212
Term::Array(a) => {

biscuit-auth/src/token/builder.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,4 +333,8 @@ check if true trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc
333333
})
334334
);
335335
}
336+
#[test]
337+
fn empty_set_display() {
338+
assert_eq!(Term::Set(BTreeSet::new()).to_string(), "{,}");
339+
}
336340
}

biscuit-auth/src/token/builder/term.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -359,8 +359,12 @@ impl fmt::Display for Term {
359359
}
360360
}
361361
Term::Set(s) => {
362-
let terms = s.iter().map(|term| term.to_string()).collect::<Vec<_>>();
363-
write!(f, "{{{}}}", terms.join(", "))
362+
if s.is_empty() {
363+
write!(f, "{{,}}")
364+
} else {
365+
let terms = s.iter().map(|term| term.to_string()).collect::<Vec<_>>();
366+
write!(f, "{{{}}}", terms.join(", "))
367+
}
364368
}
365369
Term::Parameter(s) => {
366370
write!(f, "{{{}}}", s)

biscuit-parser/src/parser.rs

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -864,8 +864,16 @@ fn null(i: &str) -> IResult<&str, builder::Term, Error> {
864864
}
865865

866866
fn set(i: &str) -> IResult<&str, builder::Term, Error> {
867+
alt((empty_set, non_empty_set))(i)
868+
}
869+
870+
fn empty_set(i: &str) -> IResult<&str, builder::Term, Error> {
871+
tag("{,}")(i).map(|(i, _)| (i, builder::set(BTreeSet::new())))
872+
}
873+
874+
fn non_empty_set(i: &str) -> IResult<&str, builder::Term, Error> {
867875
let (i, _) = preceded(space0, char('{'))(i)?;
868-
let (i, mut list) = cut(separated_list0(preceded(space0, char(',')), term_in_set))(i)?;
876+
let (i, mut list) = cut(separated_list1(preceded(space0, char(',')), term_in_set))(i)?;
869877

870878
let mut set = BTreeSet::new();
871879

@@ -960,7 +968,7 @@ fn term(i: &str) -> IResult<&str, builder::Term, Error> {
960968
preceded(
961969
space0,
962970
alt((
963-
parameter, string, date, variable, integer, bytes, boolean, null, set, array, parse_map,
971+
parameter, string, date, variable, integer, bytes, boolean, null, array, parse_map, set,
964972
)),
965973
)(i)
966974
}
@@ -2659,4 +2667,18 @@ mod tests {
26592667
))
26602668
);
26612669
}
2670+
2671+
#[test]
2672+
fn empty_set_map() {
2673+
use builder::{map, set};
2674+
2675+
assert_eq!(
2676+
super::expr("{,}").map(|(i, o)| (i, o.opcodes())),
2677+
Ok(("", vec![Op::Value(set(Default::default()))],))
2678+
);
2679+
assert_eq!(
2680+
super::expr("{}").map(|(i, o)| (i, o.opcodes())),
2681+
Ok(("", vec![Op::Value(map(Default::default()))],))
2682+
);
2683+
}
26622684
}

0 commit comments

Comments
 (0)