Skip to content

Commit

Permalink
implement isEmpty method
Browse files Browse the repository at this point in the history
Signed-off-by: Craig Disselkoen <[email protected]>
  • Loading branch information
cdisselkoen committed Dec 5, 2024
1 parent 2900ee4 commit 57b6db9
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 56 deletions.
1 change: 1 addition & 0 deletions cedar-policy-cli/protobuf_schema/AST.proto
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ message Expr {
enum Op {
Not = 0;
Neg = 1;
IsEmpty = 2;
}
}

Expand Down
1 change: 1 addition & 0 deletions cedar-policy-core/protobuf_schema/AST.proto
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ message Expr {
enum Op {
Not = 0;
Neg = 1;
IsEmpty = 2;
}
}

Expand Down
21 changes: 21 additions & 0 deletions cedar-policy-core/src/ast/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,10 @@ impl<T> Expr<T> {
ExprKind::UnaryApp {
op: UnaryOp::Not, ..
} => Some(Type::Bool),
ExprKind::UnaryApp {
op: UnaryOp::IsEmpty,
..
} => Some(Type::Bool),
ExprKind::BinaryApp {
op: BinaryOp::Add | BinaryOp::Mul | BinaryOp::Sub,
..
Expand Down Expand Up @@ -525,6 +529,11 @@ impl Expr {
ExprBuilder::new().contains_any(e1, e2)
}

/// Create a `isEmpty` expression. Argument must evaluate to Set type
pub fn is_empty(e: Expr) -> Self {
ExprBuilder::new().is_empty(e)
}

/// Create a `getTag` expression.
/// `expr` must evaluate to Entity type, `tag` must evaluate to String type.
pub fn get_tag(expr: Expr, tag: Expr) -> Self {
Expand Down Expand Up @@ -1396,6 +1405,14 @@ impl<T> ExprBuilder<T> {
})
}

/// Create an 'is_empty' expression. Argument must evaluate to Set type
pub fn is_empty(self, expr: Expr<T>) -> Expr<T> {
self.with_expr_kind(ExprKind::UnaryApp {
op: UnaryOp::IsEmpty,
arg: Arc::new(expr),
})
}

/// Create a 'getTag' expression.
/// `expr` must evaluate to Entity type, `tag` must evaluate to String type.
pub fn get_tag(self, expr: Expr<T>, tag: Expr<T>) -> Expr<T> {
Expand Down Expand Up @@ -2246,6 +2263,10 @@ mod test {
ExprBuilder::with_data(1).contains_any(temp.clone(), temp.clone()),
Expr::contains_any(Expr::val(1), Expr::val(1)),
),
(
ExprBuilder::with_data(1).is_empty(temp.clone()),
Expr::is_empty(Expr::val(1)),
),
(
ExprBuilder::with_data(1).set([temp.clone()]),
Expr::set([Expr::val(1)]),
Expand Down
25 changes: 16 additions & 9 deletions cedar-policy-core/src/ast/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,20 @@ pub enum UnaryOp {
///
/// Argument must have Long type
Neg,
/// isEmpty test for sets
///
/// Argument must have Set type
IsEmpty,
}

impl std::fmt::Display for UnaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryOp::Not => write!(f, "!_"),
UnaryOp::Neg => write!(f, "-_"),
UnaryOp::IsEmpty => write!(f, "isEmpty"),
}
}
}

#[cfg(feature = "protobufs")]
Expand All @@ -40,6 +54,7 @@ impl From<&proto::expr::unary_app::Op> for UnaryOp {
match v {
proto::expr::unary_app::Op::Not => UnaryOp::Not,
proto::expr::unary_app::Op::Neg => UnaryOp::Neg,
proto::expr::unary_app::Op::IsEmpty => UnaryOp::IsEmpty,
}
}
}
Expand All @@ -50,6 +65,7 @@ impl From<&UnaryOp> for proto::expr::unary_app::Op {
match v {
UnaryOp::Not => proto::expr::unary_app::Op::Not,
UnaryOp::Neg => proto::expr::unary_app::Op::Neg,
UnaryOp::IsEmpty => proto::expr::unary_app::Op::IsEmpty,
}
}
}
Expand Down Expand Up @@ -124,15 +140,6 @@ pub enum BinaryOp {
HasTag,
}

impl std::fmt::Display for UnaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnaryOp::Not => write!(f, "!_"),
UnaryOp::Neg => write!(f, "-_"),
}
}
}

impl std::fmt::Display for BinaryOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down
89 changes: 53 additions & 36 deletions cedar-policy-core/src/est.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2220,6 +2220,7 @@ mod test {
principal.owners.contains("foo")
&& principal.owners.containsAny([1, Linux::Group::"sudoers"])
&& [2+3, "spam"].containsAll(resource.foos)
&& context.violations.isEmpty()
};
"#;
let cst = parser::text_to_cst::parse_policy(policy)
Expand Down Expand Up @@ -2247,66 +2248,82 @@ mod test {
"left": {
"&&": {
"left": {
"contains": {
"&&": {
"left": {
".": {
"contains": {
"left": {
"Var": "principal"
".": {
"left": {
"Var": "principal"
},
"attr": "owners"
}
},
"attr": "owners"
"right": {
"Value": "foo"
}
}
},
"right": {
"Value": "foo"
"containsAny": {
"left": {
".": {
"left": {
"Var": "principal"
},
"attr": "owners"
}
},
"right": {
"Set": [
{ "Value": 1 },
{ "Value": {
"__entity": {
"type": "Linux::Group",
"id": "sudoers"
}
} }
]
}
}
}
}
},
"right": {
"containsAny": {
"containsAll": {
"left": {
"Set": [
{ "+": {
"left": {
"Value": 2
},
"right": {
"Value": 3
}
} },
{ "Value": "spam" },
]
},
"right": {
".": {
"left": {
"Var": "principal"
"Var": "resource"
},
"attr": "owners"
"attr": "foos"
}
},
"right": {
"Set": [
{ "Value": 1 },
{ "Value": {
"__entity": {
"type": "Linux::Group",
"id": "sudoers"
}
} }
]
}
}
}
}
},
"right": {
"containsAll": {
"left": {
"Set": [
{ "+": {
"left": {
"Value": 2
},
"right": {
"Value": 3
}
} },
{ "Value": "spam" },
]
},
"right": {
"isEmpty": {
"arg": {
".": {
"left": {
"Var": "resource"
"Var": "context"
},
"attr": "foos"
"attr": "violations"
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions cedar-policy-core/src/est/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,12 @@ pub enum ExprNoExt {
/// Right-hand argument (inside the `()`)
right: Arc<Expr>,
},
/// `isEmpty()`
#[serde(rename = "isEmpty")]
IsEmpty {
/// Argument
arg: Arc<Expr>,
},
/// `getTag()`
#[serde(rename = "getTag")]
GetTag {
Expand Down Expand Up @@ -557,6 +563,11 @@ impl Expr {
})
}

/// `arg.isEmpty()`
pub fn is_empty(arg: Arc<Expr>) -> Self {
Expr::ExprNoExt(ExprNoExt::IsEmpty { arg })
}

/// `left.getTag(right)`
pub fn get_tag(left: Arc<Expr>, right: Expr) -> Self {
Expr::ExprNoExt(ExprNoExt::GetTag {
Expand Down Expand Up @@ -731,6 +742,9 @@ impl Expr {
right: Arc::new((*right).clone().sub_entity_literals(mapping)?),
}))
}
ExprNoExt::IsEmpty { arg } => Ok(Expr::ExprNoExt(ExprNoExt::IsEmpty {
arg: Arc::new((*arg).clone().sub_entity_literals(mapping)?),
})),
ExprNoExt::GetTag { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::GetTag {
left: Arc::new((*left).clone().sub_entity_literals(mapping)?),
right: Arc::new((*right).clone().sub_entity_literals(mapping)?),
Expand Down Expand Up @@ -884,6 +898,9 @@ impl Expr {
(*left).clone().try_into_ast(id.clone())?,
(*right).clone().try_into_ast(id)?,
)),
Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => {
Ok(ast::Expr::is_empty((*arg).clone().try_into_ast(id)?))
}
Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(ast::Expr::get_tag(
(*left).clone().try_into_ast(id.clone())?,
(*right).clone().try_into_ast(id)?,
Expand Down Expand Up @@ -1011,6 +1028,7 @@ impl<T: Clone> From<ast::Expr<T>> for Expr {
match op {
ast::UnaryOp::Not => Expr::not(arg),
ast::UnaryOp::Neg => Expr::neg(arg),
ast::UnaryOp::IsEmpty => Expr::is_empty(Arc::new(arg)),
}
}
ast::ExprKind::BinaryApp { op, arg1, arg2 } => {
Expand Down Expand Up @@ -1492,6 +1510,10 @@ impl TryFrom<&Node<Option<cst::Member>>> for Expr {
left,
extract_single_argument(args, "containsAny()", &access.loc)?,
)),
"isEmpty" => {
require_zero_arguments(args, "isEmpty()", &access.loc)?;
Either::Right(Expr::is_empty(left))
}
"getTag" => Either::Right(Expr::get_tag(
left,
extract_single_argument(args, "getTag()", &access.loc)?,
Expand Down Expand Up @@ -1560,6 +1582,21 @@ pub fn extract_single_argument<T>(
}
}

/// Return a wrong arity error if the iterator has any elements.
pub fn require_zero_arguments<T>(
args: impl ExactSizeIterator<Item = T>,
fn_name: &'static str,
loc: &Loc,
) -> Result<(), ParseErrors> {
match args.len() {
0 => Ok(()),
n => Err(ParseErrors::singleton(ToASTError::new(
ToASTErrorKind::wrong_arity(fn_name, 0, n),
loc.clone(),
))),
}
}

impl TryFrom<&Node<Option<cst::Literal>>> for Expr {
type Error = ParseErrors;
fn try_from(lit: &Node<Option<cst::Literal>>) -> Result<Expr, ParseErrors> {
Expand Down Expand Up @@ -1800,6 +1837,10 @@ impl std::fmt::Display for ExprNoExt {
maybe_with_parens(f, left)?;
write!(f, ".containsAny({right})")
}
ExprNoExt::IsEmpty { arg } => {
maybe_with_parens(f, arg)?;
write!(f, ".isEmpty()")
}
ExprNoExt::GetTag { left, right } => {
maybe_with_parens(f, left)?;
write!(f, ".getTag({right})")
Expand Down Expand Up @@ -1924,6 +1965,7 @@ fn maybe_with_parens(f: &mut std::fmt::Formatter<'_>, expr: &Expr) -> std::fmt::
Expr::ExprNoExt(ExprNoExt::Contains { .. }) |
Expr::ExprNoExt(ExprNoExt::ContainsAll { .. }) |
Expr::ExprNoExt(ExprNoExt::ContainsAny { .. }) |
Expr::ExprNoExt(ExprNoExt::IsEmpty { .. }) |
Expr::ExprNoExt(ExprNoExt::GetAttr { .. }) |
Expr::ExprNoExt(ExprNoExt::HasAttr { .. }) |
Expr::ExprNoExt(ExprNoExt::GetTag { .. }) |
Expand Down
Loading

0 comments on commit 57b6db9

Please sign in to comment.