cedar_policy_validator/schema/
raw_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 crate::schema::AllDefs;
18use crate::schema_errors::TypeNotDefinedError;
19use cedar_policy_core::ast::{Id, InternalName, Name, UnreservedId};
20use itertools::Itertools;
21use nonempty::{nonempty, NonEmpty};
22use serde::{Deserialize, Serialize};
23
24/// A newtype which indicates that the contained [`InternalName`] may not yet be
25/// fully-qualified.
26///
27/// You can convert it to a fully-qualified [`InternalName`] using
28/// `.qualify_with()`, `.qualify_with_name()`, or `.conditionally_qualify_with()`.
29#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
30#[serde(transparent)]
31#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
32pub struct RawName(InternalName);
33
34impl RawName {
35    /// Create a new [`RawName`] from the given [`Id`]
36    pub fn new(id: Id) -> Self {
37        Self(InternalName::unqualified_name(id))
38    }
39
40    /// Create a new [`RawName`] from the given [`UnreservedId`]
41    pub fn new_from_unreserved(id: UnreservedId) -> Self {
42        Self::new(id.into())
43    }
44
45    /// Create a new [`RawName`] from the given [`InternalName`].
46    ///
47    /// Note that if `name` includes explicit namespaces, the result will be a
48    /// [`RawName`] that also includes those explicit namespaces, as if that
49    /// fully-qualified name appeared directly in the (JSON or Cedar) schema
50    /// format.
51    /// If `name` does not include explicit namespaces, the result will be a
52    /// [`RawName`] that also does not include explicit namespaces, which may or
53    /// may not translate back to the original input `name`, due to
54    /// namespace-qualification rules.
55    pub fn from_name(name: InternalName) -> Self {
56        Self(name)
57    }
58
59    /// Create a new [`RawName`] by parsing the provided string, which should contain
60    /// an unqualified `InternalName` (no explicit namespaces)
61    pub fn parse_unqualified_name(
62        s: &str,
63    ) -> Result<Self, cedar_policy_core::parser::err::ParseErrors> {
64        InternalName::parse_unqualified_name(s).map(RawName)
65    }
66
67    /// Create a new [`RawName`] by parsing the provided string, which should contain
68    /// an `InternalName` in normalized form.
69    ///
70    /// (See the [`cedar_policy_core::FromNormalizedStr`] trait.)
71    pub fn from_normalized_str(
72        s: &str,
73    ) -> Result<Self, cedar_policy_core::parser::err::ParseErrors> {
74        use cedar_policy_core::FromNormalizedStr;
75        InternalName::from_normalized_str(s).map(RawName)
76    }
77
78    /// Is this `RawName` unqualified, that is, written without any _explicit_
79    /// namespaces.
80    /// (This method returning `true` does not imply that the `RawName` will
81    /// _eventually resolve_ to an unqualified name.)
82    pub fn is_unqualified(&self) -> bool {
83        self.0.is_unqualified()
84    }
85
86    /// Convert this [`RawName`] to an [`InternalName`] by adding the given `ns`
87    /// as its prefix, or by no-op if `ns` is `None`.
88    ///
89    /// Note that if the [`RawName`] already had a non-empty explicit namespace,
90    /// no additional prefixing will be done, even if `ns` is `Some`.
91    pub fn qualify_with(self, ns: Option<&InternalName>) -> InternalName {
92        self.0.qualify_with(ns)
93    }
94
95    /// Convert this [`RawName`] to an [`InternalName`] by adding the given `ns`
96    /// as its prefix, or by no-op if `ns` is `None`.
97    ///
98    /// Note that if the [`RawName`] already had a non-empty explicit namespace,
99    /// no additional prefixing will be done, even if `ns` is `Some`.
100    pub fn qualify_with_name(self, ns: Option<&Name>) -> InternalName {
101        self.0.qualify_with_name(ns)
102    }
103
104    /// Convert this [`RawName`] to a [`ConditionalName`].
105    /// This method is appropriate for when we encounter this [`RawName`] as a
106    /// type reference while the current/active namespace is `ns` (or `None` if
107    /// the current/active namespace is the empty namespace).
108    ///
109    /// This [`RawName`] will resolve as follows:
110    /// - If the [`RawName`] already has a non-empty explicit namespace, there
111    ///     is no ambiguity, and it will resolve always and only to itself
112    /// - Otherwise (if the [`RawName`] does not have an explicit namespace
113    ///     already), then it resolves to the following in priority order:
114    ///     1. The fully-qualified name resulting from prefixing `ns` to this
115    ///         [`RawName`], if that fully-qualified name is declared in the schema
116    ///         (in any schema fragment)
117    ///     2. Itself in the empty namespace, if that name is declared in the schema
118    ///         (in any schema fragment)
119    ///
120    /// Note that if the [`RawName`] is the name of a primitive or extension
121    /// type (without explicit `__cedar`), it will resolve via (2) above,
122    /// because the primitive/extension type names will be added as defined
123    /// common types in the empty namespace (aliasing to the real `__cedar`
124    /// definitions), assuming the user didn't themselves define those names
125    /// in the empty namespace.
126    pub fn conditionally_qualify_with(
127        self,
128        ns: Option<&InternalName>,
129        reference_type: ReferenceType,
130    ) -> ConditionalName {
131        let possibilities = if self.is_unqualified() {
132            match ns {
133                Some(ns) => {
134                    // the `RawName` does not have any namespace attached, so it refers
135                    // to something in the current namespace if available; otherwise, it
136                    // refers to something in the empty namespace
137                    nonempty![
138                        self.clone().qualify_with(Some(ns)),
139                        self.clone().qualify_with(None),
140                    ]
141                }
142                None => {
143                    // Same as the above case, but since the current/active
144                    // namespace is the empty namespace, the two possibilities
145                    // are the same; there is only one possibility
146                    nonempty![self.clone().qualify_with(None)]
147                }
148            }
149        } else {
150            // if the `RawName` already had an explicit namespace, there's no
151            // ambiguity
152            nonempty![self.clone().qualify_with(None)]
153        };
154        ConditionalName {
155            possibilities,
156            reference_type,
157            raw: self,
158        }
159    }
160}
161
162impl std::fmt::Display for RawName {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        write!(f, "{}", self.0)
165    }
166}
167
168impl std::str::FromStr for RawName {
169    type Err = <InternalName as std::str::FromStr>::Err;
170    fn from_str(s: &str) -> Result<Self, Self::Err> {
171        InternalName::from_str(s).map(RawName)
172    }
173}
174
175/// A name which may refer to many possible different fully-qualified names,
176/// depending on which of them are declared (in any schema fragment)
177///
178/// Caution using `==` on these: [`ConditionalName`]s are only equal if the have
179/// the same raw (source) _and_ the same list of possible resolution targets (in
180/// the same order), which in practice means they must be in the same
181/// current/active namespace. In particular:
182/// - two [`ConditionalName`]s which end up resolving to the same fully-qualified
183///   name may nonetheless not be `==` in their [`ConditionalName`] forms; and
184/// - two [`ConditionalName`]s which are written the same way in the original
185///   schema may nonetheless not be `==` in their [`ConditionalName`] forms
186///
187/// This type has only one (trivial) public constructor; it is normally
188/// constructed using [`RawName::conditionally_qualify_with()`].
189#[derive(Debug, Clone, PartialEq, Eq, Hash)]
190pub struct ConditionalName {
191    /// The [`ConditionalName`] may refer to any of these `possibilities`, depending
192    /// on which of them are declared (in any schema fragment).
193    ///
194    /// These are in descending priority order. If the first `InternalName` is
195    /// declared (in any schema fragment), then this `ConditionalName` refers to
196    /// the first `InternalName`. If that `InternalName` is not declared in any
197    /// schema fragment, then we check the second `InternalName`, etc.
198    ///
199    /// All of the contained `InternalName`s must be fully-qualified.
200    ///
201    /// Typical example: In
202    /// ```text
203    /// namespace NS { ... some reference to Foo ... }
204    /// ```
205    /// `Foo` is a `ConditionalName` with `possibilities = [NS::Foo, Foo]`.
206    /// That is, if `NS::Foo` exists, `Foo` refers to `NS::Foo`, but otherwise,
207    /// `Foo` refers to the `Foo` declared in the empty namespace.
208    possibilities: NonEmpty<InternalName>,
209    /// Whether the [`ConditionalName`] can resolve to a common-type name, an
210    /// entity-type name, or both
211    reference_type: ReferenceType,
212    /// Copy of the original/raw name found in the source; this field is
213    /// used only in error messages
214    raw: RawName,
215}
216
217impl ConditionalName {
218    /// Create a [`ConditionalName`] which unconditionally resolves to the given
219    /// fully-qualified [`InternalName`].
220    pub fn unconditional(name: InternalName, reference_type: ReferenceType) -> Self {
221        ConditionalName {
222            possibilities: nonempty!(name.clone()),
223            reference_type,
224            raw: RawName(name),
225        }
226    }
227
228    /// Get the (not-yet-necessarily-fully-qualified) [`RawName`] which was
229    /// encountered in the source, for the purposes of error messages
230    pub fn raw(&self) -> &RawName {
231        &self.raw
232    }
233
234    /// Get the possible fully-qualified [`InternalName`]s which this [`ConditionalName`]
235    /// might resolve to, in priority order (highest-priority first).
236    pub(crate) fn possibilities(&self) -> impl Iterator<Item = &InternalName> {
237        self.possibilities.iter()
238    }
239
240    /// Resolve the [`ConditionalName`] into a fully-qualified [`InternalName`],
241    /// given that `all_defs` includes all fully-qualified [`InternalName`]s
242    /// defined in all schema fragments.
243    ///
244    /// Note that this returns [`InternalName`] (as opposed to [`Name`]),
245    /// because type references may resolve to an internal name like
246    /// `__cedar::String`.
247    /// In general, as noted on [`InternalName`], [`InternalName`]s are valid
248    /// to appear as type _references_, and we generally expect
249    /// [`ConditionalName`]s to also represent type _references_.
250    ///
251    /// `all_defs` also internally includes [`InternalName`]s, because some
252    /// names containing `__cedar` might be internally defined/valid, even
253    /// though it is not valid for _end-users_ to define those names.
254    pub fn resolve(self, all_defs: &AllDefs) -> Result<InternalName, TypeNotDefinedError> {
255        for possibility in &self.possibilities {
256            // Per RFC 24, we give priority to trying to resolve to a common
257            // type, before trying to resolve to an entity type.
258            // (However, we have an even stronger preference to resolve earlier
259            // in the `possibilities` list. So, in the hypothetical case where
260            // we could resolve to either an entity type first in the
261            // `possibilities` list, or a common type later in the
262            // `possibilities` list, we choose the former.)
263            // See also cedar#579.
264            if matches!(
265                self.reference_type,
266                ReferenceType::Common | ReferenceType::CommonOrEntity
267            ) && all_defs.is_defined_as_common(possibility)
268            {
269                return Ok(possibility.clone());
270            }
271            if matches!(
272                self.reference_type,
273                ReferenceType::Entity | ReferenceType::CommonOrEntity
274            ) && all_defs.is_defined_as_entity(possibility)
275            {
276                return Ok(possibility.clone());
277            }
278        }
279        Err(TypeNotDefinedError(nonempty![self]))
280    }
281
282    /// Provide a help message for the case where this [`ConditionalName`] failed to resolve
283    pub(crate) fn resolution_failure_help(&self) -> String {
284        let entity_or_common_text = match self.reference_type {
285            ReferenceType::Common => "as a common type",
286            ReferenceType::Entity => "as an entity type",
287            ReferenceType::CommonOrEntity => "as a common or entity type",
288        };
289        // PANIC SAFETY: indexing is safe because we first check the `.len()`
290        #[allow(clippy::indexing_slicing)]
291        match self.possibilities.len() {
292            1 => format!(
293                "`{}` has not been declared {}",
294                self.possibilities[0], entity_or_common_text
295            ),
296            2 => format!(
297                "neither `{}` nor `{}` refers to anything that has been declared {}",
298                self.possibilities[0], self.possibilities[1], entity_or_common_text,
299            ),
300            _ => format!(
301                "none of these have been declared {}: {}",
302                entity_or_common_text,
303                self.possibilities
304                    .iter()
305                    .map(|p| format!("`{p}`"))
306                    .join(", ")
307            ),
308        }
309    }
310}
311
312/// [`ConditionalName`] serializes as simply the raw name that was originally encountered in the schema
313impl Serialize for ConditionalName {
314    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
315    where
316        S: serde::Serializer,
317    {
318        self.raw().serialize(serializer)
319    }
320}
321
322/// Describes whether a reference can resolve to a common-type name, an
323/// entity-type name, or both
324#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
325pub enum ReferenceType {
326    /// The reference can only resolve to a common-type name
327    Common,
328    /// The reference can only resolve to an entity-type name
329    Entity,
330    /// The reference can resolve to either an entity or common type name
331    CommonOrEntity,
332}