cedar_policy_core/ast/
name.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
17use super::{id::Id, PrincipalOrResource, UnreservedId};
18use educe::Educe;
19use itertools::Itertools;
20use miette::Diagnostic;
21use ref_cast::RefCast;
22use serde::{Deserialize, Deserializer, Serialize, Serializer};
23use smol_str::ToSmolStr;
24use std::fmt::Display;
25use std::str::FromStr;
26use std::sync::Arc;
27use thiserror::Error;
28
29use crate::parser::err::{ParseError, ParseErrors, ToASTError};
30use crate::parser::Loc;
31use crate::FromNormalizedStr;
32
33/// Represents the name of an entity type, function, etc.
34/// The name may include namespaces.
35/// Clone is O(1).
36///
37/// This type may contain any name valid for use internally, including names
38/// with reserved `__cedar` components (and also names without `__cedar`).
39#[derive(Educe, Debug, Clone)]
40#[educe(PartialEq, Eq, Hash, PartialOrd, Ord)]
41pub struct InternalName {
42    /// Basename
43    pub(crate) id: Id,
44    /// Namespaces
45    pub(crate) path: Arc<Vec<Id>>,
46    /// Location of the name in source
47    #[educe(PartialEq(ignore))]
48    #[educe(Hash(ignore))]
49    #[educe(PartialOrd(ignore))]
50    pub(crate) loc: Option<Loc>,
51}
52
53/// A shortcut for [`InternalName::unqualified_name`]
54impl From<Id> for InternalName {
55    fn from(value: Id) -> Self {
56        Self::unqualified_name(value)
57    }
58}
59
60/// Convert a [`InternalName`] to an [`Id`]
61/// The error type is the unit type because the reason the conversion fails
62/// is obvious
63impl TryFrom<InternalName> for Id {
64    type Error = ();
65    fn try_from(value: InternalName) -> Result<Self, Self::Error> {
66        if value.is_unqualified() {
67            Ok(value.id)
68        } else {
69            Err(())
70        }
71    }
72}
73
74impl InternalName {
75    /// A full constructor for [`InternalName`]
76    pub fn new(basename: Id, path: impl IntoIterator<Item = Id>, loc: Option<Loc>) -> Self {
77        Self {
78            id: basename,
79            path: Arc::new(path.into_iter().collect()),
80            loc,
81        }
82    }
83
84    /// Create an [`InternalName`] with no path (no namespaces).
85    pub fn unqualified_name(id: Id) -> Self {
86        Self {
87            id,
88            path: Arc::new(vec![]),
89            loc: None,
90        }
91    }
92
93    /// Get the [`InternalName`] representing the reserved `__cedar` namespace
94    pub fn __cedar() -> Self {
95        // using `Id::new_unchecked()` for performance reasons -- this function is called many times by validator code
96        Self::unqualified_name(Id::new_unchecked("__cedar"))
97    }
98
99    /// Create an [`InternalName`] with no path (no namespaces).
100    /// Returns an error if `s` is not a valid identifier.
101    pub fn parse_unqualified_name(s: &str) -> Result<Self, ParseErrors> {
102        Ok(Self {
103            id: s.parse()?,
104            path: Arc::new(vec![]),
105            loc: None,
106        })
107    }
108
109    /// Given a type basename and a namespace (as an [`InternalName`] itself),
110    /// return an [`InternalName`] representing the type's fully qualified name
111    pub fn type_in_namespace(
112        basename: Id,
113        namespace: InternalName,
114        loc: Option<Loc>,
115    ) -> InternalName {
116        let mut path = Arc::unwrap_or_clone(namespace.path);
117        path.push(namespace.id);
118        InternalName::new(basename, path, loc)
119    }
120
121    /// Get the source location
122    pub fn loc(&self) -> Option<&Loc> {
123        self.loc.as_ref()
124    }
125
126    /// Get the basename of the [`InternalName`] (ie, with namespaces stripped).
127    pub fn basename(&self) -> &Id {
128        &self.id
129    }
130
131    /// Get the namespace of the [`InternalName`], as components
132    pub fn namespace_components(&self) -> impl Iterator<Item = &Id> {
133        self.path.iter()
134    }
135
136    /// Get the full namespace of the [`InternalName`], as a single string.
137    ///
138    /// Examples:
139    /// - `foo::bar` --> the namespace is `"foo"`
140    /// - `bar` --> the namespace is `""`
141    /// - `foo::bar::baz` --> the namespace is `"foo::bar"`
142    pub fn namespace(&self) -> String {
143        self.path.iter().join("::")
144    }
145
146    /// Qualify the name with a namespace
147    ///
148    /// If the name already has a non-empty namespace, this method does not
149    /// apply any prefix and instead returns a copy of `self`.
150    ///
151    /// If `namespace` is `None`, that represents the empty namespace, so no
152    /// prefixing will be done.
153    ///
154    /// If the name does not already have an explicit namespace (i.e., it's
155    /// just a single `Id`), this applies `namespace` as a prefix (if it is
156    /// present).
157    ///
158    /// Examples:
159    /// - `A::B`.qualify_with(None) is `A::B`
160    /// - `A::B`.qualify_with(Some(C)) is also `A::B`
161    /// - `A`.qualify_with(None) is `A`
162    /// - `A`.qualify_with(Some(C)) is `C::A`
163    /// - `A`.qualify_with(Some(B::C)) is `B::C::A`
164    pub fn qualify_with(&self, namespace: Option<&InternalName>) -> InternalName {
165        if self.is_unqualified() {
166            match namespace {
167                Some(namespace) => Self::new(
168                    self.basename().clone(),
169                    namespace
170                        .namespace_components()
171                        .chain(std::iter::once(namespace.basename()))
172                        .cloned(),
173                    self.loc.clone(),
174                ),
175                None => self.clone(),
176            }
177        } else {
178            self.clone()
179        }
180    }
181
182    /// Like `qualify_with()`, but accepts a [`Name`] as the namespace to qualify with
183    pub fn qualify_with_name(&self, namespace: Option<&Name>) -> InternalName {
184        let ns = namespace.map(AsRef::as_ref);
185        self.qualify_with(ns)
186    }
187
188    /// Test if an [`InternalName`] is an [`Id`]
189    pub fn is_unqualified(&self) -> bool {
190        self.path.is_empty()
191    }
192
193    /// Test if an [`InternalName`] is reserved
194    /// i.e., any of its components matches `__cedar`
195    pub fn is_reserved(&self) -> bool {
196        self.path
197            .iter()
198            .chain(std::iter::once(&self.id))
199            .any(|id| id.is_reserved())
200    }
201}
202
203impl std::fmt::Display for InternalName {
204    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205        for elem in self.path.as_ref() {
206            write!(f, "{}::", elem)?;
207        }
208        write!(f, "{}", self.id)?;
209        Ok(())
210    }
211}
212
213/// Serialize an [`InternalName`] using its `Display` implementation
214/// This serialization implementation is used in the JSON schema format.
215impl Serialize for InternalName {
216    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
217    where
218        S: Serializer,
219    {
220        self.to_smolstr().serialize(serializer)
221    }
222}
223
224// allow `.parse()` on a string to make an [`InternalName`]
225impl std::str::FromStr for InternalName {
226    type Err = ParseErrors;
227
228    fn from_str(s: &str) -> Result<Self, Self::Err> {
229        crate::parser::parse_internal_name(s)
230    }
231}
232
233impl FromNormalizedStr for InternalName {
234    fn describe_self() -> &'static str {
235        "internal name"
236    }
237}
238
239#[cfg(feature = "arbitrary")]
240impl<'a> arbitrary::Arbitrary<'a> for InternalName {
241    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
242        let path_size = u.int_in_range(0..=8)?;
243        Ok(Self {
244            id: u.arbitrary()?,
245            path: Arc::new(
246                (0..path_size)
247                    .map(|_| u.arbitrary())
248                    .collect::<Result<Vec<Id>, _>>()?,
249            ),
250            loc: None,
251        })
252    }
253}
254
255struct NameVisitor;
256
257impl serde::de::Visitor<'_> for NameVisitor {
258    type Value = InternalName;
259
260    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
261        formatter.write_str("a name consisting of an optional namespace and id")
262    }
263
264    fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
265    where
266        E: serde::de::Error,
267    {
268        InternalName::from_normalized_str(value)
269            .map_err(|err| serde::de::Error::custom(format!("invalid name `{value}`: {err}")))
270    }
271}
272
273/// Deserialize an [`InternalName`] using `from_normalized_str`.
274/// This deserialization implementation is used in the JSON schema format.
275impl<'de> Deserialize<'de> for InternalName {
276    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
277    where
278        D: Deserializer<'de>,
279    {
280        deserializer.deserialize_str(NameVisitor)
281    }
282}
283
284/// Identifier for a slot
285/// Clone is O(1).
286// This simply wraps a separate enum -- currently [`ValidSlotId`] -- in case we
287// want to generalize later
288#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
289#[serde(transparent)]
290pub struct SlotId(pub(crate) ValidSlotId);
291
292impl SlotId {
293    /// Get the slot for `principal`
294    pub fn principal() -> Self {
295        Self(ValidSlotId::Principal)
296    }
297
298    /// Get the slot for `resource`
299    pub fn resource() -> Self {
300        Self(ValidSlotId::Resource)
301    }
302
303    /// Check if a slot represents a principal
304    pub fn is_principal(&self) -> bool {
305        matches!(self, Self(ValidSlotId::Principal))
306    }
307
308    /// Check if a slot represents a resource
309    pub fn is_resource(&self) -> bool {
310        matches!(self, Self(ValidSlotId::Resource))
311    }
312}
313
314impl From<PrincipalOrResource> for SlotId {
315    fn from(v: PrincipalOrResource) -> Self {
316        match v {
317            PrincipalOrResource::Principal => SlotId::principal(),
318            PrincipalOrResource::Resource => SlotId::resource(),
319        }
320    }
321}
322
323impl std::fmt::Display for SlotId {
324    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325        write!(f, "{}", self.0)
326    }
327}
328
329/// Two possible variants for Slots
330#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
331pub(crate) enum ValidSlotId {
332    #[serde(rename = "?principal")]
333    Principal,
334    #[serde(rename = "?resource")]
335    Resource,
336}
337
338impl std::fmt::Display for ValidSlotId {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        let s = match self {
341            ValidSlotId::Principal => "principal",
342            ValidSlotId::Resource => "resource",
343        };
344        write!(f, "?{s}")
345    }
346}
347
348/// [`SlotId`] plus a source location
349#[derive(Educe, Debug, Clone)]
350#[educe(PartialEq, Eq, Hash)]
351pub struct Slot {
352    /// [`SlotId`]
353    pub id: SlotId,
354    /// Source location, if available
355    #[educe(PartialEq(ignore))]
356    #[educe(Hash(ignore))]
357    pub loc: Option<Loc>,
358}
359
360#[cfg(test)]
361mod vars_test {
362    use super::*;
363    // Make sure the vars always parse correctly
364    #[test]
365    fn vars_correct() {
366        SlotId::principal();
367        SlotId::resource();
368    }
369
370    #[test]
371    fn display() {
372        assert_eq!(format!("{}", SlotId::principal()), "?principal")
373    }
374}
375
376/// A new type which indicates that the contained [`InternalName`] does not
377/// contain reserved `__cedar`, as specified by RFC 52.
378/// This represents names which are legal for end-users to _define_, while
379/// [`InternalName`] represents names which are legal for end-users to
380/// _reference_.
381#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, RefCast)]
382#[repr(transparent)]
383#[serde(transparent)]
384pub struct Name(pub(crate) InternalName);
385
386impl From<UnreservedId> for Name {
387    fn from(value: UnreservedId) -> Self {
388        Self::unqualified_name(value)
389    }
390}
391
392impl TryFrom<Name> for UnreservedId {
393    type Error = ();
394    fn try_from(value: Name) -> Result<Self, Self::Error> {
395        if value.0.is_unqualified() {
396            Ok(value.basename())
397        } else {
398            Err(())
399        }
400    }
401}
402
403impl Display for Name {
404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
405        self.0.fmt(f)
406    }
407}
408
409impl FromStr for Name {
410    type Err = ParseErrors;
411    fn from_str(s: &str) -> Result<Self, Self::Err> {
412        let n: InternalName = s.parse()?;
413        n.try_into().map_err(ParseErrors::singleton)
414    }
415}
416
417impl FromNormalizedStr for Name {
418    fn describe_self() -> &'static str {
419        "Name"
420    }
421}
422
423/// Deserialize a [`Name`] using `from_normalized_str`
424/// This deserialization implementation is used in the JSON schema format.
425impl<'de> Deserialize<'de> for Name {
426    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
427    where
428        D: Deserializer<'de>,
429    {
430        deserializer
431            .deserialize_str(NameVisitor)
432            .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
433    }
434}
435
436impl Name {
437    /// Create a [`Name`] with no path (no namespaces).
438    /// Returns an error if `s` is not a valid identifier.
439    pub fn parse_unqualified_name(s: &str) -> Result<Self, ParseErrors> {
440        InternalName::parse_unqualified_name(s)
441            .and_then(|n| n.try_into().map_err(ParseErrors::singleton))
442    }
443
444    /// Create a [`Name`] with no path (no namespaces).
445    pub fn unqualified_name(id: UnreservedId) -> Self {
446        // This is safe (upholds the `Name` invariant) because `id` must be an `UnreservedId`
447        Self(InternalName::unqualified_name(id.0))
448    }
449
450    /// Get the basename of the [`Name`] (ie, with namespaces stripped).
451    /// Return a reference to [`Id`]
452    pub fn basename_as_ref(&self) -> &Id {
453        self.0.basename()
454    }
455
456    /// Get the basename of the [`Name`] (ie, with namespaces stripped).
457    /// Return an [`UnreservedId`]
458    pub fn basename(&self) -> UnreservedId {
459        // PANIC SAFETY: Any component of a `Name` is a `UnreservedId`
460        #![allow(clippy::unwrap_used)]
461        self.0.basename().clone().try_into().unwrap()
462    }
463
464    /// Test if a [`Name`] is an [`UnreservedId`]
465    pub fn is_unqualified(&self) -> bool {
466        self.0.is_unqualified()
467    }
468
469    /// Qualify the name with an optional namespace
470    ///
471    /// This method has the same behavior as [`InternalName::qualify_with()`]
472    pub fn qualify_with(&self, namespace: Option<&InternalName>) -> InternalName {
473        self.0.qualify_with(namespace)
474    }
475
476    /// Qualify the name with an optional namespace
477    ///
478    /// This method has the same behavior as [`InternalName::qualify_with_name()`] except that
479    /// it's guaranteed to return [`Name`], not [`InternalName`]
480    pub fn qualify_with_name(&self, namespace: Option<&Self>) -> Self {
481        // This is safe (upholds the `Name` invariant) because both `self` and `namespace`
482        // cannot contain `__cedar` -- they were already `Name`s
483        Self(self.as_ref().qualify_with(namespace.map(|n| n.as_ref())))
484    }
485
486    /// Get the source location
487    pub fn loc(&self) -> Option<&Loc> {
488        self.0.loc()
489    }
490}
491
492/// Error when a reserved name is used where it is not allowed
493#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic, Hash)]
494#[error("The name `{0}` contains `__cedar`, which is reserved")]
495pub struct ReservedNameError(pub(crate) InternalName);
496
497impl ReservedNameError {
498    /// The [`InternalName`] which contained a reserved component
499    pub fn name(&self) -> &InternalName {
500        &self.0
501    }
502}
503
504impl From<ReservedNameError> for ParseError {
505    fn from(value: ReservedNameError) -> Self {
506        ParseError::ToAST(ToASTError::new(
507            value.clone().into(),
508            match &value.0.loc {
509                Some(loc) => loc.clone(),
510                None => {
511                    let name_str = value.0.to_string();
512                    Loc::new(0..(name_str.len()), name_str.into())
513                }
514            },
515        ))
516    }
517}
518
519impl TryFrom<InternalName> for Name {
520    type Error = ReservedNameError;
521    fn try_from(value: InternalName) -> Result<Self, Self::Error> {
522        if value.is_reserved() {
523            Err(ReservedNameError(value))
524        } else {
525            Ok(Self(value))
526        }
527    }
528}
529
530impl<'a> TryFrom<&'a InternalName> for &'a Name {
531    type Error = ReservedNameError;
532    fn try_from(value: &'a InternalName) -> Result<&'a Name, ReservedNameError> {
533        if value.is_reserved() {
534            Err(ReservedNameError(value.clone()))
535        } else {
536            Ok(<Name as RefCast>::ref_cast(value))
537        }
538    }
539}
540
541impl From<Name> for InternalName {
542    fn from(value: Name) -> Self {
543        value.0
544    }
545}
546
547impl AsRef<InternalName> for Name {
548    fn as_ref(&self) -> &InternalName {
549        &self.0
550    }
551}
552
553#[cfg(feature = "arbitrary")]
554impl<'a> arbitrary::Arbitrary<'a> for Name {
555    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
556        // Computing hash of long id strings can be expensive
557        // Hence we limit the size of `path` such that DRT does not report slow
558        // units
559        let path_size = u.int_in_range(0..=8)?;
560        let basename: UnreservedId = u.arbitrary()?;
561        let path: Vec<UnreservedId> = (0..path_size)
562            .map(|_| u.arbitrary())
563            .collect::<Result<Vec<_>, _>>()?;
564        let name = InternalName::new(basename.into(), path.into_iter().map(|id| id.into()), None);
565        // PANIC SAFETY: `name` is made of `UnreservedId`s and thus should be a valid `Name`
566        #[allow(clippy::unwrap_used)]
567        Ok(name.try_into().unwrap())
568    }
569
570    fn size_hint(depth: usize) -> (usize, Option<usize>) {
571        <InternalName as arbitrary::Arbitrary>::size_hint(depth)
572    }
573}
574
575#[cfg(test)]
576mod test {
577    use super::*;
578
579    #[test]
580    fn normalized_name() {
581        InternalName::from_normalized_str("foo").expect("should be OK");
582        InternalName::from_normalized_str("foo::bar").expect("should be OK");
583        InternalName::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
584        InternalName::from_normalized_str(" foo").expect_err("shouldn't be OK");
585        InternalName::from_normalized_str("foo ").expect_err("shouldn't be OK");
586        InternalName::from_normalized_str("foo\n").expect_err("shouldn't be OK");
587        InternalName::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
588    }
589
590    #[test]
591    fn qualify_with() {
592        assert_eq!(
593            "foo::bar::baz",
594            InternalName::from_normalized_str("baz")
595                .unwrap()
596                .qualify_with(Some(&"foo::bar".parse().unwrap()))
597                .to_smolstr()
598        );
599        assert_eq!(
600            "C::D",
601            InternalName::from_normalized_str("C::D")
602                .unwrap()
603                .qualify_with(Some(&"A::B".parse().unwrap()))
604                .to_smolstr()
605        );
606        assert_eq!(
607            "A::B::C::D",
608            InternalName::from_normalized_str("D")
609                .unwrap()
610                .qualify_with(Some(&"A::B::C".parse().unwrap()))
611                .to_smolstr()
612        );
613        assert_eq!(
614            "B::C::D",
615            InternalName::from_normalized_str("B::C::D")
616                .unwrap()
617                .qualify_with(Some(&"A".parse().unwrap()))
618                .to_smolstr()
619        );
620        assert_eq!(
621            "A",
622            InternalName::from_normalized_str("A")
623                .unwrap()
624                .qualify_with(None)
625                .to_smolstr()
626        )
627    }
628
629    #[test]
630    fn test_reserved() {
631        for n in [
632            "__cedar",
633            "__cedar::A",
634            "__cedar::A::B",
635            "A::__cedar",
636            "A::__cedar::B",
637        ] {
638            assert!(InternalName::from_normalized_str(n).unwrap().is_reserved());
639        }
640
641        for n in ["__cedarr", "A::_cedar", "A::___cedar::B"] {
642            assert!(!InternalName::from_normalized_str(n).unwrap().is_reserved());
643        }
644    }
645}