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