scufflecloud_core_cedar/
lib.rs

1use std::borrow::Borrow;
2use std::collections::BTreeMap;
3
4use ext_traits::ResultExt;
5use serde::ser::SerializeMap;
6
7pub trait CedarEntityId: CedarEntity {
8    type Id<'a>: CedarIdentifiable
9    where
10        Self: 'a;
11
12    fn id(&self) -> impl std::borrow::Borrow<Self::Id<'_>>;
13}
14
15impl<T: CedarEntityId> CedarIdentifiable for T {
16    const ENTITY_TYPE: EntityTypeName = T::Id::ENTITY_TYPE;
17
18    fn entity_id(&self) -> cedar_policy::EntityId {
19        self.id().borrow().entity_id()
20    }
21
22    fn entity_uid(&self) -> JsonEntityUid {
23        self.id().borrow().entity_uid()
24    }
25}
26
27#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub struct JsonEntityUid {
29    pub type_name: EntityTypeName,
30    pub id: cedar_policy::EntityId,
31}
32
33impl From<JsonEntityUid> for cedar_policy::EntityUid {
34    fn from(value: JsonEntityUid) -> Self {
35        cedar_policy::EntityUid::from_type_name_and_id(value.type_name.as_str().parse().unwrap(), value.id)
36    }
37}
38
39impl serde::Serialize for JsonEntityUid {
40    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
41    where
42        S: serde::Serializer,
43    {
44        let mut map = serializer.serialize_map(Some(2))?;
45        map.serialize_entry("type", self.type_name.as_str())?;
46        map.serialize_entry("id", self.id.unescaped())?;
47        map.end()
48    }
49}
50
51pub trait CedarIdentifiable {
52    /// MUST be a normalized cedar entity type name.
53    ///
54    /// See [`cedar_policy::EntityTypeName`] and <https://github.com/cedar-policy/rfcs/blob/main/text/0009-disallow-whitespace-in-entityuid.md>.
55    const ENTITY_TYPE: EntityTypeName;
56
57    fn entity_id(&self) -> cedar_policy::EntityId;
58
59    fn entity_uid(&self) -> JsonEntityUid {
60        JsonEntityUid {
61            type_name: Self::ENTITY_TYPE,
62            id: self.entity_id(),
63        }
64    }
65}
66
67pub trait CedarEntity: CedarIdentifiable + serde::Serialize + Send + Sync {
68    fn parents(
69        &self,
70        global: &impl core_traits::Global,
71    ) -> impl Future<Output = Result<impl IntoIterator<Item = JsonEntityUid>, tonic::Status>> + Send {
72        let _ = global;
73        std::future::ready(Ok([]))
74    }
75
76    fn additional_attributes(
77        &self,
78        global: &impl core_traits::Global,
79    ) -> impl Future<Output = Result<impl serde::Serialize, tonic::Status>> + Send {
80        let _ = global;
81        std::future::ready(Ok(BTreeMap::<(), ()>::new()))
82    }
83
84    /// Returns the attributes of the entity as a map.
85    /// Also includes additional attributes from [`additional_attributes`](Self::additional_attributes).
86    fn attributes(
87        &self,
88        global: &impl core_traits::Global,
89    ) -> impl std::future::Future<Output = Result<impl serde::Serialize, tonic::Status>> + Send {
90        #[derive(serde_derive::Serialize)]
91        struct Merged<A, B> {
92            #[serde(flatten)]
93            a: A,
94            #[serde(flatten)]
95            b: B,
96        }
97
98        async move {
99            Ok(Merged {
100                a: self,
101                b: self.additional_attributes(global).await?,
102            })
103        }
104    }
105
106    fn to_entity(
107        &self,
108        global: &impl core_traits::Global,
109        schema: Option<&cedar_policy::Schema>,
110    ) -> impl std::future::Future<Output = Result<cedar_policy::Entity, tonic::Status>> + Send {
111        async move {
112            let value = serde_json::json!({
113                "uid": self.entity_uid(),
114                "attrs": self.attributes(global).await?,
115                "parents": self.parents(global).await?.into_iter().collect::<Vec<_>>(),
116            });
117
118            cedar_policy::Entity::from_json_value(value, schema).into_tonic_internal_err("failed to create cedar entity")
119        }
120    }
121}
122
123pub use entity_type_name::EntityTypeName;
124
125mod entity_type_name;
126mod macros;
127mod models;