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.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#[allow(clippy::module_name_repetitions)]
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    #[allow(clippy::result_large_err)]
258    pub fn from_json(json: serde_json::Value) -> Result<Self, JsonDeserializationError> {
259        let parsed: cedar_policy_core::entities::EntityUidJson = serde_json::from_value(json)?;
260        Ok(parsed
261            .into_euid(|| JsonDeserializationErrorContext::EntityUid)?
262            .into())
263    }
264}
265
266#[cfg(test)]
267impl EntityUid {
268    /// Testing utility for creating `EntityUids` a bit easier
269    pub(crate) fn from_strs(typename: &str, id: &str) -> Self {
270        Self::from_type_name_and_id(
271            EntityTypeName::from_str(typename).unwrap(),
272            EntityId::from_str(id).unwrap(),
273        )
274    }
275}
276
277impl FromStr for EntityUid {
278    type Err = ParseErrors;
279
280    /// Parse an [`EntityUid`].
281    ///
282    /// An [`EntityUid`] consists of an [`EntityTypeName`] followed by a quoted [`EntityId`].
283    /// The two are joined by a `::`.
284    /// For the formal grammar, see <https://docs.cedarpolicy.com/policies/syntax-grammar.html#entity>
285    ///
286    /// Examples:
287    /// ```
288    ///  # use cedar_policy::EntityUid;
289    ///  let euid: EntityUid = r#"Foo::Bar::"george""#.parse().unwrap();
290    ///  // Get the type of this euid (`Foo::Bar`)
291    ///  euid.type_name();
292    ///  // Or the id
293    ///  euid.id();
294    /// ```
295    ///
296    /// This [`FromStr`] implementation requires the _normalized_ representation of the
297    /// UID. See <https://github.com/cedar-policy/rfcs/pull/9/>.
298    ///
299    /// A note on safety:
300    ///
301    /// __DO NOT__ create [`EntityUid`]'s via string concatenation.
302    /// If you have separate components of an [`EntityUid`], use [`EntityUid::from_type_name_and_id`]
303    fn from_str(uid_str: &str) -> Result<Self, Self::Err> {
304        ast::EntityUID::from_normalized_str(uid_str)
305            .map(Into::into)
306            .map_err(Into::into)
307    }
308}
309
310impl std::fmt::Display for EntityUid {
311    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312        write!(f, "{}", self.0)
313    }
314}
315
316#[doc(hidden)]
317impl From<EntityUid> for ast::EntityUID {
318    fn from(uid: EntityUid) -> Self {
319        uid.0
320    }
321}
322
323#[doc(hidden)]
324impl From<ast::EntityUID> for EntityUid {
325    fn from(uid: ast::EntityUID) -> Self {
326        Self(uid)
327    }
328}
329
330/// Unique ids assigned to policies and templates.
331///
332/// A [`PolicyId`] can can be constructed using [`PolicyId::new`] or by calling
333/// `parse()` on a string. The `parse()` implementation is [`Infallible`], so
334/// the parsed [`EntityId`] can be extracted safely.
335/// Examples:
336/// ```
337/// # use cedar_policy::PolicyId;
338/// let id = PolicyId::new("my-id");
339/// let id : PolicyId = "my-id".parse().unwrap_or_else(|never| match never {});
340/// # assert_eq!(AsRef::<str>::as_ref(&id), "my-id");
341/// ```
342#[repr(transparent)]
343#[allow(clippy::module_name_repetitions)]
344#[derive(Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord, Serialize, Deserialize, RefCast)]
345#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
346#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
347pub struct PolicyId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::PolicyID);
348
349#[doc(hidden)] // because this converts to a private/internal type
350impl AsRef<ast::PolicyID> for PolicyId {
351    fn as_ref(&self) -> &ast::PolicyID {
352        &self.0
353    }
354}
355
356impl PolicyId {
357    /// Construct a [`PolicyId`] from a source string
358    pub fn new(id: impl AsRef<str>) -> Self {
359        Self(ast::PolicyID::from_string(id.as_ref()))
360    }
361}
362
363impl FromStr for PolicyId {
364    type Err = Infallible;
365
366    /// Create a `PolicyId` from a string. Currently always returns `Ok()`.
367    fn from_str(id: &str) -> Result<Self, Self::Err> {
368        Ok(Self(ast::PolicyID::from_string(id)))
369    }
370}
371
372impl std::fmt::Display for PolicyId {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        write!(f, "{}", self.0)
375    }
376}
377
378impl AsRef<str> for PolicyId {
379    fn as_ref(&self) -> &str {
380        self.0.as_ref()
381    }
382}
383
384#[doc(hidden)]
385impl From<PolicyId> for ast::PolicyID {
386    fn from(uid: PolicyId) -> Self {
387        uid.0
388    }
389}
390
391/// Identifier for a Template slot
392#[repr(transparent)]
393#[allow(clippy::module_name_repetitions)]
394#[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Hash, RefCast, Serialize, Deserialize)]
395#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
396#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
397pub struct SlotId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::SlotId);
398
399#[doc(hidden)] // because this converts to a private/internal type
400impl AsRef<ast::SlotId> for SlotId {
401    fn as_ref(&self) -> &ast::SlotId {
402        &self.0
403    }
404}
405
406impl SlotId {
407    /// Get the slot for `principal`
408    pub fn principal() -> Self {
409        Self(ast::SlotId::principal())
410    }
411
412    /// Get the slot for `resource`
413    pub fn resource() -> Self {
414        Self(ast::SlotId::resource())
415    }
416}
417
418impl std::fmt::Display for SlotId {
419    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
420        write!(f, "{}", self.0)
421    }
422}
423
424#[doc(hidden)]
425impl From<ast::SlotId> for SlotId {
426    fn from(a: ast::SlotId) -> Self {
427        Self(a)
428    }
429}
430
431#[doc(hidden)]
432impl From<SlotId> for ast::SlotId {
433    fn from(s: SlotId) -> Self {
434        s.0
435    }
436}