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}