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}