cedar_policy/api/
id.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module defines the publicly exported identifier types including
18//! `EntityUid` and `PolicyId`.
19
20use crate::entities_json_errors::JsonDeserializationError;
21use crate::ParseErrors;
22use cedar_policy_core::ast;
23use cedar_policy_core::entities::json::err::JsonDeserializationErrorContext;
24use cedar_policy_core::FromNormalizedStr;
25use ref_cast::RefCast;
26use serde::{Deserialize, Serialize};
27use smol_str::SmolStr;
28use std::convert::Infallible;
29use std::str::FromStr;
30
31/// Identifier portion of the [`EntityUid`] type.
32///
33/// All strings are valid [`EntityId`]s, and can be
34/// constructed either using [`EntityId::new`]
35/// or by using the implementation of [`FromStr`]. This implementation is [`Infallible`], so the
36/// parsed [`EntityId`] can be extracted safely.
37///
38/// ```
39/// # use cedar_policy::EntityId;
40/// let id : EntityId = "my-id".parse().unwrap_or_else(|never| match never {});
41/// # assert_eq!(id.as_ref(), "my-id");
42/// ```
43///
44/// `EntityId` does not implement `Display`, partly because it is unclear
45/// whether `Display` should produce an escaped representation or an unescaped
46/// representation (see [#884](https://github.com/cedar-policy/cedar/issues/884)).
47/// To get an escaped representation, use `.escaped()`.
48/// To get an unescaped representation, use `.as_ref()`.
49#[repr(transparent)]
50#[allow(clippy::module_name_repetitions)]
51#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
52pub struct EntityId(ast::Eid);
53
54impl EntityId {
55    /// Construct an [`EntityId`] from a source string
56    pub fn new(src: impl AsRef<str>) -> Self {
57        match src.as_ref().parse() {
58            Ok(eid) => eid,
59            Err(infallible) => match infallible {},
60        }
61    }
62
63    /// Get the contents of the `EntityId` as an escaped string
64    pub fn escaped(&self) -> SmolStr {
65        self.0.escaped()
66    }
67}
68
69impl FromStr for EntityId {
70    type Err = Infallible;
71    fn from_str(eid_str: &str) -> Result<Self, Self::Err> {
72        Ok(Self(ast::Eid::new(eid_str)))
73    }
74}
75
76impl AsRef<str> for EntityId {
77    fn as_ref(&self) -> &str {
78        self.0.as_ref()
79    }
80}
81
82/// Represents an entity type name. Consists of a namespace and the type name.
83///
84/// An `EntityTypeName` can can be constructed using
85/// [`EntityTypeName::from_str`] or by calling `parse()` on a string. Unlike
86/// [`EntityId::from_str`], _this can fail_, so it is important to properly
87/// handle an `Err` result.
88///
89/// ```
90/// # use cedar_policy::EntityTypeName;
91/// let id : Result<EntityTypeName, _> = "Namespace::Type".parse();
92/// # let id = id.unwrap();
93/// # assert_eq!(id.basename(), "Type");
94/// # assert_eq!(id.namespace(), "Namespace");
95/// ```
96#[repr(transparent)]
97#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
98pub struct EntityTypeName(pub(crate) ast::EntityType);
99
100impl EntityTypeName {
101    /// Get the basename of the `EntityTypeName` (ie, with namespaces stripped).
102    /// ```
103    /// # use cedar_policy::EntityTypeName;
104    /// # use std::str::FromStr;
105    /// let type_name = EntityTypeName::from_str("MySpace::User").unwrap();
106    /// assert_eq!(type_name.basename(), "User");
107    /// ```
108    pub fn basename(&self) -> &str {
109        self.0.as_ref().basename_as_ref().as_ref()
110    }
111
112    /// Get the namespace of the `EntityTypeName`, as components
113    /// ```
114    /// # use cedar_policy::EntityTypeName;
115    /// # use std::str::FromStr;
116    /// let type_name = EntityTypeName::from_str("Namespace::MySpace::User").unwrap();
117    /// let mut components = type_name.namespace_components();
118    /// assert_eq!(components.next(), Some("Namespace"));
119    /// assert_eq!(components.next(), Some("MySpace"));
120    /// assert_eq!(components.next(), None);
121    /// ```
122    pub fn namespace_components(&self) -> impl Iterator<Item = &str> {
123        self.0
124            .name()
125            .as_ref()
126            .namespace_components()
127            .map(AsRef::as_ref)
128    }
129
130    /// Get the full namespace of the `EntityTypeName`, as a single string.
131    /// ```
132    /// # use cedar_policy::EntityTypeName;
133    /// # use std::str::FromStr;
134    /// let type_name = EntityTypeName::from_str("Namespace::MySpace::User").unwrap();
135    /// let components = type_name.namespace();
136    /// assert_eq!(components,"Namespace::MySpace");
137    /// ```
138    pub fn namespace(&self) -> String {
139        self.0.as_ref().as_ref().namespace()
140    }
141}
142
143/// This `FromStr` implementation requires the _normalized_ representation of the
144/// type name. See <https://github.com/cedar-policy/rfcs/pull/9/>.
145impl FromStr for EntityTypeName {
146    type Err = ParseErrors;
147
148    fn from_str(namespace_type_str: &str) -> Result<Self, Self::Err> {
149        ast::Name::from_normalized_str(namespace_type_str)
150            .map(|name| Self(ast::EntityType::from(name)))
151            .map_err(Into::into)
152    }
153}
154
155impl std::fmt::Display for EntityTypeName {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        write!(f, "{}", self.0)
158    }
159}
160
161#[doc(hidden)]
162impl From<ast::EntityType> for EntityTypeName {
163    fn from(ty: ast::EntityType) -> Self {
164        Self(ty)
165    }
166}
167
168/// Unique id for an entity, such as `User::"alice"`.
169///
170/// An `EntityUid` contains an [`EntityTypeName`] and [`EntityId`]. It can
171/// be constructed from these components using
172/// [`EntityUid::from_type_name_and_id`], parsed from a string using `.parse()`
173/// (via [`EntityUid::from_str`]), or constructed from a JSON value using
174/// [`EntityUid::from_json`].
175///
176// INVARIANT: this can never be an `ast::EntityType::Unspecified`
177#[repr(transparent)]
178#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, RefCast)]
179pub struct EntityUid(pub(crate) ast::EntityUID);
180
181impl EntityUid {
182    /// Returns the portion of the Euid that represents namespace and entity type
183    /// ```
184    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
185    /// # use std::str::FromStr;
186    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "alice" } });
187    /// let euid = EntityUid::from_json(json_data).unwrap();
188    /// assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
189    /// ```
190    pub fn type_name(&self) -> &EntityTypeName {
191        EntityTypeName::ref_cast(self.0.entity_type())
192    }
193
194    /// Returns the id portion of the Euid
195    /// ```
196    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
197    /// # use std::str::FromStr;
198    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "alice" } });
199    /// let euid = EntityUid::from_json(json_data).unwrap();
200    /// assert_eq!(euid.id(), &EntityId::from_str("alice").unwrap());
201    /// ```
202    pub fn id(&self) -> &EntityId {
203        EntityId::ref_cast(self.0.eid())
204    }
205
206    /// Creates `EntityUid` from `EntityTypeName` and `EntityId`
207    ///```
208    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
209    /// # use std::str::FromStr;
210    /// let eid = EntityId::from_str("alice").unwrap();
211    /// let type_name: EntityTypeName = EntityTypeName::from_str("User").unwrap();
212    /// let euid = EntityUid::from_type_name_and_id(type_name, eid);
213    /// # assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
214    /// # assert_eq!(euid.id(), &EntityId::from_str("alice").unwrap());
215    /// ```
216    pub fn from_type_name_and_id(name: EntityTypeName, id: EntityId) -> Self {
217        // INVARIANT: `from_components` always constructs a Concrete id
218        Self(ast::EntityUID::from_components(name.0, id.0, None))
219    }
220
221    /// Creates `EntityUid` from a JSON value, which should have
222    /// either the implicit or explicit `__entity` form.
223    /// ```
224    /// # use cedar_policy::{Entity, EntityId, EntityTypeName, EntityUid};
225    /// # use std::str::FromStr;
226    /// let json_data = serde_json::json!({ "__entity": { "type": "User", "id": "123abc" } });
227    /// let euid = EntityUid::from_json(json_data).unwrap();
228    /// # assert_eq!(euid.type_name(), &EntityTypeName::from_str("User").unwrap());
229    /// # assert_eq!(euid.id(), &EntityId::from_str("123abc").unwrap());
230    /// ```
231    #[allow(clippy::result_large_err)]
232    pub fn from_json(json: serde_json::Value) -> Result<Self, JsonDeserializationError> {
233        let parsed: cedar_policy_core::entities::EntityUidJson = serde_json::from_value(json)?;
234        Ok(parsed
235            .into_euid(|| JsonDeserializationErrorContext::EntityUid)?
236            .into())
237    }
238
239    /// Testing utility for creating `EntityUids` a bit easier
240    #[cfg(test)]
241    pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
242        Self::from_type_name_and_id(
243            EntityTypeName::from_str(typename).unwrap(),
244            EntityId::from_str(id).unwrap(),
245        )
246    }
247}
248
249impl FromStr for EntityUid {
250    type Err = ParseErrors;
251
252    /// Parse an [`EntityUid`].
253    ///
254    /// An [`EntityUid`] consists of an [`EntityTypeName`] followed by a quoted [`EntityId`].
255    /// The two are joined by a `::`.
256    /// For the formal grammar, see <https://docs.cedarpolicy.com/policies/syntax-grammar.html#entity>
257    ///
258    /// Examples:
259    /// ```
260    ///  # use cedar_policy::EntityUid;
261    ///  let euid: EntityUid = r#"Foo::Bar::"george""#.parse().unwrap();
262    ///  // Get the type of this euid (`Foo::Bar`)
263    ///  euid.type_name();
264    ///  // Or the id
265    ///  euid.id();
266    /// ```
267    ///
268    /// This [`FromStr`] implementation requires the _normalized_ representation of the
269    /// UID. See <https://github.com/cedar-policy/rfcs/pull/9/>.
270    ///
271    /// A note on safety:
272    ///
273    /// __DO NOT__ create [`EntityUid`]'s via string concatenation.
274    /// If you have separate components of an [`EntityUid`], use [`EntityUid::from_type_name_and_id`]
275    fn from_str(uid_str: &str) -> Result<Self, Self::Err> {
276        ast::EntityUID::from_normalized_str(uid_str)
277            .map(Into::into)
278            .map_err(Into::into)
279    }
280}
281
282impl std::fmt::Display for EntityUid {
283    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284        write!(f, "{}", self.0)
285    }
286}
287
288#[doc(hidden)]
289impl AsRef<ast::EntityUID> for EntityUid {
290    fn as_ref(&self) -> &ast::EntityUID {
291        &self.0
292    }
293}
294
295#[doc(hidden)]
296impl From<EntityUid> for ast::EntityUID {
297    fn from(uid: EntityUid) -> Self {
298        uid.0
299    }
300}
301
302#[doc(hidden)]
303impl From<ast::EntityUID> for EntityUid {
304    fn from(uid: ast::EntityUID) -> Self {
305        Self(uid)
306    }
307}
308
309/// Unique ids assigned to policies and templates.
310///
311/// A [`PolicyId`] can can be constructed using [`PolicyId::new`] or by calling
312/// `parse()` on a string. The `parse()` implementation is [`Infallible`], so
313/// the parsed [`EntityId`] can be extracted safely.
314/// Examples:
315/// ```
316/// # use cedar_policy::PolicyId;
317/// let id = PolicyId::new("my-id");
318/// let id : PolicyId = "my-id".parse().unwrap_or_else(|never| match never {});
319/// # assert_eq!(AsRef::<str>::as_ref(&id), "my-id");
320/// ```
321#[repr(transparent)]
322#[allow(clippy::module_name_repetitions)]
323#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize, Deserialize, RefCast)]
324#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
325#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
326pub struct PolicyId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::PolicyID);
327
328impl PolicyId {
329    /// Construct a [`PolicyId`] from a source string
330    pub fn new(id: impl AsRef<str>) -> Self {
331        Self(ast::PolicyID::from_string(id.as_ref()))
332    }
333}
334
335impl FromStr for PolicyId {
336    type Err = Infallible;
337
338    /// Create a `PolicyId` from a string. Currently always returns `Ok()`.
339    fn from_str(id: &str) -> Result<Self, Self::Err> {
340        Ok(Self(ast::PolicyID::from_string(id)))
341    }
342}
343
344impl std::fmt::Display for PolicyId {
345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
346        write!(f, "{}", self.0)
347    }
348}
349
350impl AsRef<str> for PolicyId {
351    fn as_ref(&self) -> &str {
352        self.0.as_ref()
353    }
354}
355
356#[doc(hidden)]
357impl AsRef<ast::PolicyID> for PolicyId {
358    fn as_ref(&self) -> &ast::PolicyID {
359        &self.0
360    }
361}
362
363#[doc(hidden)]
364impl From<PolicyId> for ast::PolicyID {
365    fn from(uid: PolicyId) -> Self {
366        uid.0
367    }
368}
369
370/// Identifier for a Template slot
371#[repr(transparent)]
372#[allow(clippy::module_name_repetitions)]
373#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, RefCast, Serialize, Deserialize)]
374#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
375#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
376pub struct SlotId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::SlotId);
377
378impl SlotId {
379    /// Get the slot for `principal`
380    pub fn principal() -> Self {
381        Self(ast::SlotId::principal())
382    }
383
384    /// Get the slot for `resource`
385    pub fn resource() -> Self {
386        Self(ast::SlotId::resource())
387    }
388}
389
390impl std::fmt::Display for SlotId {
391    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
392        write!(f, "{}", self.0)
393    }
394}
395
396#[doc(hidden)]
397impl From<ast::SlotId> for SlotId {
398    fn from(a: ast::SlotId) -> Self {
399        Self(a)
400    }
401}
402
403#[doc(hidden)]
404impl From<SlotId> for ast::SlotId {
405    fn from(s: SlotId) -> Self {
406        s.0
407    }
408}