Skip to main content

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