Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cedar-policy-validator/src/human_schema/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use cedar_policy_core::{
ast::Id,
parser::{Loc, Node},
};
use itertools::Itertools;
use itertools::{Either, Itertools};
use nonempty::NonEmpty;
use smol_str::SmolStr;
// We don't need this import on macOS but CI fails without it
Expand Down Expand Up @@ -311,7 +311,7 @@ pub enum AppDecl {
/// Constraints on the `principal`` or `resource``
PR(PRAppDecl),
/// Constraints on the `context`
Context(Vec<Node<AttrDecl>>),
Context(Either<Path, Vec<Node<AttrDecl>>>),
}

/// An action declaration
Expand Down
20 changes: 16 additions & 4 deletions cedar-policy-validator/src/human_schema/grammar.lalrpop
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ Entity: Node<Declaration> = {
=> Node::with_source_loc(Declaration::Entity(EntityDecl { names: ets, member_of_types: ps.unwrap_or_default(), attrs: ds.map(|ds| ds.unwrap_or_default()).unwrap_or_default()}), Loc::new(l..r, Arc::clone(src))),
}

// Action := 'action' Names ['in' QualNameOrNames)]
// Action := 'action' Names ['in' QualNameOrNames]
Action: Node<Declaration> = {
<l:@L> ACTION <ns:Names> <ps:(IN <QualNameOrQualNames>)?> <ads:(APPLIESTO "{" <AppDecls> "}")?> <attrs:(ATTRIBUTES "{" "}")?>";" <r:@R>
=> Node::with_source_loc(Declaration::Action(ActionDecl { names: ns, parents: ps, app_decls: ads}), Loc::new(l..r, Arc::clone(src))),
Expand All @@ -108,7 +108,7 @@ TypeDecl: Node<Declaration> = {
}

// AppDecls := ('principal' | 'resource') ':' EntOrTyps [',' | ',' AppDecls]
// | 'context' ':' RecType [',' | ',' AppDecls]
// | 'context' ':' (Path | RecType) [',' | ',' AppDecls]
AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
<l:@L> <pr: PrincipalOrResource> ":" <ets:EntTypes> ","? <r:@R>
=>?
Expand All @@ -128,15 +128,27 @@ AppDecls: Node<NonEmpty<Node<AppDecl>>> = {
ds.insert(0, Node::with_source_loc(AppDecl::PR(PRAppDecl { kind:pr, entity_tys: ets}), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(ds, Loc::new(l..r, Arc::clone(src)))
}),
<l:@L> CONTEXT ":" <p:Path> ","? <r:@R>
=> Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" <p:Path> "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Left(p)), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
},
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" ","? <r:@R>
=>
Node::with_source_loc(
nonempty![Node::with_source_loc(AppDecl::Context(attrs.unwrap_or_default()), Loc::new(l..r, Arc::clone(src)))],
nonempty![Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src)))],
Loc::new(l..r, Arc::clone(src))),
<l:@L> CONTEXT ":" "{" <attrs:AttrDecls?> "}" "," <r:@R> <mut ds: AppDecls>
=> {
let (mut ds, _) = ds.into_inner();
ds.insert(0, Node::with_source_loc(AppDecl::Context(attrs.unwrap_or_default()), Loc::new(l..r, Arc::clone(src))));
ds.insert(0, Node::with_source_loc(AppDecl::Context(Either::Right(attrs.unwrap_or_default())), Loc::new(l..r, Arc::clone(src))));
Node::with_source_loc(
ds,
Loc::new(l..r, Arc::clone(src)))
Expand Down
57 changes: 57 additions & 0 deletions cedar-policy-validator/src/human_schema/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,63 @@ mod demo_tests {
assert!(as_src.contains(expected), "src was:\n`{as_src}`");
}

#[test]
fn context_is_common_type() {
assert!(SchemaFragment::from_str_natural(
r#"
type empty = {};
action "Foo" appliesTo {
context: empty,
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
type flag = { value: __cedar::Bool };
action "Foo" appliesTo {
context: flag
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
namespace Bar { type empty = {}; }
action "Foo" appliesTo {
context: Bar::empty
};
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
namespace Bar { type flag = { value: Bool }; }
namespace Baz {action "Foo" appliesTo {
context: Bar::flag
};}
"#
)
.is_ok());
assert!(SchemaFragment::from_str_natural(
r#"
type authcontext = {
ip: ipaddr,
is_authenticated: Bool,
timestamp: Long
};
entity Ticket {
who: String,
operation: Long,
request: authcontext
};
action view appliesTo { context: authcontext };
action upload appliesTo { context: authcontext };
"#
)
.is_ok());
}

#[test]
fn print_actions() {
let namespace = NamespaceDefinition {
Expand Down
30 changes: 23 additions & 7 deletions cedar-policy-validator/src/human_schema/to_json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ impl<'a> ConversionContext<'a> {
let context = contexts
.into_iter()
.at_most_one()
.map_err(|e| convert_context_error(e, loc.clone()))?
.map(|attrs| self.convert_attr_decls(attrs))
.map_err(|_| convert_context_error(loc.clone()))?
.map(|attrs| self.convert_context_decl(attrs))
.transpose()?
.unwrap_or_default();

Expand Down Expand Up @@ -291,6 +291,25 @@ impl<'a> ConversionContext<'a> {
)))
}

/// Create a context decl
fn convert_context_decl(
&self,
decl: Either<Path, Vec<Node<AttrDecl>>>,
) -> Result<AttributesOrContext, ToJsonSchemaErrors> {
Ok(AttributesOrContext(match decl {
Either::Left(p) => SchemaType::TypeDef {
type_name: p.to_smolstr(),
},
Either::Right(attrs) => SchemaType::Type(SchemaTypeVariant::Record {
attributes: collect_all_errors(
attrs.into_iter().map(|attr| self.convert_attr_decl(attr)),
)?
.collect(),
additional_attributes: false,
}),
}))
}

/// Convert an attribute type from an AttrDecl
fn convert_attr_decl(
&self,
Expand Down Expand Up @@ -402,18 +421,15 @@ fn convert_pr_error(
}

/// Wrap [`ExactlyOneError`] for the purpose of converting ContextDecls
fn convert_context_error(
_e: ExactlyOneError<std::vec::IntoIter<Vec<Node<AttrDecl>>>>,
loc: Loc,
) -> ToJsonSchemaError {
fn convert_context_error(loc: Loc) -> ToJsonSchemaError {
ToJsonSchemaError::DuplicateContext {
start: loc.clone(),
end: loc,
}
}

/// Partition on whether or not this [`AppDecl`] is defining a context
fn is_context_decl(n: Node<AppDecl>) -> Either<Vec<Node<AttrDecl>>, PRAppDecl> {
fn is_context_decl(n: Node<AppDecl>) -> Either<Either<Path, Vec<Node<AttrDecl>>>, PRAppDecl> {
match n.node {
AppDecl::PR(decl) => Either::Right(decl),
AppDecl::Context(attrs) => Either::Left(attrs),
Expand Down