use super::err::{ParseError, ParseErrors, Ref, RefCreationError, ToASTError};
use super::node::{ASTNode, SourceInfo};
use super::unescape::{to_pattern, to_unescaped_string};
use super::{cst, err};
use crate::ast::{
self, ActionConstraint, CallStyle, EntityReference, EntityType, EntityUID,
ExprConstructionError, PatternElem, PolicySetError, PrincipalConstraint,
PrincipalOrResourceConstraint, ResourceConstraint,
};
use itertools::Either;
use smol_str::SmolStr;
use std::cmp::Ordering;
use std::collections::{BTreeMap, HashSet};
use std::mem;
use std::sync::Arc;
struct ExtStyles<'a> {
functions: HashSet<&'a ast::Name>,
methods: HashSet<&'a str>,
}
lazy_static::lazy_static! {
static ref EXTENSION_STYLES: ExtStyles<'static> = load_styles();
}
fn load_styles() -> ExtStyles<'static> {
let mut functions = HashSet::new();
let mut methods = HashSet::new();
for func in crate::extensions::Extensions::all_available().all_funcs() {
match func.style() {
CallStyle::FunctionStyle => functions.insert(func.name()),
CallStyle::MethodStyle => methods.insert(func.name().basename().as_ref()),
};
}
ExtStyles { functions, methods }
}
impl ASTNode<Option<cst::Policies>> {
pub fn with_generated_policyids(
&self,
) -> Option<impl Iterator<Item = (ast::PolicyID, &ASTNode<Option<cst::Policy>>)>> {
let maybe_policies = self.as_inner();
let policies = maybe_policies?;
Some(
policies
.0
.iter()
.enumerate()
.map(|(count, node)| (ast::PolicyID::from_string(format!("policy{count}")), node)),
)
}
pub fn to_policyset(&self, errs: &mut ParseErrors) -> Option<ast::PolicySet> {
let mut pset = ast::PolicySet::new();
let mut complete_set = true;
for (policy_id, policy) in self.with_generated_policyids()? {
match policy.to_policy_or_template(policy_id, errs) {
Some(Either::Right(template)) => {
if let Err(e) = pset.add_template(template) {
match e {
PolicySetError::Occupied { id } => {
errs.push(ToASTError::DuplicateTemplateId(id))
}
};
complete_set = false
}
}
Some(Either::Left(inline_policy)) => {
if let Err(e) = pset.add_static(inline_policy) {
match e {
PolicySetError::Occupied { id } => {
errs.push(ToASTError::DuplicatePolicyId(id))
}
};
complete_set = false
}
}
None => complete_set = false,
};
}
if complete_set {
Some(pset)
} else {
None
}
}
}
impl ASTNode<Option<cst::Policy>> {
pub fn to_policy_or_template(
&self,
id: ast::PolicyID,
errs: &mut ParseErrors,
) -> Option<Either<ast::StaticPolicy, ast::Template>> {
let t = self.to_policy_template(id, errs)?;
if t.slots().count() == 0 {
ast::StaticPolicy::try_from(t).ok().map(Either::Left)
} else {
Some(Either::Right(t))
}
}
pub fn to_policy(
&self,
id: ast::PolicyID,
errs: &mut ParseErrors,
) -> Option<ast::StaticPolicy> {
let tp = self.to_policy_template(id, errs);
let policy = tp.map(ast::StaticPolicy::try_from);
let new_errs = errs
.iter()
.flat_map(|err| match err {
ParseError::ToAST(ToASTError::SlotsInConditionClause { slot, .. }) => {
Some(ToASTError::UnexpectedTemplate { slot: slot.clone() })
}
_ => None,
})
.collect::<Vec<_>>();
errs.extend(new_errs);
match policy {
Some(Err(ast::UnexpectedSlotError::FoundSlot(slot))) => {
errs.push(ToASTError::UnexpectedTemplate { slot: slot.into() });
None
}
Some(Ok(p)) => Some(p),
None => None,
}
}
pub fn to_policy_template(
&self,
id: ast::PolicyID,
errs: &mut ParseErrors,
) -> Option<ast::Template> {
let (src, maybe_policy) = self.as_inner_pair();
let policy = maybe_policy?;
let mut failure = false;
let maybe_effect = policy.effect.to_effect(errs);
let annotations: BTreeMap<_, _> = policy
.annotations
.iter()
.filter_map(|a| a.to_kv_pair(errs))
.collect();
if annotations.len() != policy.annotations.len() {
failure = true;
errs.push(ToASTError::BadAnnotations)
}
let (maybe_principal, maybe_action, maybe_resource) = policy.extract_head(errs);
let conds: Vec<_> = policy
.conds
.iter()
.filter_map(|c| {
let (e, is_when) = c.to_expr(errs)?;
for slot in e.slots() {
errs.push(ToASTError::SlotsInConditionClause {
slot: slot.clone().into(),
clausetype: if is_when { "when" } else { "unless" },
});
}
Some(e)
})
.collect();
if conds.len() != policy.conds.len() {
failure = true
}
if failure || !errs.is_empty() {
return None;
};
let effect = maybe_effect?;
let principal = maybe_principal?;
let action = maybe_action?;
let resource = maybe_resource?;
Some(construct_template_policy(
id,
annotations,
effect,
principal,
action,
resource,
conds,
src.clone(),
))
}
}
impl cst::Policy {
pub fn extract_head(
&self,
errs: &mut ParseErrors,
) -> (
Option<PrincipalConstraint>,
Option<ActionConstraint>,
Option<ResourceConstraint>,
) {
let mut vars = self.variables.iter().peekable();
let principal = if let Some(head1) = vars.next() {
head1.to_principal_constraint(errs)
} else {
errs.push(ToASTError::MissingScopeConstraint(ast::Var::Principal));
None
};
let action = if let Some(head2) = vars.next() {
head2.to_action_constraint(errs)
} else {
errs.push(ToASTError::MissingScopeConstraint(ast::Var::Action));
None
};
let resource = if let Some(head3) = vars.next() {
head3.to_resource_constraint(errs)
} else {
errs.push(ToASTError::MissingScopeConstraint(ast::Var::Resource));
None
};
if vars.peek().is_some() {
for extra_var in vars {
if let Some(def) = extra_var.as_inner() {
errs.push(ToASTError::ExtraHeadConstraints(def.clone()))
}
}
}
(principal, action, resource)
}
}
impl ASTNode<Option<cst::Annotation>> {
pub fn to_kv_pair(&self, errs: &mut ParseErrors) -> Option<(ast::Id, SmolStr)> {
let maybe_anno = self.as_inner();
let anno = maybe_anno?;
let maybe_key = anno.key.to_valid_ident(errs);
let maybe_value = anno.value.as_valid_string(errs);
let maybe_value = match maybe_value.map(|s| to_unescaped_string(s)).transpose() {
Ok(maybe_value) => maybe_value,
Err(unescape_errs) => {
errs.extend(
unescape_errs
.into_iter()
.map(|e| ParseError::ToAST(e.into())),
);
None
}
};
match (maybe_key, maybe_value) {
(Some(k), Some(v)) => Some((k, v)),
_ => None,
}
}
}
impl ASTNode<Option<cst::Ident>> {
pub fn to_valid_ident(&self, errs: &mut ParseErrors) -> Option<ast::Id> {
let maybe_ident = self.as_inner();
let ident = maybe_ident?;
match ident {
cst::Ident::If
| cst::Ident::True
| cst::Ident::False
| cst::Ident::Then
| cst::Ident::Else
| cst::Ident::In
| cst::Ident::Is
| cst::Ident::Has
| cst::Ident::Like => {
errs.push(ToASTError::ReservedIdentifier(ident.clone()));
None
}
cst::Ident::Invalid(i) => {
errs.push(ToASTError::InvalidIdentifier(i.clone()));
None
}
_ => Some(construct_id(format!("{ident}"))),
}
}
pub(crate) fn to_effect(&self, errs: &mut ParseErrors) -> Option<ast::Effect> {
let maybe_effect = self.as_inner();
let effect = maybe_effect?;
match effect {
cst::Ident::Permit => Some(ast::Effect::Permit),
cst::Ident::Forbid => Some(ast::Effect::Forbid),
_ => {
errs.push(ToASTError::InvalidEffect(effect.clone()));
None
}
}
}
pub(crate) fn to_cond_is_when(&self, errs: &mut ParseErrors) -> Option<bool> {
let maybe_cond = self.as_inner();
let cond = maybe_cond?;
match cond {
cst::Ident::When => Some(true),
cst::Ident::Unless => Some(false),
_ => {
errs.push(ToASTError::InvalidCondition(cond.clone()));
None
}
}
}
fn to_var(&self, errs: &mut ParseErrors) -> Option<ast::Var> {
let maybe_ident = self.as_inner();
if maybe_ident.is_none() && errs.is_empty() {
errs.push(ToASTError::MissingNodeData);
}
match maybe_ident? {
cst::Ident::Principal => Some(ast::Var::Principal),
cst::Ident::Action => Some(ast::Var::Action),
cst::Ident::Resource => Some(ast::Var::Resource),
ident => {
errs.push(ToASTError::InvalidScopeConstraintVariable(ident.clone()));
None
}
}
}
}
impl ast::Id {
fn to_meth(
&self,
e: ast::Expr,
mut args: Vec<ast::Expr>,
errs: &mut ParseErrors,
l: SourceInfo,
) -> Option<ast::Expr> {
let mut adj_args = args.iter_mut().peekable();
match (self.as_ref(), adj_args.next(), adj_args.peek()) {
("contains", Some(a), None) => {
let arg = mem::replace(a, ast::Expr::val(false));
Some(construct_method_contains(e, arg, l))
}
("containsAll", Some(a), None) => {
let arg = mem::replace(a, ast::Expr::val(false));
Some(construct_method_contains_all(e, arg, l))
}
("containsAny", Some(a), None) => {
let arg = mem::replace(a, ast::Expr::val(false));
Some(construct_method_contains_any(e, arg, l))
}
(name, _, _) => {
if EXTENSION_STYLES.methods.contains(&name) {
args.insert(0, e);
Some(construct_ext_meth(name.to_string(), args, l))
} else {
errs.push(ToASTError::InvalidMethodName(name.to_string()));
None
}
}
}
}
}
#[derive(Debug)]
enum PrincipalOrResource {
Principal(PrincipalConstraint),
Resource(ResourceConstraint),
}
impl ASTNode<Option<cst::VariableDef>> {
fn to_principal_constraint(&self, errs: &mut ParseErrors) -> Option<PrincipalConstraint> {
match self.to_principal_or_resource_constraint(ast::Var::Principal, errs)? {
PrincipalOrResource::Principal(p) => Some(p),
PrincipalOrResource::Resource(_) => {
errs.push(ToASTError::IncorrectVariable {
expected: ast::Var::Principal,
got: ast::Var::Resource,
});
None
}
}
}
fn to_resource_constraint(&self, errs: &mut ParseErrors) -> Option<ResourceConstraint> {
match self.to_principal_or_resource_constraint(ast::Var::Resource, errs)? {
PrincipalOrResource::Principal(_) => {
errs.push(ToASTError::IncorrectVariable {
expected: ast::Var::Resource,
got: ast::Var::Principal,
});
None
}
PrincipalOrResource::Resource(r) => Some(r),
}
}
fn to_principal_or_resource_constraint(
&self,
expected: ast::Var,
errs: &mut ParseErrors,
) -> Option<PrincipalOrResource> {
let maybe_vardef = self.as_inner();
let vardef = maybe_vardef?;
let var = vardef.variable.to_var(errs)?;
match vardef.variable.to_var(errs) {
Some(v) if v == var => Some(()),
Some(got) => {
errs.push(ToASTError::IncorrectVariable { expected: var, got });
None
}
None => None,
}?;
if let Some(unused_typename) = vardef.unused_type_name.as_ref() {
unused_typename.to_type_constraint(errs)?;
}
let c = if let Some((op, rel_expr)) = &vardef.ineq {
let eref = rel_expr.to_ref_or_slot(errs, var)?;
match (op, &vardef.entity_type) {
(cst::RelOp::Eq, None) => Some(PrincipalOrResourceConstraint::Eq(eref)),
(cst::RelOp::Eq, Some(_)) => {
errs.push(ToASTError::InvalidIs(err::InvalidIsError::WrongOp(
cst::RelOp::Eq,
)));
None
}
(cst::RelOp::In, None) => Some(PrincipalOrResourceConstraint::In(eref)),
(cst::RelOp::In, Some(entity_type)) => Some(PrincipalOrResourceConstraint::IsIn(
entity_type.to_name(errs)?,
eref,
)),
(op, _) => {
errs.push(ToASTError::InvalidConstraintOperator(*op));
None
}
}
} else if let Some(entity_type) = &vardef.entity_type {
Some(PrincipalOrResourceConstraint::Is(
entity_type.to_name(errs)?,
))
} else {
Some(PrincipalOrResourceConstraint::Any)
}?;
match var {
ast::Var::Principal => {
Some(PrincipalOrResource::Principal(PrincipalConstraint::new(c)))
}
ast::Var::Resource => Some(PrincipalOrResource::Resource(ResourceConstraint::new(c))),
got => {
errs.push(ToASTError::IncorrectVariable { expected, got });
None
}
}
}
fn to_action_constraint(&self, errs: &mut ParseErrors) -> Option<ast::ActionConstraint> {
let maybe_vardef = self.as_inner();
let vardef = maybe_vardef?;
match vardef.variable.to_var(errs) {
Some(ast::Var::Action) => Some(()),
Some(got) => {
errs.push(ToASTError::IncorrectVariable {
expected: ast::Var::Action,
got,
});
None
}
None => None,
}?;
if let Some(typename) = vardef.unused_type_name.as_ref() {
typename.to_type_constraint(errs)?;
}
if vardef.entity_type.is_some() {
errs.push(ToASTError::InvalidIs(err::InvalidIsError::ActionScope));
return None;
}
let action_constraint = if let Some((op, rel_expr)) = &vardef.ineq {
let refs = rel_expr.to_refs(errs, ast::Var::Action)?;
match (op, refs) {
(cst::RelOp::In, OneOrMultipleRefs::Multiple(euids)) => {
Some(ActionConstraint::is_in(euids))
}
(cst::RelOp::In, OneOrMultipleRefs::Single(euid)) => {
Some(ActionConstraint::is_in([euid]))
}
(cst::RelOp::Eq, OneOrMultipleRefs::Single(euid)) => {
Some(ActionConstraint::is_eq(euid))
}
(cst::RelOp::Eq, OneOrMultipleRefs::Multiple(_)) => {
errs.push(ToASTError::InvalidScopeEqualityRHS);
None
}
(op, _) => {
errs.push(ToASTError::InvalidConstraintOperator(*op));
None
}
}
} else {
Some(ActionConstraint::Any)
}?;
match action_constraint_contains_only_action_types(action_constraint) {
Ok(a) => Some(a),
Err(mut id_errs) => {
errs.append(&mut id_errs);
None
}
}
}
}
fn action_constraint_contains_only_action_types(
a: ActionConstraint,
) -> Result<ActionConstraint, ParseErrors> {
match a {
ActionConstraint::Any => Ok(a),
ActionConstraint::In(ref euids) => {
let non_actions = euids
.iter()
.filter(|euid| !euid_has_action_type(euid))
.collect::<Vec<_>>();
if non_actions.is_empty() {
Ok(a)
} else {
Err(non_actions
.into_iter()
.map(|euid| ToASTError::InvalidActionType(euid.as_ref().clone()))
.collect())
}
}
ActionConstraint::Eq(ref euid) => {
if euid_has_action_type(euid) {
Ok(a)
} else {
Err(ParseErrors(vec![ToASTError::InvalidActionType(
euid.as_ref().clone(),
)
.into()]))
}
}
}
}
fn euid_has_action_type(euid: &EntityUID) -> bool {
if let EntityType::Specified(name) = euid.entity_type() {
name.id.as_ref() == "Action"
} else {
false
}
}
impl ASTNode<Option<cst::Cond>> {
fn to_expr(&self, errs: &mut ParseErrors) -> Option<(ast::Expr, bool)> {
let (src, maybe_cond) = self.as_inner_pair();
let cond = maybe_cond?;
let maybe_is_when = cond.cond.to_cond_is_when(errs)?;
let maybe_expr = match &cond.expr {
Some(expr) => expr.to_expr(errs),
None => {
let ident = Some(if maybe_is_when {
cst::Ident::Ident("when".into())
} else {
cst::Ident::Ident("unless".into())
});
errs.push(match cond.cond.as_ref().node {
Some(ident) => ToASTError::EmptyClause(Some(ident.clone())),
None => ToASTError::EmptyClause(ident),
});
None
}
};
maybe_expr.map(|e| {
if maybe_is_when {
(e, true)
} else {
(construct_expr_not(e, src.clone()), false)
}
})
}
}
impl ASTNode<Option<cst::Str>> {
pub(crate) fn as_valid_string(&self, errs: &mut ParseErrors) -> Option<&SmolStr> {
let id = self.as_inner();
let id = id?;
match id {
cst::Str::String(s) => Some(s),
cst::Str::Invalid(s) => {
errs.push(ToASTError::InvalidString(s.to_string()));
None
}
}
}
}
pub(crate) enum ExprOrSpecial<'a> {
Expr(ast::Expr),
Var(ast::Var, SourceInfo),
Name(ast::Name),
StrLit(&'a SmolStr, SourceInfo),
}
impl ExprOrSpecial<'_> {
fn into_expr(self, errs: &mut ParseErrors) -> Option<ast::Expr> {
match self {
Self::Expr(e) => Some(e),
Self::Var(v, l) => Some(construct_expr_var(v, l)),
Self::Name(n) => {
errs.push(ToASTError::ArbitraryVariable(n.to_string().into()));
None
}
Self::StrLit(s, l) => match to_unescaped_string(s) {
Ok(s) => Some(construct_expr_string(s, l)),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
},
}
}
pub(crate) fn into_valid_attr(self, errs: &mut ParseErrors) -> Option<SmolStr> {
match self {
Self::Var(var, _) => Some(construct_string_from_var(var)),
Self::Name(name) => name.into_valid_attr(errs),
Self::StrLit(s, _) => match to_unescaped_string(s) {
Ok(s) => Some(s),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
},
Self::Expr(e) => {
errs.push(ToASTError::InvalidAttribute(e.to_string().into()));
None
}
}
}
fn into_pattern(self, errs: &mut ParseErrors) -> Option<Vec<PatternElem>> {
match self {
Self::StrLit(s, _) => match to_pattern(s) {
Ok(pat) => Some(pat),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
},
Self::Var(var, _) => {
errs.push(ToASTError::InvalidPattern(var.to_string()));
None
}
Self::Name(name) => {
errs.push(ToASTError::InvalidPattern(name.to_string()));
None
}
Self::Expr(e) => {
errs.push(ToASTError::InvalidPattern(e.to_string()));
None
}
}
}
fn into_string_literal(self, errs: &mut ParseErrors) -> Option<SmolStr> {
match self {
Self::StrLit(s, _) => match to_unescaped_string(s) {
Ok(s) => Some(s),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
},
Self::Var(var, _) => {
errs.push(ToASTError::InvalidString(var.to_string()));
None
}
Self::Name(name) => {
errs.push(ToASTError::InvalidString(name.to_string()));
None
}
Self::Expr(e) => {
errs.push(ToASTError::InvalidString(e.to_string()));
None
}
}
}
}
impl ASTNode<Option<cst::Expr>> {
fn to_ref(&self, var: ast::Var, errs: &mut ParseErrors) -> Option<EntityUID> {
self.to_ref_or_refs::<SingleEntity>(errs, var).map(|x| x.0)
}
fn to_ref_or_slot(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<EntityReference> {
self.to_ref_or_refs::<EntityReference>(errs, var)
}
fn to_refs(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<OneOrMultipleRefs> {
self.to_ref_or_refs::<OneOrMultipleRefs>(errs, var)
}
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_expr = self.as_inner();
let expr = &*maybe_expr?.expr;
match expr {
cst::ExprData::Or(o) => o.to_ref_or_refs::<T>(errs, var),
cst::ExprData::If(_, _, _) => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"an `if` expression",
None::<String>,
));
None
}
}
}
pub fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
pub(crate) fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_expr) = self.as_inner_pair();
let expr = &*maybe_expr?.expr;
match expr {
cst::ExprData::Or(or) => or.to_expr_or_special(errs),
cst::ExprData::If(i, t, e) => {
let maybe_guard = i.to_expr(errs);
let maybe_then = t.to_expr(errs);
let maybe_else = e.to_expr(errs);
match (maybe_guard, maybe_then, maybe_else) {
(Some(i), Some(t), Some(e)) => {
Some(ExprOrSpecial::Expr(construct_expr_if(i, t, e, src.clone())))
}
_ => None,
}
}
}
}
}
trait RefKind: Sized {
fn err_str() -> &'static str;
fn create_single_ref(e: EntityUID, errs: &mut ParseErrors) -> Option<Self>;
fn create_multiple_refs(es: Vec<EntityUID>, errs: &mut ParseErrors) -> Option<Self>;
fn create_slot(errs: &mut ParseErrors) -> Option<Self>;
}
struct SingleEntity(pub EntityUID);
impl RefKind for SingleEntity {
fn err_str() -> &'static str {
"an entity uid"
}
fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors) -> Option<Self> {
Some(SingleEntity(e))
}
fn create_multiple_refs(_es: Vec<EntityUID>, errs: &mut ParseErrors) -> Option<Self> {
errs.push(RefCreationError::one_expected(Ref::Single, Ref::Set));
None
}
fn create_slot(errs: &mut ParseErrors) -> Option<Self> {
errs.push(RefCreationError::one_expected(Ref::Single, Ref::Template));
None
}
}
impl RefKind for EntityReference {
fn err_str() -> &'static str {
"an entity uid or matching template slot"
}
fn create_slot(_: &mut ParseErrors) -> Option<Self> {
Some(EntityReference::Slot)
}
fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors) -> Option<Self> {
Some(EntityReference::euid(e))
}
fn create_multiple_refs(_es: Vec<EntityUID>, errs: &mut ParseErrors) -> Option<Self> {
errs.push(RefCreationError::two_expected(
Ref::Single,
Ref::Template,
Ref::Set,
));
None
}
}
#[derive(Debug)]
enum OneOrMultipleRefs {
Single(EntityUID),
Multiple(Vec<EntityUID>),
}
impl RefKind for OneOrMultipleRefs {
fn err_str() -> &'static str {
"an entity uid, set of entity uids, or template slot"
}
fn create_slot(errs: &mut ParseErrors) -> Option<Self> {
errs.push(RefCreationError::two_expected(
Ref::Single,
Ref::Set,
Ref::Template,
));
None
}
fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors) -> Option<Self> {
Some(OneOrMultipleRefs::Single(e))
}
fn create_multiple_refs(es: Vec<EntityUID>, _errs: &mut ParseErrors) -> Option<Self> {
Some(OneOrMultipleRefs::Multiple(es))
}
}
impl ASTNode<Option<cst::Or>> {
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_or) = self.as_inner_pair();
let or = maybe_or?;
let maybe_first = or.initial.to_expr_or_special(errs);
let mut more = or.extended.iter().filter_map(|i| i.to_expr(errs));
let maybe_second = more.next();
let rest: Vec<_> = more.collect();
match (maybe_first, maybe_second, rest.len(), or.extended.len()) {
(f, None, _, 0) => f,
(Some(f), Some(s), r, e) if 1 + r == e => f
.into_expr(errs)
.map(|e| ExprOrSpecial::Expr(construct_expr_or(e, s, rest, src.clone()))),
_ => None,
}
}
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_or = self.as_inner();
let or = maybe_or?;
match or.extended.len() {
0 => or.initial.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a `||` expression",
None::<String>,
));
None
}
}
}
}
impl ASTNode<Option<cst::And>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_and = self.as_inner();
let and = maybe_and?;
match and.extended.len() {
0 => and.initial.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a `&&` expression",
None::<String>,
));
None
}
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_and) = self.as_inner_pair();
let and = maybe_and?;
let maybe_first = and.initial.to_expr_or_special(errs);
let mut more = and.extended.iter().filter_map(|i| i.to_expr(errs));
let maybe_second = more.next();
let rest: Vec<_> = more.collect();
match (maybe_first, maybe_second, rest.len(), and.extended.len()) {
(f, None, _, 0) => f,
(Some(f), Some(s), r, e) if 1 + r == e => f
.into_expr(errs)
.map(|e| ExprOrSpecial::Expr(construct_expr_and(e, s, rest, src.clone()))),
_ => None,
}
}
}
impl ASTNode<Option<cst::Relation>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_rel = self.as_inner();
match maybe_rel? {
cst::Relation::Common { initial, extended } => match extended.len() {
0 => initial.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a binary operator",
None::<String>,
));
None
}
},
cst::Relation::Has { .. } => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a `has` expression",
None::<String>,
));
None
}
cst::Relation::Like { .. } => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a `like` expression",
None::<String>,
));
None
}
cst::Relation::IsIn { .. } => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"an `is` expression",
None::<String>,
));
None
}
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_rel) = self.as_inner_pair();
let rel = maybe_rel?;
match rel {
cst::Relation::Common { initial, extended } => {
let maybe_first = initial.to_expr_or_special(errs);
let mut more = extended
.iter()
.filter_map(|(op, i)| i.to_expr(errs).map(|e| (op, e)));
let maybe_second = more.next();
let _rest: Vec<_> = more.collect();
match (maybe_first, maybe_second, extended.len()) {
(_, _, l) if l > 1 => {
errs.push(ToASTError::AmbiguousOperators);
None
}
(_, None, 1) => None,
(f, None, 0) => f,
(Some(f), Some((op, s)), _) => f
.into_expr(errs)
.map(|e| ExprOrSpecial::Expr(construct_expr_rel(e, *op, s, src.clone()))),
_ => None,
}
}
cst::Relation::Has { target, field } => {
match (
target.to_expr(errs),
field.to_expr_or_special(errs)?.into_valid_attr(errs),
) {
(Some(t), Some(s)) => {
Some(ExprOrSpecial::Expr(construct_expr_has(t, s, src.clone())))
}
_ => None,
}
}
cst::Relation::Like { target, pattern } => {
match (
target.to_expr(errs),
pattern.to_expr_or_special(errs)?.into_pattern(errs),
) {
(Some(t), Some(s)) => {
Some(ExprOrSpecial::Expr(construct_expr_like(t, s, src.clone())))
}
_ => None,
}
}
cst::Relation::IsIn {
target,
entity_type,
in_entity,
} => match (target.to_expr(errs), entity_type.to_name(errs)) {
(Some(t), Some(n)) => match in_entity {
Some(in_entity) => in_entity.to_expr(errs).map(|in_entity| {
ExprOrSpecial::Expr(construct_expr_and(
construct_expr_is(t.clone(), n, src.clone()),
construct_expr_rel(t, cst::RelOp::In, in_entity, src.clone()),
std::iter::empty(),
src.clone(),
))
}),
None => Some(ExprOrSpecial::Expr(construct_expr_is(t, n, src.clone()))),
},
_ => None,
},
}
}
}
impl ASTNode<Option<cst::Add>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_add = self.as_inner();
let add = maybe_add?;
match add.extended.len() {
0 => add.initial.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(T::err_str(), "a `+/-` expression", Some("note that entity types and namespaces cannot use `+` or `-` characters -- perhaps try `_` or `::` instead?")));
None
}
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_add) = self.as_inner_pair();
let add = maybe_add?;
let maybe_first = add.initial.to_expr_or_special(errs);
let more: Vec<(cst::AddOp, _)> = add
.extended
.iter()
.filter_map(|&(op, ref i)| i.to_expr(errs).map(|e| (op, e)))
.collect();
if !more.is_empty() {
Some(ExprOrSpecial::Expr(construct_expr_add(
maybe_first?.into_expr(errs)?,
more,
src.clone(),
)))
} else {
maybe_first
}
}
}
impl ASTNode<Option<cst::Mult>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_mult = self.as_inner();
let mult = maybe_mult?;
match mult.extended.len() {
0 => mult.initial.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"a `*` expression",
None::<String>,
));
None
}
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_mult) = self.as_inner_pair();
let mult = maybe_mult?;
let maybe_first = mult.initial.to_expr_or_special(errs);
let more: Vec<(cst::MultOp, _)> = mult
.extended
.iter()
.filter_map(|&(op, ref i)| i.to_expr(errs).map(|e| (op, e)))
.collect();
if !more.is_empty() {
let first = maybe_first?.into_expr(errs)?;
for (op, _) in &more {
match op {
cst::MultOp::Times => {}
cst::MultOp::Divide => {
errs.push(ToASTError::UnsupportedDivision);
return None;
}
cst::MultOp::Mod => {
errs.push(ToASTError::UnsupportedModulo);
return None;
}
}
}
let (constantints, nonconstantints): (Vec<ast::Expr>, Vec<ast::Expr>) =
std::iter::once(first)
.chain(more.into_iter().map(|(_, e)| e))
.partition(|e| {
matches!(e.expr_kind(), ast::ExprKind::Lit(ast::Literal::Long(_)))
});
let constantints = constantints
.into_iter()
.map(|e| match e.expr_kind() {
ast::ExprKind::Lit(ast::Literal::Long(i)) => *i,
#[allow(clippy::unreachable)]
_ => unreachable!(
"checked it matched ast::ExprKind::Lit(ast::Literal::Long(_)) above"
),
})
.collect::<Vec<i64>>();
if nonconstantints.len() > 1 {
errs.push(ToASTError::NonConstantMultiplication);
None
} else if nonconstantints.is_empty() {
#[allow(clippy::indexing_slicing)]
Some(ExprOrSpecial::Expr(construct_expr_mul(
construct_expr_num(constantints[0], src.clone()),
constantints[1..].iter().copied(),
src.clone(),
)))
} else {
#[allow(clippy::expect_used)]
let nonconstantint: ast::Expr = nonconstantints
.into_iter()
.next()
.expect("already checked that it's not empty");
Some(ExprOrSpecial::Expr(construct_expr_mul(
nonconstantint,
constantints,
src.clone(),
)))
}
} else {
maybe_first
}
}
}
impl ASTNode<Option<cst::Unary>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_unary = self.as_inner();
let unary = maybe_unary?;
match &unary.op {
Some(op) => {
errs.push(ToASTError::wrong_node(
T::err_str(),
format!("a `{op}` expression"),
None::<String>,
));
None
}
None => unary.item.to_ref_or_refs::<T>(errs, var),
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_unary) = self.as_inner_pair();
let unary = maybe_unary?;
let mut maybe_item = || unary.item.to_expr_or_special(errs);
match unary.op {
None => maybe_item(),
Some(cst::NegOp::Bang(0)) => maybe_item(),
Some(cst::NegOp::Dash(0)) => maybe_item(),
Some(cst::NegOp::Bang(n)) => {
let item = maybe_item().and_then(|i| i.into_expr(errs));
if n % 2 == 0 {
item.map(|i| {
ExprOrSpecial::Expr(construct_expr_not(
construct_expr_not(i, src.clone()),
src.clone(),
))
})
} else {
item.map(|i| ExprOrSpecial::Expr(construct_expr_not(i, src.clone())))
}
}
Some(cst::NegOp::Dash(c)) => {
let (last, rc) = if let Some(cst::Literal::Num(n)) = unary.item.to_lit() {
match n.cmp(&(i64::MAX as u64 + 1)) {
Ordering::Equal => (
Some(construct_expr_num(i64::MIN, unary.item.info.clone())),
c - 1,
),
Ordering::Less => (
Some(construct_expr_num(-(*n as i64), unary.item.info.clone())),
c - 1,
),
Ordering::Greater => {
errs.push(ToASTError::IntegerLiteralTooLarge(*n));
(None, 0)
}
}
} else {
(maybe_item().and_then(|i| i.into_expr(errs)), c)
};
(0..rc)
.fold(last, |r, _| r.map(|e| (construct_expr_neg(e, src.clone()))))
.map(ExprOrSpecial::Expr)
}
Some(cst::NegOp::OverBang) => {
errs.push(ToASTError::UnaryOpLimit(ast::UnaryOp::Not));
None
}
Some(cst::NegOp::OverDash) => {
errs.push(ToASTError::UnaryOpLimit(ast::UnaryOp::Neg));
None
}
}
}
}
enum AstAccessor {
Field(ast::Id),
Call(Vec<ast::Expr>),
Index(SmolStr),
}
impl ASTNode<Option<cst::Member>> {
fn to_lit(&self) -> Option<&cst::Literal> {
let m = self.as_ref().node.as_ref()?;
if !m.access.is_empty() {
return None;
}
match m.item.as_ref().node.as_ref()? {
cst::Primary::Literal(l) => l.as_ref().node.as_ref(),
_ => None,
}
}
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_mem = self.as_inner();
let mem = maybe_mem?;
match mem.access.len() {
0 => mem.item.to_ref_or_refs::<T>(errs, var),
_n => {
errs.push(ToASTError::wrong_node(T::err_str(), "a `.` expression", Some("note that entity types and namespaces cannot use `.` characters -- perhaps try `_` or `::` instead?")));
None
}
}
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_mem) = self.as_inner_pair();
let mem = maybe_mem?;
let maybe_prim = mem.item.to_expr_or_special(errs);
let mut accessors: Vec<_> = mem.access.iter().map(|a| a.to_access(errs)).collect();
let mut head = maybe_prim;
let mut tail = &mut accessors[..];
loop {
use AstAccessor::*;
use ExprOrSpecial::*;
match (&mut head, tail) {
(_, []) => break head,
(_, [None, Some(Call(_)), rest @ ..]) => {
head = None;
tail = rest;
}
(_, [None, rest @ ..]) => {
head = None;
tail = rest;
}
(Some(Name(n)), [Some(Call(a)), rest @ ..]) => {
let args = std::mem::take(a);
let nn =
mem::replace(n, ast::Name::unqualified_name(ast::Id::new_unchecked("")));
head = nn.into_func(args, errs, src.clone()).map(Expr);
tail = rest;
}
(Some(Var(v, _)), [Some(Call(_)), rest @ ..]) => {
errs.push(ToASTError::VariableCall(*v));
head = None;
tail = rest;
}
(_, [Some(Call(_)), rest @ ..]) => {
errs.push(ToASTError::ExpressionCall);
head = None;
tail = rest;
}
(None, [Some(Field(_)), Some(Call(_)), rest @ ..]) => {
tail = rest;
}
(Some(Name(n)), [Some(Field(f)), Some(Call(_)), rest @ ..]) => {
errs.push(ToASTError::NoMethods(n.clone(), f.clone()));
head = None;
tail = rest;
}
(Some(Var(v, vl)), [Some(Field(i)), Some(Call(a)), rest @ ..]) => {
let var = mem::replace(v, ast::Var::Principal);
let args = std::mem::take(a);
let id = mem::replace(i, ast::Id::new_unchecked(""));
head = id
.to_meth(construct_expr_var(var, vl.clone()), args, errs, src.clone())
.map(Expr);
tail = rest;
}
(Some(Expr(e)), [Some(Field(i)), Some(Call(a)), rest @ ..]) => {
let args = std::mem::take(a);
let expr = mem::replace(e, ast::Expr::val(false));
let id = mem::replace(i, ast::Id::new_unchecked(""));
head = id.to_meth(expr, args, errs, src.clone()).map(Expr);
tail = rest;
}
(Some(StrLit(s, sl)), [Some(Field(i)), Some(Call(a)), rest @ ..]) => {
let args = std::mem::take(a);
let id = mem::replace(i, ast::Id::new_unchecked(""));
let maybe_expr = match to_unescaped_string(s) {
Ok(s) => Some(construct_expr_string(s, sl.clone())),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
};
head =
maybe_expr.and_then(|e| id.to_meth(e, args, errs, src.clone()).map(Expr));
tail = rest;
}
(None, [Some(Field(_)) | Some(Index(_)), rest @ ..]) => {
tail = rest;
}
(Some(Name(n)), [Some(Field(f)), rest @ ..]) => {
errs.push(ToASTError::InvalidAccess(n.clone(), f.to_string().into()));
head = None;
tail = rest;
}
(Some(Name(n)), [Some(Index(i)), rest @ ..]) => {
errs.push(ToASTError::InvalidIndex(n.clone(), i.clone()));
head = None;
tail = rest;
}
(Some(Var(v, vl)), [Some(Field(i)), rest @ ..]) => {
let var = mem::replace(v, ast::Var::Principal);
let id = mem::replace(i, ast::Id::new_unchecked(""));
head = Some(Expr(construct_expr_attr(
construct_expr_var(var, vl.clone()),
id.to_smolstr(),
src.clone(),
)));
tail = rest;
}
(Some(Expr(e)), [Some(Field(i)), rest @ ..]) => {
let expr = mem::replace(e, ast::Expr::val(false));
let id = mem::replace(i, ast::Id::new_unchecked(""));
head = Some(Expr(construct_expr_attr(
expr,
id.to_smolstr(),
src.clone(),
)));
tail = rest;
}
(Some(StrLit(s, sl)), [Some(Field(i)), rest @ ..]) => {
let id = mem::replace(i, ast::Id::new_unchecked(""));
let maybe_expr = match to_unescaped_string(s) {
Ok(s) => Some(construct_expr_string(s, sl.clone())),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
};
head = maybe_expr
.map(|e| Expr(construct_expr_attr(e, id.to_smolstr(), src.clone())));
tail = rest;
}
(Some(Var(v, vl)), [Some(Index(i)), rest @ ..]) => {
let var = mem::replace(v, ast::Var::Principal);
let s = mem::take(i);
head = Some(Expr(construct_expr_attr(
construct_expr_var(var, vl.clone()),
s,
src.clone(),
)));
tail = rest;
}
(Some(Expr(e)), [Some(Index(i)), rest @ ..]) => {
let expr = mem::replace(e, ast::Expr::val(false));
let s = mem::take(i);
head = Some(Expr(construct_expr_attr(expr, s, src.clone())));
tail = rest;
}
(Some(StrLit(s, sl)), [Some(Index(i)), rest @ ..]) => {
let id = mem::take(i);
let maybe_expr = match to_unescaped_string(s) {
Ok(s) => Some(construct_expr_string(s, sl.clone())),
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
};
head = maybe_expr.map(|e| Expr(construct_expr_attr(e, id, src.clone())));
tail = rest;
}
}
}
}
}
impl ASTNode<Option<cst::MemAccess>> {
fn to_access(&self, errs: &mut ParseErrors) -> Option<AstAccessor> {
let maybe_acc = self.as_inner();
let acc = maybe_acc?;
match acc {
cst::MemAccess::Field(i) => {
let ident = i.to_valid_ident(errs);
ident.map(AstAccessor::Field)
}
cst::MemAccess::Call(args) => {
let conv_args: Vec<_> = args.iter().filter_map(|e| e.to_expr(errs)).collect();
if conv_args.len() == args.len() {
Some(AstAccessor::Call(conv_args))
} else {
None
}
}
cst::MemAccess::Index(index) => {
let s = index.to_expr_or_special(errs)?.into_string_literal(errs);
s.map(AstAccessor::Index)
}
}
}
}
impl ASTNode<Option<cst::Primary>> {
fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
let maybe_prim = self.as_inner();
let prim = maybe_prim?;
match prim {
cst::Primary::Slot(s) => {
let slot = s.as_inner()?;
if slot.matches(var) {
T::create_slot(errs)
} else {
errs.push(ToASTError::wrong_node(
T::err_str(),
format!("{slot} instead of ?{var}"),
None::<String>,
));
None
}
}
cst::Primary::Literal(lit) => {
let found = match lit.as_inner() {
Some(lit) => format!("literal `{lit}`"),
None => "empty node".to_string(),
};
errs.push(ToASTError::wrong_node(T::err_str(), found, None::<String>));
None
}
cst::Primary::Ref(x) => T::create_single_ref(x.to_ref(errs)?, errs),
cst::Primary::Name(name) => {
let found = match name.as_inner() {
Some(name) => format!("name `{name}`"),
None => "name".to_string(),
};
errs.push(ToASTError::wrong_node(T::err_str(), found, None::<String>));
None
}
cst::Primary::Expr(x) => x.to_ref_or_refs::<T>(errs, var),
cst::Primary::EList(lst) => {
let v: Option<Vec<EntityUID>> =
lst.iter().map(|expr| expr.to_ref(var, errs)).collect();
T::create_multiple_refs(v?, errs)
}
cst::Primary::RInits(_) => {
errs.push(ToASTError::wrong_node(
T::err_str(),
"record initializer",
None::<String>,
));
None
}
}
}
pub(crate) fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_expr_or_special(errs)?.into_expr(errs)
}
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_prim) = self.as_inner_pair();
let prim = maybe_prim?;
match prim {
cst::Primary::Literal(l) => l.to_expr_or_special(errs),
cst::Primary::Ref(r) => r.to_expr(errs).map(ExprOrSpecial::Expr),
cst::Primary::Slot(s) => s.clone().into_expr(errs).map(ExprOrSpecial::Expr),
#[allow(clippy::manual_map)]
cst::Primary::Name(n) => {
if let Some(v) = n.to_var(&mut ParseErrors::new()) {
Some(ExprOrSpecial::Var(v, src.clone()))
} else if let Some(n) = n.to_name(errs) {
Some(ExprOrSpecial::Name(n))
} else {
None
}
}
cst::Primary::Expr(e) => e.to_expr(errs).map(ExprOrSpecial::Expr),
cst::Primary::EList(es) => {
let list: Vec<_> = es.iter().filter_map(|e| e.to_expr(errs)).collect();
if list.len() == es.len() {
Some(ExprOrSpecial::Expr(construct_expr_set(list, src.clone())))
} else {
None
}
}
cst::Primary::RInits(is) => {
let rec: Vec<_> = is.iter().filter_map(|i| i.to_init(errs)).collect();
if rec.len() == is.len() {
match construct_expr_record(rec, src.clone()) {
Ok(rec) => Some(ExprOrSpecial::Expr(rec)),
Err(e) => {
errs.push(e);
None
}
}
} else {
errs.push(ToASTError::InvalidAttributesInRecordLiteral);
None
}
}
}
}
pub fn to_string_literal(&self, errs: &mut ParseErrors) -> Option<SmolStr> {
let maybe_prim = self.as_inner();
let prim = maybe_prim?;
match prim {
cst::Primary::Literal(l) => l.to_expr_or_special(errs)?.into_string_literal(errs),
_ => None,
}
}
}
impl ASTNode<Option<cst::Slot>> {
fn into_expr(self, _errs: &mut ParseErrors) -> Option<ast::Expr> {
let (s, src) = self.into_inner();
s.map(|s| ast::ExprBuilder::new().with_source_info(src).slot(s.into()))
}
}
impl From<cst::Slot> for ast::SlotId {
fn from(slot: cst::Slot) -> ast::SlotId {
match slot {
cst::Slot::Principal => ast::SlotId::principal(),
cst::Slot::Resource => ast::SlotId::resource(),
}
}
}
impl From<ast::SlotId> for cst::Slot {
fn from(slot: ast::SlotId) -> cst::Slot {
match slot {
ast::SlotId(ast::ValidSlotId::Principal) => cst::Slot::Principal,
ast::SlotId(ast::ValidSlotId::Resource) => cst::Slot::Resource,
}
}
}
impl ASTNode<Option<cst::Name>> {
fn to_type_constraint(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
let (src, maybe_name) = self.as_inner_pair();
match maybe_name {
Some(_) => {
errs.push(ToASTError::TypeConstraints);
None
}
None => Some(construct_expr_bool(true, src.clone())),
}
}
pub(crate) fn to_name(&self, errs: &mut ParseErrors) -> Option<ast::Name> {
let maybe_name = self.as_inner();
let name = maybe_name?;
let path: Vec<_> = name
.path
.iter()
.filter_map(|i| i.to_valid_ident(errs))
.collect();
let maybe_name = name.name.to_valid_ident(errs);
match (maybe_name, path.len()) {
(Some(r), l) if l == name.path.len() => Some(construct_name(path, r)),
_ => None,
}
}
fn to_ident(&self, errs: &mut ParseErrors) -> Option<&cst::Ident> {
let maybe_name = self.as_inner();
let name = maybe_name?;
let path: Vec<_> = name
.path
.iter()
.filter_map(|i| i.to_valid_ident(errs))
.collect();
if path.len() > 1 {
errs.push(ToASTError::InvalidPath);
return None;
}
name.name.as_inner()
}
fn to_var(&self, errs: &mut ParseErrors) -> Option<ast::Var> {
let name = self.to_ident(errs)?;
match name {
cst::Ident::Principal => Some(ast::Var::Principal),
cst::Ident::Action => Some(ast::Var::Action),
cst::Ident::Resource => Some(ast::Var::Resource),
cst::Ident::Context => Some(ast::Var::Context),
n => {
errs.push(ToASTError::ArbitraryVariable(n.to_string().into()));
None
}
}
}
}
impl ast::Name {
fn into_valid_attr(self, errs: &mut ParseErrors) -> Option<SmolStr> {
if !self.path.is_empty() {
errs.push(ToASTError::PathAsAttribute(self.to_string()));
None
} else {
Some(self.id.to_smolstr())
}
}
fn into_func(
self,
args: Vec<ast::Expr>,
errs: &mut ParseErrors,
l: SourceInfo,
) -> Option<ast::Expr> {
if self.path.is_empty() {
let id = self.id.as_ref();
match id {
"contains" | "containsAll" | "containsAny" => {
errs.push(ToASTError::FunctionCallOnMethod(self.id));
return None;
}
_ => {}
}
}
if EXTENSION_STYLES.functions.contains(&self) {
Some(construct_ext_func(self, args, l))
} else {
errs.push(ToASTError::NotAFunction(self));
None
}
}
}
impl ASTNode<Option<cst::Ref>> {
pub fn to_ref(&self, errs: &mut ParseErrors) -> Option<ast::EntityUID> {
let maybe_ref = self.as_inner();
let refr = maybe_ref?;
match refr {
cst::Ref::Uid { path, eid } => {
let maybe_path = path.to_name(errs);
let maybe_eid = match eid
.as_valid_string(errs)
.map(|s| to_unescaped_string(s))
.transpose()
{
Ok(opt) => opt,
Err(escape_errs) => {
errs.extend(escape_errs.into_iter().map(ToASTError::Unescape));
None
}
};
match (maybe_path, maybe_eid) {
(Some(p), Some(e)) => Some(construct_refr(p, e)),
_ => None,
}
}
cst::Ref::Ref { .. } => {
errs.push(ToASTError::UnsupportedEntityLiterals);
None
}
}
}
fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
self.to_ref(errs)
.map(|euid| construct_expr_ref(euid, self.info.clone()))
}
}
impl ASTNode<Option<cst::Literal>> {
fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
let (src, maybe_lit) = self.as_inner_pair();
let lit = maybe_lit?;
match lit {
cst::Literal::True => Some(ExprOrSpecial::Expr(construct_expr_bool(true, src.clone()))),
cst::Literal::False => {
Some(ExprOrSpecial::Expr(construct_expr_bool(false, src.clone())))
}
cst::Literal::Num(n) => match i64::try_from(*n) {
Ok(i) => Some(ExprOrSpecial::Expr(construct_expr_num(i, src.clone()))),
Err(_) => {
errs.push(ToASTError::IntegerLiteralTooLarge(*n));
None
}
},
cst::Literal::Str(s) => {
let maybe_str = s.as_valid_string(errs);
maybe_str.map(|s| ExprOrSpecial::StrLit(s, src.clone()))
}
}
}
}
impl ASTNode<Option<cst::RecInit>> {
fn to_init(&self, errs: &mut ParseErrors) -> Option<(SmolStr, ast::Expr)> {
let (_src, maybe_lit) = self.as_inner_pair();
let lit = maybe_lit?;
let maybe_attr = lit.0.to_expr_or_special(errs)?.into_valid_attr(errs);
let maybe_value = lit.1.to_expr(errs);
match (maybe_attr, maybe_value) {
(Some(s), Some(v)) => Some((s, v)),
_ => None,
}
}
}
#[allow(clippy::too_many_arguments)]
fn construct_template_policy(
id: ast::PolicyID,
annotations: BTreeMap<ast::Id, SmolStr>,
effect: ast::Effect,
principal: ast::PrincipalConstraint,
action: ast::ActionConstraint,
resource: ast::ResourceConstraint,
conds: Vec<ast::Expr>,
l: SourceInfo,
) -> ast::Template {
let construct_template = |non_head_constraint| {
ast::Template::new(
id,
annotations,
effect,
principal,
action,
resource,
non_head_constraint,
)
};
let mut conds_iter = conds.into_iter();
if let Some(first_expr) = conds_iter.next() {
construct_template(match conds_iter.next() {
Some(e) => construct_expr_and(first_expr, e, conds_iter, l),
None => first_expr,
})
} else {
construct_template(construct_expr_bool(true, l))
}
}
fn construct_id(s: String) -> ast::Id {
ast::Id::new_unchecked(s)
}
fn construct_string_from_var(v: ast::Var) -> SmolStr {
match v {
ast::Var::Principal => "principal".into(),
ast::Var::Action => "action".into(),
ast::Var::Resource => "resource".into(),
ast::Var::Context => "context".into(),
}
}
fn construct_name(path: Vec<ast::Id>, id: ast::Id) -> ast::Name {
ast::Name {
id,
path: Arc::new(path),
}
}
fn construct_refr(p: ast::Name, n: SmolStr) -> ast::EntityUID {
let eid = ast::Eid::new(n);
ast::EntityUID::from_components(p, eid)
}
fn construct_expr_ref(r: ast::EntityUID, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).val(r)
}
fn construct_expr_num(n: i64, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).val(n)
}
fn construct_expr_string(s: SmolStr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).val(s)
}
fn construct_expr_bool(b: bool, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).val(b)
}
fn construct_expr_neg(e: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).neg(e)
}
fn construct_expr_not(e: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).not(e)
}
fn construct_expr_var(v: ast::Var, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).var(v)
}
fn construct_expr_if(i: ast::Expr, t: ast::Expr, e: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).ite(i, t, e)
}
fn construct_expr_or(
f: ast::Expr,
s: ast::Expr,
chained: impl IntoIterator<Item = ast::Expr>,
l: SourceInfo,
) -> ast::Expr {
let first = ast::ExprBuilder::new().with_source_info(l.clone()).or(f, s);
chained.into_iter().fold(first, |a, n| {
ast::ExprBuilder::new().with_source_info(l.clone()).or(a, n)
})
}
fn construct_expr_and(
f: ast::Expr,
s: ast::Expr,
chained: impl IntoIterator<Item = ast::Expr>,
l: SourceInfo,
) -> ast::Expr {
let first = ast::ExprBuilder::new()
.with_source_info(l.clone())
.and(f, s);
chained.into_iter().fold(first, |a, n| {
ast::ExprBuilder::new()
.with_source_info(l.clone())
.and(a, n)
})
}
fn construct_expr_rel(f: ast::Expr, rel: cst::RelOp, s: ast::Expr, l: SourceInfo) -> ast::Expr {
let builder = ast::ExprBuilder::new().with_source_info(l);
match rel {
cst::RelOp::Less => builder.less(f, s),
cst::RelOp::LessEq => builder.lesseq(f, s),
cst::RelOp::GreaterEq => builder.greatereq(f, s),
cst::RelOp::Greater => builder.greater(f, s),
cst::RelOp::NotEq => builder.noteq(f, s),
cst::RelOp::Eq => builder.is_eq(f, s),
cst::RelOp::In => builder.is_in(f, s),
}
}
fn construct_expr_add(
f: ast::Expr,
chained: impl IntoIterator<Item = (cst::AddOp, ast::Expr)>,
l: SourceInfo,
) -> ast::Expr {
let mut expr = f;
for (op, next_expr) in chained {
let builder = ast::ExprBuilder::new().with_source_info(l.clone());
expr = match op {
cst::AddOp::Plus => builder.add(expr, next_expr),
cst::AddOp::Minus => builder.sub(expr, next_expr),
};
}
expr
}
fn construct_expr_mul(
f: ast::Expr,
chained: impl IntoIterator<Item = i64>,
l: SourceInfo,
) -> ast::Expr {
let mut expr = f;
for next_expr in chained {
expr = ast::ExprBuilder::new()
.with_source_info(l.clone())
.mul(expr, next_expr)
}
expr
}
fn construct_expr_has(t: ast::Expr, s: SmolStr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).has_attr(t, s)
}
fn construct_expr_attr(e: ast::Expr, s: SmolStr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).get_attr(e, s)
}
fn construct_expr_like(e: ast::Expr, s: Vec<PatternElem>, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).like(e, s)
}
fn construct_expr_is(e: ast::Expr, n: ast::Name, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new()
.with_source_info(l)
.is_entity_type(e, n)
}
fn construct_ext_func(name: ast::Name, args: Vec<ast::Expr>, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new()
.with_source_info(l)
.call_extension_fn(name, args)
}
fn construct_method_contains(e0: ast::Expr, e1: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).contains(e0, e1)
}
fn construct_method_contains_all(e0: ast::Expr, e1: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new()
.with_source_info(l)
.contains_all(e0, e1)
}
fn construct_method_contains_any(e0: ast::Expr, e1: ast::Expr, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new()
.with_source_info(l)
.contains_any(e0, e1)
}
fn construct_ext_meth(n: String, args: Vec<ast::Expr>, l: SourceInfo) -> ast::Expr {
let id = ast::Id::new_unchecked(n);
let name = ast::Name::unqualified_name(id);
ast::ExprBuilder::new()
.with_source_info(l)
.call_extension_fn(name, args)
}
fn construct_expr_set(s: Vec<ast::Expr>, l: SourceInfo) -> ast::Expr {
ast::ExprBuilder::new().with_source_info(l).set(s)
}
fn construct_expr_record(
kvs: Vec<(SmolStr, ast::Expr)>,
l: SourceInfo,
) -> Result<ast::Expr, ToASTError> {
ast::ExprBuilder::new()
.with_source_info(l)
.record(kvs)
.map_err(|e| match e {
ExprConstructionError::DuplicateKeyInRecordLiteral { key } => {
ToASTError::DuplicateKeyInRecordLiteral { key }
}
})
}
#[allow(clippy::panic)]
#[cfg(test)]
mod tests {
use super::*;
use crate::{
ast::Expr,
parser::{err::ParseErrors, *},
};
use cool_asserts::assert_matches;
use std::str::FromStr;
#[test]
fn show_expr1() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
assert!(errs.is_empty());
println!("{:?}", expr);
}
#[test]
fn show_expr2() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
[2,3,4].foo["hello"]
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
println!("{:?}", expr);
}
#[test]
fn show_expr3() {
let mut errs = ParseErrors::new();
let expr = text_to_cst::parse_expr(
r#"
"first".some_ident
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "some_ident");
}
_ => panic!("should be a get expr"),
}
let expr = text_to_cst::parse_expr(
r#"
1.some_ident
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "some_ident");
}
_ => panic!("should be a get expr"),
}
let expr = text_to_cst::parse_expr(
r#"
"first"["some string"]
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "some string");
}
_ => panic!("should be a get expr"),
}
}
#[test]
fn show_expr4() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
{"one":1,"two":2} has one
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::HasAttr { attr, .. } => {
assert_eq!(attr, "one");
}
_ => panic!("should be a has expr"),
}
}
#[test]
fn show_expr5() {
let mut errs = ParseErrors::new();
let expr = text_to_cst::parse_expr(
r#"
{"one":1,"two":2}.one
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "one");
}
_ => panic!("should be a get expr"),
}
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
{"one":1,"two":2}["one"]
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "one");
}
_ => panic!("should be a get expr"),
}
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
{"this is a valid map key+.-_%()":1,"two":2}["this is a valid map key+.-_%()"]
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "this is a valid map key+.-_%()");
}
_ => panic!("should be a get expr"),
}
}
#[test]
fn show_expr6_idents() {
let mut errs = ParseErrors::new();
let expr = text_to_cst::parse_expr(
r#"
{if true then a else b:"b"} ||
{if false then a else b:"b"}
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(expr.is_none());
assert!(errs.len() == 6);
errs.clear();
let expr = text_to_cst::parse_expr(
r#"
{principal:"principal"}
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
println!("{:?}", expr);
match expr.expr_kind() {
ast::ExprKind::Record { .. } => {}
_ => panic!("should be record"),
}
errs.clear();
let expr = text_to_cst::parse_expr(
r#"
{"principal":"principal"}
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
println!("{:?}", expr);
match expr.expr_kind() {
ast::ExprKind::Record { .. } => {}
_ => panic!("should be record"),
}
}
#[test]
fn reserved_idents1() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_expr(
r#"
The::true::path::to::"enlightenment".false
"#,
)
.expect("failed parse");
let convert = parse.to_expr(&mut errs);
println!("{:?}", errs);
assert!(errs.len() == 2);
assert!(convert.is_none());
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_expr(
r#"
if {if: true}.if then {"if":false}["if"] else {when:true}.permit
"#,
)
.expect("failed parse");
let convert = parse.to_expr(&mut errs);
println!("{:?}", errs);
assert!(errs.len() == 3);
assert!(convert.is_none());
}
#[test]
fn reserved_idents2() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_expr(
r#"
if {where: true}.like || {has:false}.in then {"like":false}["in"] else {then:true}.else
"#,
)
.expect("failed parse");
let convert = parse.to_expr(&mut errs);
println!("{:?}", errs);
assert!(errs.len() == 7);
assert!(convert.is_none());
}
#[test]
fn show_policy1() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
"#,
)
.expect("failed parse");
println!("{:#}", parse.as_inner().expect("internal parse error"));
let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
println!("{:?}", errs);
assert!(errs.len() == 6);
assert!(convert.is_none());
println!("{:?}", convert);
}
#[test]
fn show_policy2() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(principal,action,resource)when{true};
"#,
)
.expect("failed parse");
println!("{}", parse.as_inner().expect("internal parse error"));
println!("{:?}", parse.as_inner().expect("internal parse error"));
let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
assert!(convert.is_some());
println!("{:?}", convert);
}
#[test]
fn show_policy3() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(principal in User::"jane",action,resource);
"#,
)
.expect("failed parse");
println!("{}", parse.as_inner().expect("internal parse error"));
println!("{:?}", parse.as_inner().expect("internal parse error"));
let convert = parse
.to_policy(ast::PolicyID::from_string("id"), &mut errs)
.expect("failed convert");
assert!(errs.is_empty());
println!("{:?}", convert);
}
#[test]
fn show_policy4() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
forbid(principal in User::"jane",action,resource)unless{
context.group != "friends"
};
"#,
)
.expect("failed parse");
let convert = parse
.to_policy(ast::PolicyID::from_string("id"), &mut errs)
.expect("failed convert");
assert!(errs.is_empty());
println!("\n{:?}", convert);
}
#[test]
fn policy_annotations() {
let mut errs = ParseErrors::new();
let policy = text_to_cst::parse_policy(
r#"
@anno("good annotation")permit(principal,action,resource);
"#,
)
.expect("should parse")
.to_policy(ast::PolicyID::from_string("id"), &mut errs)
.expect("should be valid");
assert_eq!(
policy.annotation(&ast::Id::new_unchecked("anno")),
Some(&"good annotation".into())
);
let mut errs = ParseErrors::new();
let policy = text_to_cst::parse_policy(
r#"
@anno("good annotation")
@anno2("good annotation")
@anno("oops, duplicate")
permit(principal,action,resource);
"#,
)
.expect("should parse")
.to_policy(ast::PolicyID::from_string("id"), &mut errs);
assert!(policy.is_none());
assert!(errs.len() == 1);
let mut errs = ParseErrors::new();
let policyset = text_to_cst::parse_policies(
r#"
@anno1("first")
permit(principal,action,resource);
@anno2("second")
permit(principal,action,resource);
@anno3a("third-a")
@anno3b("third-b")
permit(principal,action,resource);
"#,
)
.expect("should parse")
.to_policyset(&mut errs)
.expect("should be valid");
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy0"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno0")),
None
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy0"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno1")),
Some(&"first".into())
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy1"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno2")),
Some(&"second".into())
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy2"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno3a")),
Some(&"third-a".into())
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy2"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno3b")),
Some(&"third-b".into())
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy2"))
.expect("should be a policy")
.annotation(&ast::Id::new_unchecked("anno3c")),
None
);
assert_eq!(
policyset
.get(&ast::PolicyID::from_string("policy2"))
.expect("should be a policy")
.annotations()
.count(),
2
);
}
#[test]
fn fail_head1() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(
principal in [User::"jane",Group::"friends"],
action,
resource
);
"#,
)
.expect("failed parse");
println!("\n{:#}", parse.as_inner().expect("internal parse error"));
let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
println!("{:?}", errs);
assert!(errs.len() == 1);
assert!(convert.is_none());
}
#[test]
fn fail_head2() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(
principal in User::"jane",
action == if true then Photo::"view" else Photo::"edit",
resource
);
"#,
)
.expect("failed parse");
println!("{:#}", parse.as_inner().expect("internal parse error"));
let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
println!("{:?}", errs);
assert!(errs.len() == 1);
assert!(convert.is_none());
}
#[test]
fn fail_head3() {
let mut errs = ParseErrors::new();
let parse = text_to_cst::parse_policy(
r#"
permit(principal,action,resource,context);
"#,
)
.expect("failed parse");
let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
assert!(errs.len() == 1);
assert!(convert.is_none());
}
#[test]
fn method_call2() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
principal.contains(resource)
"#,
)
.expect("parse error")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_some());
assert!(errs.is_empty());
let e = text_to_cst::parse_expr(
r#"
contains(principal,resource)
"#,
)
.expect("parse error")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
assert!(errs.len() == 1);
}
#[test]
fn construct_record1() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
{one:"one"}
"#,
)
.expect("parse error")
.to_expr(&mut errs)
.expect("convert fail");
if let ast::ExprKind::Record { .. } = e.expr_kind() {
} else {
panic!("not a record")
}
println!("{e}");
let e = text_to_cst::parse_expr(
r#"
{"one":"one"}
"#,
)
.expect("parse error")
.to_expr(&mut errs)
.expect("convert fail");
if let ast::ExprKind::Record { .. } = e.expr_kind() {
} else {
panic!("not a record")
}
println!("{e}");
let e = text_to_cst::parse_expr(
r#"
{"one":"one",two:"two"}
"#,
)
.expect("parse error")
.to_expr(&mut errs)
.expect("convert fail");
if let ast::ExprKind::Record { .. } = e.expr_kind() {
} else {
panic!("not a record")
}
println!("{e}");
let e = text_to_cst::parse_expr(
r#"
{one:"one","two":"two"}
"#,
)
.expect("parse error")
.to_expr(&mut errs)
.expect("convert fail");
if let ast::ExprKind::Record { .. } = e.expr_kind() {
} else {
panic!("not a record")
}
println!("{e}");
let e = text_to_cst::parse_expr(
r#"
{one:"b\"","b\"":2}
"#,
)
.expect("parse error")
.to_expr(&mut errs)
.expect("convert fail");
if let ast::ExprKind::Record { .. } = e.expr_kind() {
} else {
panic!("not a record")
}
println!("{e}");
}
#[test]
fn construct_invalid_get() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
{"one":1, "two":"two"}[0]
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
assert!(errs.len() == 1);
let e = text_to_cst::parse_expr(
r#"
{"one":1, "two":"two"}[-1]
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
let e = text_to_cst::parse_expr(
r#"
{"one":1, "two":"two"}[true]
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
let e = text_to_cst::parse_expr(
r#"
{"one":1, "two":"two"}[one]
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
}
#[test]
fn construct_has() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
{"one":1,"two":2} has "arbitrary+ _string"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::HasAttr { attr, .. } => {
assert_eq!(attr, "arbitrary+ _string");
}
_ => panic!("should be a has expr"),
}
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
{"one":1,"two":2} has 1
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
assert!(errs.len() == 1);
}
#[test]
fn construct_like() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
"354 hams" like "*5*"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(pattern.to_string(), "*5*");
}
_ => panic!("should be a like expr"),
}
let e = text_to_cst::parse_expr(
r#"
"354 hams" like 354
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
assert!(errs.len() == 1);
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
"string\\with\\backslashes" like "string\\with\\backslashes"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(pattern.to_string(), r"string\\with\\backslashes");
}
_ => panic!("should be a like expr"),
}
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
"string\\with\\backslashes" like "string\*with\*backslashes"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(pattern.to_string(), r"string\*with\*backslashes");
}
_ => panic!("should be a like expr"),
}
let e = text_to_cst::parse_expr(
r#"
"string\*with\*escaped\*stars" like "string\*with\*escaped\*stars"
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
println!("{:?}", errs);
assert!(e.is_none());
assert!(errs.len() == 3); let expr: ast::Expr = text_to_cst::parse_expr(
r#"
"string*with*stars" like "string\*with\*stars"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(pattern.to_string(), "string\\*with\\*stars");
}
_ => panic!("should be a like expr"),
}
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
"string\\*with\\*backslashes\\*and\\*stars" like "string\\\*with\\\*backslashes\\\*and\\\*stars"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(
pattern.to_string(),
r"string\\\*with\\\*backslashes\\\*and\\\*stars"
);
}
_ => panic!("should be a like expr"),
}
let test_pattern = &vec![
PatternElem::Char('h'),
PatternElem::Char('e'),
PatternElem::Char('l'),
PatternElem::Char('l'),
PatternElem::Char('o'),
PatternElem::Char('\\'),
PatternElem::Char('0'),
PatternElem::Char('*'),
PatternElem::Char('\\'),
PatternElem::Char('*'),
];
let e1 = ast::Expr::like(ast::Expr::val("hello"), test_pattern.clone());
let s1 = format!("{e1}");
assert_eq!(s1, r#""hello" like "hello\\0\*\\\*""#);
let e2 = text_to_cst::parse_expr(&s1)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match e2.expr_kind() {
ast::ExprKind::Like { pattern, .. } => {
assert_eq!(pattern.get_elems(), test_pattern);
}
_ => panic!("should be a like expr"),
}
let s2 = format!("{e2}");
assert_eq!(s1, s2);
}
#[test]
fn issue_wf_5046() {
let policy = parse_policy(
Some("WF-5046".into()),
r#"permit(
principal,
action in [Action::"action"],
resource in G::""
) when {
true && ("" like "/gisterNatives\\*D")
};"#,
);
assert!(policy.is_ok());
}
#[test]
fn entity_access() {
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
User::"jane" has age
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::HasAttr { attr, .. } => {
assert_eq!(attr, "age");
}
_ => panic!("should be a has expr"),
}
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
User::"jane" has "arbitrary+ _string"
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::HasAttr { attr, .. } => {
assert_eq!(attr, "arbitrary+ _string");
}
_ => panic!("should be a has expr"),
}
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
User::"jane" has 1
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
assert!(e.is_none());
assert!(errs.len() == 1);
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
User::"jane".age
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "age");
}
_ => panic!("should be a get expr"),
}
let mut errs = ParseErrors::new();
let expr: ast::Expr = text_to_cst::parse_expr(
r#"
User::"jane"["arbitrary+ _string"]
"#,
)
.expect("failed parser")
.to_expr(&mut errs)
.expect("failed convert");
match expr.expr_kind() {
ast::ExprKind::GetAttr { attr, .. } => {
assert_eq!(attr, "arbitrary+ _string");
}
_ => panic!("should be a get expr"),
}
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
User::"jane"[age]
"#,
)
.expect("failed parser")
.to_expr(&mut errs);
assert!(e.is_none());
assert!(errs.len() == 1);
}
#[test]
fn relational_ops1() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(
r#"
3 >= 2 >= 1
"#,
)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_none());
let e = text_to_cst::parse_expr(
r#"
3 >= ("dad" in "dad")
"#,
)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(
r#"
(3 >= 2) == true
"#,
)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(
r#"
if 4 < 3 then 4 != 3 else 4 == 3 < 4
"#,
)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_none());
}
#[test]
fn arithmetic() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(r#" 2 + 4 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 2 + -5 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 2 - 5 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 2 * 5 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 2 * -5 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" context.size * 4 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 4 * context.size "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" context.size * context.scale "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_none());
let e = text_to_cst::parse_expr(r#" 5 + 10 + 90 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 5 + 10 - 90 * -2 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 5 + 10 * 90 - 2 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 5 - 10 - 90 - 2 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" 5 * context.size * 10 "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_expr(r#" context.size * 3 * context.scale "#)
.expect("parse error")
.to_expr(&mut errs);
assert!(e.is_none());
}
const CORRECT_TEMPLATES: [&str; 7] = [
r#"permit(principal == ?principal, action == Action::"action", resource == ?resource);"#,
r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
r#"permit(principal in p::"principal", action == Action::"action", resource in ?resource);"#,
r#"permit(principal == p::"principal", action == Action::"action", resource in ?resource);"#,
r#"permit(principal in ?principal, action == Action::"action", resource in r::"resource");"#,
r#"permit(principal in ?principal, action == Action::"action", resource == r::"resource");"#,
];
#[test]
fn template_tests() {
for src in CORRECT_TEMPLATES {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_policy(src)
.expect("parse_error")
.to_policy_template(ast::PolicyID::from_string("i0"), &mut errs);
if e.is_none() {
panic!("Failed to create a policy template: {:?}", errs);
}
}
}
const WRONG_VAR_TEMPLATES: [&str; 16] = [
r#"permit(principal == ?resource, action, resource);"#,
r#"permit(principal in ?resource, action, resource);"#,
r#"permit(principal, action, resource == ?principal);"#,
r#"permit(principal, action, resource in ?principal);"#,
r#"permit(principal, action == ?principal, resource);"#,
r#"permit(principal, action in ?principal, resource);"#,
r#"permit(principal, action == ?resource, resource);"#,
r#"permit(principal, action in ?resource, resource);"#,
r#"forbid(principal == ?resource, action, resource);"#,
r#"forbid(principal in ?resource, action, resource);"#,
r#"forbid(principal, action, resource == ?principal);"#,
r#"forbid(principal, action, resource in ?principal);"#,
r#"forbid(principal, action == ?principal, resource);"#,
r#"forbid(principal, action in ?principal, resource);"#,
r#"forbid(principal, action == ?resource, resource);"#,
r#"forbid(principal, action in ?resource, resource);"#,
];
#[test]
fn test_wrong_template_var() {
for src in WRONG_VAR_TEMPLATES {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_policy(src)
.expect("Parse Error")
.to_policy_template(ast::PolicyID::from_string("id0"), &mut errs);
assert!(e.is_none());
}
}
#[test]
fn var_type() {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_policy(
r#"
permit(principal,action,resource);
"#,
)
.expect("parse error")
.to_policy(ast::PolicyID::from_string("0"), &mut errs);
assert!(e.is_some());
let e = text_to_cst::parse_policy(
r#"
permit(principal:User,action,resource);
"#,
)
.expect("parse error")
.to_policy(ast::PolicyID::from_string("1"), &mut errs);
assert!(e.is_none());
}
#[test]
fn string_escapes() {
let test_valid = |s: &str| {
let r = parse_literal(&format!("\"{}\"", s.escape_default()));
assert!(r.is_ok());
assert_eq!(r.unwrap(), ast::Literal::String(s.into()));
};
test_valid("\t");
test_valid("\0");
test_valid("👍");
test_valid("🐈");
test_valid("\u{1F408}");
test_valid("abc\tde\\fg");
test_valid("aaa\u{1F408}bcd👍👍👍");
let test_invalid = |s: &str, en: usize| {
let r = parse_literal(&format!("\"{}\"", s));
assert!(r.is_err());
assert!(r.unwrap_err().len() == en);
};
test_invalid("\\a", 1);
test_invalid("\\b", 1);
test_invalid("\\\\aa\\p", 1);
test_invalid(r"\aaa\u{}", 2);
}
fn expect_action_error(test: &str, euid_strs: Vec<&str>) {
let euids = euid_strs
.into_iter()
.map(|euid_str| {
EntityUID::from_str(euid_str).expect("Test was provided with invalid euid")
})
.collect::<Vec<_>>();
let p = parse_policyset(test);
match p {
Ok(pset) => panic!("Policy: {pset}, shouln't have parsed!"),
Err(es) => {
if es.len() != euids.len() {
panic!(
"Parse should have produced exactly {} parse errors, produced: {:?}",
euids.len(),
es
);
} else {
for euid in euids {
let err = ToASTError::InvalidActionType(euid).into();
assert!(es.contains(&err));
}
}
}
}
}
#[test]
fn action_checker() {
let euid = EntityUID::from_str("Action::\"view\"").unwrap();
assert!(euid_has_action_type(&euid));
let euid = EntityUID::from_str("Foo::Action::\"view\"").unwrap();
assert!(euid_has_action_type(&euid));
let euid = EntityUID::from_str("Foo::\"view\"").unwrap();
assert!(!euid_has_action_type(&euid));
let euid = EntityUID::from_str("Action::Foo::\"view\"").unwrap();
assert!(!euid_has_action_type(&euid));
}
#[test]
fn action_must_be_action() {
parse_policyset(r#"permit(principal, action == Action::"view", resource);"#)
.expect("Valid policy failed to parse");
parse_policyset(r#"permit(principal, action == Foo::Action::"view", resource);"#)
.expect("Valid policy failed to parse");
parse_policyset(r#"permit(principal, action in Action::"view", resource);"#)
.expect("Valid policy failed to parse");
parse_policyset(r#"permit(principal, action in Foo::Action::"view", resource);"#)
.expect("Valid policy failed to parse");
parse_policyset(r#"permit(principal, action in [Foo::Action::"view"], resource);"#)
.expect("Valid policy failed to parse");
parse_policyset(
r#"permit(principal, action in [Foo::Action::"view", Action::"view"], resource);"#,
)
.expect("Valid policy failed to parse");
expect_action_error(
r#"permit(principal, action == Foo::"view", resource);"#,
vec!["Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action == Action::Foo::"view", resource);"#,
vec!["Action::Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action == Bar::Action::Foo::"view", resource);"#,
vec!["Bar::Action::Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action in Bar::Action::Foo::"view", resource);"#,
vec!["Bar::Action::Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action in [Bar::Action::Foo::"view"], resource);"#,
vec!["Bar::Action::Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action in [Bar::Action::Foo::"view", Action::"check"], resource);"#,
vec!["Bar::Action::Foo::\"view\""],
);
expect_action_error(
r#"permit(principal, action in [Bar::Action::Foo::"view", Foo::"delete", Action::"check"], resource);"#,
vec!["Bar::Action::Foo::\"view\"", "Foo::\"delete\""],
);
}
#[test]
fn method_style() {
let policy = parse_policyset(
r#"permit(principal, action, resource)
when { contains(true) < 1 };"#,
);
assert!(
policy.is_err()
&& matches!(
policy.as_ref().unwrap_err().as_slice(),
[err::ParseError::ToAST(_)]
),
"builtin functions must be called in method-style"
);
}
#[test]
fn test_mul() {
for (es, expr) in [
("--2*3", Expr::mul(Expr::neg(Expr::val(-2)), 3)),
(
"1 * 2 * false",
Expr::mul(Expr::mul(Expr::val(false), 1), 2),
),
(
"0 * 1 * principal",
Expr::mul(Expr::mul(Expr::var(ast::Var::Principal), 0), 1),
),
(
"0 * (-1) * principal",
Expr::mul(Expr::mul(Expr::var(ast::Var::Principal), 0), -1),
),
] {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(es)
.expect("should construct a CST")
.to_expr(&mut errs)
.expect("should convert to AST");
assert!(
e.eq_shape(&expr),
"{:?} and {:?} should have the same shape.",
e,
expr
);
}
for es in [
r#"false * "bob""#,
"principal * (1 + 2)",
"principal * -(-1)",
"principal * --1",
] {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(es)
.expect("should construct a CST")
.to_expr(&mut errs);
assert!(e.is_none());
}
}
#[test]
fn test_not() {
for (es, expr) in [
(
"!1 + 2 == 3",
Expr::is_eq(
Expr::add(Expr::not(Expr::val(1)), Expr::val(2)),
Expr::val(3),
),
),
(
"!!1 + 2 == 3",
Expr::is_eq(
Expr::add(Expr::not(Expr::not(Expr::val(1))), Expr::val(2)),
Expr::val(3),
),
),
(
"!!!1 + 2 == 3",
Expr::is_eq(
Expr::add(Expr::not(Expr::val(1)), Expr::val(2)),
Expr::val(3),
),
),
(
"!!!!1 + 2 == 3",
Expr::is_eq(
Expr::add(Expr::not(Expr::not(Expr::val(1))), Expr::val(2)),
Expr::val(3),
),
),
(
"!!(-1) + 2 == 3",
Expr::is_eq(
Expr::add(Expr::not(Expr::not(Expr::val(-1))), Expr::val(2)),
Expr::val(3),
),
),
] {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(es)
.expect("should construct a CST")
.to_expr(&mut errs)
.expect("should convert to AST");
assert!(
e.eq_shape(&expr),
"{:?} and {:?} should have the same shape.",
e,
expr
);
}
}
#[test]
fn test_neg() {
for (es, expr) in [
("-(1 + 2)", Expr::neg(Expr::add(Expr::val(1), Expr::val(2)))),
("1-(2)", Expr::sub(Expr::val(1), Expr::val(2))),
("1-2", Expr::sub(Expr::val(1), Expr::val(2))),
("(-1)", Expr::val(-1)),
("-(-1)", Expr::neg(Expr::val(-1))),
("--1", Expr::neg(Expr::val(-1))),
("--(--1)", Expr::neg(Expr::neg(Expr::neg(Expr::val(-1))))),
("2--1", Expr::sub(Expr::val(2), Expr::val(-1))),
("-9223372036854775808", Expr::val(-(9223372036854775808))),
(
"--9223372036854775808",
Expr::neg(Expr::val(-9223372036854775808)),
),
(
"-(9223372036854775807)",
Expr::neg(Expr::val(9223372036854775807)),
),
] {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(es)
.expect("should construct a CST")
.to_expr(&mut errs)
.expect("should convert to AST");
assert!(
e.eq_shape(&expr),
"{:?} and {:?} should have the same shape.",
e,
expr
);
}
for (es, em) in [
(
"-9223372036854775809",
ParseError::ToAST(ToASTError::IntegerLiteralTooLarge(9223372036854775809)),
),
(
"-(9223372036854775808)",
ParseError::ToAST(ToASTError::IntegerLiteralTooLarge(9223372036854775808)),
),
] {
let mut errs = ParseErrors::new();
let e = text_to_cst::parse_expr(es)
.expect("should construct a CST")
.to_expr(&mut errs);
assert!(e.is_none());
println!("{:?}", errs);
assert!(errs.contains(&em));
}
}
#[test]
fn test_is_condition_ok() {
for (es, expr) in [
(
r#"User::"alice" is User"#,
Expr::is_entity_type(
Expr::val(r#"User::"alice""#.parse::<EntityUID>().unwrap()),
"User".parse().unwrap(),
),
),
(
r#"principal is User"#,
Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
),
(
r#"principal.foo is User"#,
Expr::is_entity_type(
Expr::get_attr(Expr::var(ast::Var::Principal), "foo".into()),
"User".parse().unwrap(),
),
),
(
r#"1 is User"#,
Expr::is_entity_type(Expr::val(1), "User".parse().unwrap()),
),
(
r#"principal is User in Group::"friends""#,
Expr::and(
Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
Expr::is_in(
Expr::var(ast::Var::Principal),
Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
),
),
),
(
r#"true && principal is User in principal"#,
Expr::and(
Expr::val(true),
Expr::and(
Expr::is_entity_type(
Expr::var(ast::Var::Principal),
"User".parse().unwrap(),
),
Expr::is_in(
Expr::var(ast::Var::Principal),
Expr::var(ast::Var::Principal),
),
),
),
),
(
r#"principal is User in principal && true"#,
Expr::and(
Expr::and(
Expr::is_entity_type(
Expr::var(ast::Var::Principal),
"User".parse().unwrap(),
),
Expr::is_in(
Expr::var(ast::Var::Principal),
Expr::var(ast::Var::Principal),
),
),
Expr::val(true),
),
),
(
r#"principal is A::B::C::User"#,
Expr::is_entity_type(
Expr::var(ast::Var::Principal),
"A::B::C::User".parse().unwrap(),
),
),
(
r#"principal is A::B::C::User in Group::"friends""#,
Expr::and(
Expr::is_entity_type(
Expr::var(ast::Var::Principal),
"A::B::C::User".parse().unwrap(),
),
Expr::is_in(
Expr::var(ast::Var::Principal),
Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
),
),
),
] {
let e = parse_expr(es).unwrap();
assert!(
e.eq_shape(&expr),
"{:?} and {:?} should have the same shape.",
e,
expr
);
}
}
#[test]
fn is_scope() {
for (src, p, a, r) in [
(
r#"permit(principal is User, action, resource);"#,
PrincipalConstraint::is_entity_type("User".parse().unwrap()),
ActionConstraint::any(),
ResourceConstraint::any(),
),
(
r#"permit(principal is A::User, action, resource);"#,
PrincipalConstraint::is_entity_type("A::User".parse().unwrap()),
ActionConstraint::any(),
ResourceConstraint::any(),
),
(
r#"permit(principal is User in Group::"thing", action, resource);"#,
PrincipalConstraint::is_entity_type_in(
"User".parse().unwrap(),
r#"Group::"thing""#.parse().unwrap(),
),
ActionConstraint::any(),
ResourceConstraint::any(),
),
(
r#"permit(principal is A::User in Group::"thing", action, resource);"#,
PrincipalConstraint::is_entity_type_in(
"A::User".parse().unwrap(),
r#"Group::"thing""#.parse().unwrap(),
),
ActionConstraint::any(),
ResourceConstraint::any(),
),
(
r#"permit(principal is User in ?principal, action, resource);"#,
PrincipalConstraint::is_entity_type_in_slot("User".parse().unwrap()),
ActionConstraint::any(),
ResourceConstraint::any(),
),
(
r#"permit(principal, action, resource is Folder);"#,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::is_entity_type("Folder".parse().unwrap()),
),
(
r#"permit(principal, action, resource is Folder in Folder::"inner");"#,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::is_entity_type_in(
"Folder".parse().unwrap(),
r#"Folder::"inner""#.parse().unwrap(),
),
),
(
r#"permit(principal, action, resource is Folder in ?resource);"#,
PrincipalConstraint::any(),
ActionConstraint::any(),
ResourceConstraint::is_entity_type_in_slot("Folder".parse().unwrap()),
),
] {
let policy = parse_policy_template(None, src).unwrap();
assert_eq!(policy.principal_constraint(), &p);
assert_eq!(policy.action_constraint(), &a);
assert_eq!(policy.resource_constraint(), &r);
}
}
#[test]
fn is_err() {
let invalid_is_policies = [
(
r#"permit(principal == ?resource, action, resource);"#,
"?resource instead of ?principal",
),
(
r#"permit(principal, action, resource == ?principal);"#,
"?principal instead of ?resource",
),
(
r#"permit(principal in Group::"friends" is User, action, resource);"#,
"expected an entity uid or matching template slot",
),
(
r#"permit(principal, action, resource in Folder::"folder" is File);"#,
"expected an entity uid or matching template slot",
),
(
r#"permit(principal is User == User::"Alice", action, resource);"#,
"`is` cannot appear in the scope at the same time as `==`",
),
(
r#"permit(principal, action, resource is Doc == Doc::"a");"#,
"`is` cannot appear in the scope at the same time as `==`",
),
(
r#"permit(principal is User::"alice", action, resource);"#,
r#"unexpected token `"alice"`"#,
),
(
r#"permit(principal, action, resource is File::"f");"#,
r#"unexpected token `"f"`"#,
),
(
r#"permit(principal is User in 1, action, resource);"#,
"expected an entity uid or matching template slot, found literal `1`",
),
(
r#"permit(principal, action, resource is File in 1);"#,
"expected an entity uid or matching template slot, found literal `1`",
),
(
r#"permit(principal is User in User, action, resource);"#,
"expected an entity uid or matching template slot, found name `User`",
),
(
r#"permit(principal, action, resource is File in File);"#,
"expected an entity uid or matching template slot, found name `File`",
),
(
r#"permit(principal is 1, action, resource);"#,
"unexpected token `1`",
),
(
r#"permit(principal, action, resource is 1);"#,
"unexpected token `1`",
),
(
r#"permit(principal, action is Action, resource);"#,
"`is` cannot appear in the action scope",
),
(
r#"permit(principal, action is Action in Action::"A", resource);"#,
"`is` cannot appear in the action scope",
),
(
r#"permit(principal is User in ?resource, action, resource);"#,
"expected an entity uid or matching template slot",
),
(
r#"permit(principal, action, resource is Folder in ?principal);"#,
"expected an entity uid or matching template slot",
),
(
r#"permit(principal, action, resource) when { principal is 1 };"#,
"unexpected token `1`",
),
];
for (p_src, expected) in invalid_is_policies {
let err = parse_policy_template(None, p_src).unwrap_err().to_string();
assert!(
err.contains(expected),
"expected error containing `{}`, saw `{}`",
expected,
err
);
}
}
#[test]
fn issue_255() {
let policy = r#"
permit (
principal == name-with-dashes::"Alice",
action,
resource
);
"#;
assert_matches!(
parse_policy(None, policy),
Err(e) => {
assert!(
e.to_string().contains("expected an entity uid or matching template slot, found a `+/-` expression; note that entity types and namespaces cannot use `+` or `-` characters -- perhaps try `_` or `::` instead?"),
"actual error message was {e}"
)
}
)
}
}