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