Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable generation of arbitrary annotations and test their equivalence #501

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
102 changes: 102 additions & 0 deletions cedar-drt/fuzz/src/schemas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,40 @@ impl<'a, T: Equiv> Equiv for &'a T {
}
}

impl Equiv for cedar_policy_core::est::Annotations {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.0, &rhs.0)
}
}

impl Equiv for Option<cedar_policy_core::ast::Annotation> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
match (lhs, rhs) {
(Some(a), Some(b)) => {
if a == b {
return Ok(());
}
}
(Some(a), None) | (None, Some(a)) => {
if a.val.is_empty() {
return Ok(());
}
}
(None, None) => return Ok(()),
};
Err(format!("two annotations mismatch: {lhs:?} and {rhs:?}"))
shaobo-he-aws marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv
for json_schema::NamespaceDefinition<N>
{
fn equiv(
lhs: &json_schema::NamespaceDefinition<N>,
rhs: &json_schema::NamespaceDefinition<N>,
) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in namespace annotations: {e}"))?;
Equiv::equiv(&lhs.entity_types, &rhs.entity_types)
.map_err(|e| format!("mismatch in entity type declarations: {e}"))?;
Equiv::equiv(&lhs.common_types, &rhs.common_types)
Expand Down Expand Up @@ -191,12 +218,16 @@ impl<K: Eq + Ord + Display, V: Equiv> Equiv for BTreeMap<K, V> {

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv for json_schema::CommonType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in common type annotations: {e}"))?;
Equiv::equiv(&lhs.ty, &rhs.ty)
}
}

impl<N: Clone + PartialEq + Debug + Display + TypeName + Ord> Equiv for json_schema::EntityType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in entity annotations: {e}"))?;
Equiv::equiv(
&lhs.member_of_types.iter().collect::<BTreeSet<_>>(),
&rhs.member_of_types.iter().collect::<BTreeSet<_>>(),
Expand Down Expand Up @@ -226,6 +257,8 @@ impl Equiv for cedar_policy_validator::ValidatorEntityType {

impl<N: Clone + PartialEq + TypeName + Debug + Display> Equiv for json_schema::TypeOfAttribute<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in type of attribute annotations: {e}"))?;
if lhs.required != rhs.required {
return Err("attributes differ in required flag".into());
}
Expand Down Expand Up @@ -434,6 +467,8 @@ impl TypeName for InternalName {

impl<N: PartialEq + Debug + Display + Clone + TypeName + Ord> Equiv for json_schema::ActionType<N> {
fn equiv(lhs: &Self, rhs: &Self) -> Result<(), String> {
Equiv::equiv(&lhs.annotations, &rhs.annotations)
.map_err(|e| format!("mismatch in action annotations: {e}"))?;
if &lhs.attributes != &rhs.attributes
&& !(lhs.attributes.as_ref().is_none_or(HashMap::is_empty)
&& rhs.attributes.as_ref().is_none_or(HashMap::is_empty))
Expand Down Expand Up @@ -517,3 +552,70 @@ impl Equiv for cedar_policy_validator::ValidatorSchema {
Ok(())
}
}

#[cfg(test)]
mod tests {
use cedar_drt::est::Annotations;

use crate::schemas::Equiv;

#[test]
fn annotations() {
// positive cases
let pairs: [(Annotations, Annotations)] = [
// value being null is equivalent to an empty string
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
),
// both values being null is also equivalent
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
),
// otherwise compare non-null values
(
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨", "b": "🥯🍩"})).unwrap(),
serde_json::from_value(serde_json::json!({"b": "🥯🍩", "a": "🥨"})).unwrap(),
),
];
pairs
.iter()
.for_each(|(a, b)| assert!(Equiv::equiv(a, b).is_ok()));

// negative cases
let pairs: [(Annotations, Annotations)] = [
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🍪"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": ""})).unwrap(),
serde_json::from_value(serde_json::json!({"b": null})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": null})).unwrap(),
serde_json::from_value(serde_json::json!({"b": null})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨"})).unwrap(),
serde_json::from_value(serde_json::json!({"a": "🍪"})).unwrap(),
),
(
serde_json::from_value(serde_json::json!({"a": "🥨", "b": "🥯🍪"})).unwrap(),
serde_json::from_value(serde_json::json!({"b": "🥯🍩", "a": "🥨"})).unwrap(),
),
];
pairs
.iter()
.for_each(|(a, b)| assert!(Equiv::equiv(a, b).is_err()));
}
}
21 changes: 6 additions & 15 deletions cedar-policy-generators/src/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@ use crate::hierarchy::Hierarchy;
use crate::size_hint_utils::size_hint_for_ratio;
use arbitrary::{Arbitrary, Unstructured};
use cedar_policy_core::ast::{
Annotation, Annotations, AnyId, Effect, EntityUID, Expr, Policy, PolicyID, PolicySet,
StaticPolicy, Template,
Effect, EntityUID, Expr, Policy, PolicyID, PolicySet, StaticPolicy, Template,
};
use cedar_policy_core::{ast, est};
use serde::Serialize;
use smol_str::SmolStr;
use std::fmt::Display;
use std::sync::Arc;

Expand All @@ -39,7 +37,7 @@ use std::sync::Arc;
#[serde(into = "est::Policy")]
pub struct GeneratedPolicy {
id: PolicyID,
annotations: HashMap<AnyId, SmolStr>,
annotations: cedar_policy_core::est::Annotations,
effect: Effect,
principal_constraint: PrincipalOrResourceConstraint,
action_constraint: ActionConstraint,
Expand All @@ -59,7 +57,7 @@ impl GeneratedPolicy {
/// Create a new `GeneratedPolicy` with these fields
pub fn new(
id: PolicyID,
annotations: impl IntoIterator<Item = (AnyId, SmolStr)>,
annotations: cedar_policy_core::est::Annotations,
effect: Effect,
principal_constraint: PrincipalOrResourceConstraint,
action_constraint: ActionConstraint,
Expand All @@ -68,7 +66,7 @@ impl GeneratedPolicy {
) -> Self {
Self {
id,
annotations: annotations.into_iter().collect(),
annotations,
effect,
principal_constraint,
action_constraint,
Expand Down Expand Up @@ -141,19 +139,12 @@ impl GeneratedPolicy {
}
}

fn convert_annotations(annotations: HashMap<AnyId, SmolStr>) -> Annotations {
annotations
.into_iter()
.map(|(k, v)| (k, Annotation { val: v, loc: None }))
.collect()
}

impl From<GeneratedPolicy> for StaticPolicy {
fn from(gen: GeneratedPolicy) -> StaticPolicy {
StaticPolicy::new(
gen.id,
None,
convert_annotations(gen.annotations),
gen.annotations.into(),
gen.effect,
gen.principal_constraint.into(),
gen.action_constraint.into(),
Expand All @@ -169,7 +160,7 @@ impl From<GeneratedPolicy> for Template {
Template::new(
gen.id,
None,
convert_annotations(gen.annotations),
gen.annotations.into(),
gen.effect,
gen.principal_constraint.into(),
gen.action_constraint.into(),
Expand Down
25 changes: 12 additions & 13 deletions cedar-policy-generators/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ use crate::size_hint_utils::{size_hint_for_choose, size_hint_for_range, size_hin
use crate::{accum, gen, gen_inner, uniform};
use arbitrary::{self, Arbitrary, MaxRecursionReached, Unstructured};
use cedar_policy_core::ast::{self, Effect, PolicyID, UnreservedId};
use cedar_policy_core::est::Annotations;
use cedar_policy_core::extensions::Extensions;
use cedar_policy_validator::json_schema::{CommonType, CommonTypeId};
use cedar_policy_validator::{
Expand Down Expand Up @@ -153,15 +152,16 @@ fn arbitrary_typeofattribute_with_bounded_depth<N: From<ast::Name>>(
Ok(json_schema::TypeOfAttribute {
ty: arbitrary_schematype_with_bounded_depth::<N>(settings, entity_types, max_depth, u)?,
required: u.arbitrary()?,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
})
}
/// size hint for arbitrary_typeofattribute_with_bounded_depth
fn arbitrary_typeofattribute_size_hint(depth: usize) -> (usize, Option<usize>) {
arbitrary::size_hint::and(
arbitrary::size_hint::and_all(&[
arbitrary_schematype_size_hint(depth),
<bool as Arbitrary>::size_hint(depth),
)
<cedar_policy_core::est::Annotations as Arbitrary>::size_hint(depth),
])
}

/// internal helper function, an alternative to the `Arbitrary` impl for
Expand Down Expand Up @@ -710,15 +710,15 @@ impl Schema {
common_types: common_types
.into_iter()
.map(|(id, ty)| {
(
Ok((
id,
CommonType {
ty,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
)
))
})
.collect(),
.collect::<Result<BTreeMap<_, _>>>()?,
entity_types: entity_types.into(),
actions: actions.into(),
annotations: self.schema.annotations.clone(),
Expand Down Expand Up @@ -931,7 +931,7 @@ impl Schema {
u
)?)
),
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
))
})
Expand Down Expand Up @@ -1034,7 +1034,7 @@ impl Schema {
},
//TODO: Fuzz arbitrary attribute names and values.
attributes: None,
annotations: Annotations::new(),
annotations: u.arbitrary()?,
},
))
})
Expand Down Expand Up @@ -1064,7 +1064,7 @@ impl Schema {
common_types: BTreeMap::new().into(),
entity_types: entity_types.into_iter().collect(),
actions: actions.into_iter().collect(),
annotations: Annotations::new(),
annotations: u.arbitrary()?,
};
let attrsorcontexts /* : impl Iterator<Item = &AttributesOrContext> */ = nsdef.entity_types.values().map(|et| attrs_from_attrs_or_context(&nsdef, &et.shape))
.chain(nsdef.actions.iter().filter_map(|(_, action)| action.applies_to.as_ref()).map(|a| attrs_from_attrs_or_context(&nsdef, &a.context)));
Expand Down Expand Up @@ -1361,7 +1361,6 @@ impl Schema {
u: &mut Unstructured<'_>,
) -> Result<ABACPolicy> {
let id = u.arbitrary()?;
let annotations: HashMap<ast::AnyId, SmolStr> = u.arbitrary()?;
let effect = u.arbitrary()?;
let principal_constraint = self.arbitrary_principal_constraint(hierarchy, u)?;
let action_constraint = self.arbitrary_action_constraint(u, Some(3))?;
Expand All @@ -1386,7 +1385,7 @@ impl Schema {
}
Ok(ABACPolicy(GeneratedPolicy::new(
id,
annotations,
u.arbitrary()?,
effect,
principal_constraint,
action_constraint,
Expand Down
Loading