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}