use crate::ast::*;
peg::parser! {
grammar policy_parser() for str {
rule _ = [' ' | '\t' | '\n']*
pub rule expr() -> Expression
= quiet!{_ e:expr_inner() _ { e }}
/ expected!("expression")
rule expr_inner() -> Expression = precedence!{
x:(@) _ "or" _ y:@ { Expression::Or(Box::new(x), Box::new(y)) }
--
x:(@) _ "and" _ y:@ { Expression::And(Box::new(x), Box::new(y)) }
--
"not" e:@ { Expression::Not(Box::new(e)) }
e:atom() { e }
}
rule atom() -> Expression
= quiet!{_ c:atom_inner() _ { c }}
/ expected!("check or opening parenthesis")
rule atom_inner() -> Expression
= "@" { Expression::Const(true) }
/ "!" { Expression::Const(false) }
/ "(" e:expr() ")" { e }
/ l:check_lhs() ":" r:check_rhs() { Expression::Check(l, r) }
rule check_lhs() -> LeftHandSide
= "'" s:check_lhs_inner() "'" { LeftHandSide::Literal(s) }
/ "\"" s:check_lhs_inner() "\"" { LeftHandSide::Literal(s) }
/ s:check_lhs_inner() { LeftHandSide::Identifier(s) }
rule check_lhs_inner() -> String
= s:$([^' ' | '\t' | '\n' | ':' | '\'' | '"' | '\\']+) { s.to_owned() }
rule check_rhs() -> String
= s:$([^' ' | '\t' | '\n']+) { s.to_owned() }
}
}
pub(crate) type InternalParseError = peg::error::ParseError<peg::str::LineCol>;
pub(crate) fn parse_expression(input: &str) -> Result<Expression, InternalParseError> {
policy_parser::expr(input)
}
#[cfg(test)]
mod tests {
use super::parse_expression;
use crate::ast::build::*;
#[test]
fn test_basic() {
assert_eq!(parse_expression("@ and !"), Ok(make_and(true, false)));
assert_eq!(
parse_expression(" @ or ! "),
Ok(make_or(true, false))
);
}
fn assert_all_identical(inputs: &[&'static str]) {
let expr0 = parse_expression(inputs[0]);
for input in inputs.iter() {
let expr = parse_expression(input);
assert_eq!(
expr, expr0,
"left input was {:?}, right input was {:?}",
input, inputs[0]
);
}
}
#[test]
fn test_all_identical() {
assert_all_identical(&[
"( @ ) and ! or @",
"@ and ( ! ) or @",
"@ and ! or ( @ )",
"( @ ) and ! or ( @ )",
"@ and ( ! ) or ( @ )",
"( @ ) and ( ! ) or ( @ )",
"( @ and ! ) or @",
"( ( @ ) and ! ) or @",
"( @ and ( ! ) ) or @",
"( ( @ and ! ) ) or @",
"( @ and ! or @ )",
]);
assert_all_identical(&[
"not ( @ ) and ! or @",
"not @ and ( ! ) or @",
"not @ and ! or ( @ )",
"( not @ ) and ! or @",
"( not @ and ! ) or @",
"( not @ and ! or @ )",
]);
assert_all_identical(&[
"( @ ) and not ! or @",
"@ and ( not ! ) or @",
"@ and not ( ! ) or @",
"@ and not ! or ( @ )",
"( @ and not ! ) or @",
"( @ and not ! or @ )",
]);
assert_all_identical(&[
"( @ ) and ! or not @",
"@ and ( ! ) or not @",
"@ and ! or not ( @ )",
"@ and ! or ( not @ )",
"( @ and ! ) or not @",
"( @ and ! or not @ )",
]);
}
#[test]
fn test_parsing_of_checks() {
let input = "user_id:%(target.user_id)s and role:compute:get_all";
let lhs = make_check("user_id", "%(target.user_id)s");
let rhs = make_check("role", "compute:get_all");
let parsed = parse_expression(input);
assert_eq!(parsed, Ok(make_and(lhs, rhs)));
assert_eq!(parsed.unwrap().to_string(), input);
let input = "is_admin:True or 'Member':%(role.name)s";
let lhs = make_check("is_admin", "True");
let rhs = make_literal_check("Member", "%(role.name)s");
let parsed = parse_expression(input);
assert_eq!(parsed, Ok(make_or(lhs, rhs)));
assert_eq!(parsed.unwrap().to_string(), input);
let input = "\"Member\":%(role.name)s";
let check = make_literal_check("Member", "%(role.name)s");
let parsed = parse_expression(input);
assert_eq!(parsed, Ok(check));
assert_eq!(parsed.unwrap().to_string(), "'Member':%(role.name)s");
let input = "";
assert!(parse_expression(input).is_err());
let input = "'foo bar':%(role.name)s";
assert!(parse_expression(input).is_err());
for escape_sequence in ["\\n", "\\\\", "\\\"", "\\'"] {
let input = format!("'foo{escape_sequence}bar':%(role.name)s");
assert!(parse_expression(&input).is_err());
}
}
}