cedar_policy_core/ast/
name.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 std::sync::Arc;
18
19use itertools::Itertools;
20use serde::{Deserialize, Serialize};
21use smol_str::SmolStr;
22
23use super::PrincipalOrResource;
24use crate::parser::err::ParseError;
25
26/// This is the `Name` type used to name types, functions, etc.
27/// The name can include namespaces.
28/// Clone is O(1).
29#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
30#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
31pub struct Name {
32    /// Basename
33    pub(crate) id: Id,
34    /// Namespaces
35    pub(crate) path: Arc<Vec<Id>>,
36}
37
38impl Name {
39    /// A full constructor for `Name`
40    pub fn new(id: Id, path: impl IntoIterator<Item = Id>) -> Self {
41        Self {
42            id,
43            path: Arc::new(path.into_iter().collect()),
44        }
45    }
46
47    /// Create a `Name` with no path (no namespaces).
48    pub fn unqualified_name(id: Id) -> Self {
49        Self {
50            id,
51            path: Arc::new(vec![]),
52        }
53    }
54
55    /// Create a `Name` with no path (no namespaces).
56    /// Returns an error if `s` is not a valid identifier.
57    pub fn parse_unqualified_name(s: &str) -> Result<Self, Vec<ParseError>> {
58        Ok(Self {
59            id: s.parse()?,
60            path: Arc::new(vec![]),
61        })
62    }
63
64    /// Get the basename of the `Name` (ie, with namespaces stripped).
65    pub fn basename(&self) -> &Id {
66        &self.id
67    }
68
69    /// Get the namespace of the `Name`, as components
70    pub fn namespace_components(&self) -> impl Iterator<Item = &Id> {
71        self.path.iter()
72    }
73
74    /// Get the full namespace of the `Name`, as a single string.
75    ///
76    /// Examples:
77    /// - `foo::bar` --> the namespace is `"foo"`
78    /// - `bar` --> the namespace is `""`
79    /// - `foo::bar::baz` --> the namespace is `"foo::bar"`
80    pub fn namespace(&self) -> String {
81        self.path.iter().join("::")
82    }
83}
84
85impl std::fmt::Display for Name {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        for elem in self.path.as_ref() {
88            write!(f, "{}::", elem)?;
89        }
90        write!(f, "{}", self.id)?;
91        Ok(())
92    }
93}
94
95// allow `.parse()` on a string to make a `Name`
96impl std::str::FromStr for Name {
97    type Err = Vec<ParseError>;
98
99    fn from_str(s: &str) -> Result<Self, Vec<ParseError>> {
100        crate::parser::parse_name(s)
101    }
102}
103
104/// Identifier for a slot
105/// Clone is O(1).
106// This simply wraps a separate enum -- currently `ValidSlotId` -- in case we
107// want to generalize later
108#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
109#[serde(transparent)]
110pub struct SlotId(ValidSlotId);
111
112impl SlotId {
113    /// Get the slot for `principal`
114    pub fn principal() -> Self {
115        Self(ValidSlotId::Principal)
116    }
117
118    /// Get the slot for `resource`
119    pub fn resource() -> Self {
120        Self(ValidSlotId::Resource)
121    }
122
123    /// Check if a slot represents a principal
124    pub fn is_principal(&self) -> bool {
125        matches!(self, Self(ValidSlotId::Principal))
126    }
127
128    /// Check if a slot represents a resource
129    pub fn is_resource(&self) -> bool {
130        matches!(self, Self(ValidSlotId::Resource))
131    }
132}
133
134impl From<PrincipalOrResource> for SlotId {
135    fn from(v: PrincipalOrResource) -> Self {
136        match v {
137            PrincipalOrResource::Principal => SlotId::principal(),
138            PrincipalOrResource::Resource => SlotId::resource(),
139        }
140    }
141}
142
143impl std::fmt::Display for SlotId {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(f, "{}", self.0)
146    }
147}
148
149/// Two possible variants for Slots
150#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
151enum ValidSlotId {
152    #[serde(rename = "?principal")]
153    Principal,
154    #[serde(rename = "?resource")]
155    Resource,
156}
157
158impl std::fmt::Display for ValidSlotId {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        let s = match self {
161            ValidSlotId::Principal => "principal",
162            ValidSlotId::Resource => "resource",
163        };
164        write!(f, "?{s}")
165    }
166}
167
168#[cfg(test)]
169mod test {
170    use super::*;
171    // Make sure the vars always parse correctly
172    #[test]
173    fn vars_correct() {
174        SlotId::principal();
175        SlotId::resource();
176    }
177
178    #[test]
179    fn display() {
180        assert_eq!(format!("{}", SlotId::principal()), "?principal")
181    }
182}
183
184/// Identifiers. Anything in `Id` should be a valid identifier (and not contain,
185/// for instance, spaces or characters like '+').
186//
187// For now, internally, `Id`s are just owned `SmolString`s.
188#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
189pub struct Id(SmolStr);
190
191impl Id {
192    /// Create a new `Id` from a `String`, where it is the caller's
193    /// responsibility to ensure that the string is indeed a valid identifier.
194    ///
195    /// When possible, callers should not use this, and instead use `s.parse()`,
196    /// which checks that `s` is a valid identifier, and returns a parse error
197    /// if not.
198    ///
199    /// This method was created for the `From<cst::Ident> for Id` impl to use.
200    /// Since `parser::parse_ident()` implicitly uses that `From` impl itself,
201    /// if we tried to make that `From` impl go through `.parse()` like everyone
202    /// else, we'd get infinite recursion.  And, we assert that `cst::Ident` is
203    /// always already checked to contain a valid identifier, otherwise it would
204    /// never have been created.
205    pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> Id {
206        Id(s.into())
207    }
208
209    /// Get the underlying string
210    pub fn to_smolstr(self) -> SmolStr {
211        self.0
212    }
213}
214
215impl AsRef<str> for Id {
216    fn as_ref(&self) -> &str {
217        &self.0
218    }
219}
220
221impl std::fmt::Display for Id {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        write!(f, "{}", &self.0)
224    }
225}
226
227// allow `.parse()` on a string to make an `Id`
228impl std::str::FromStr for Id {
229    type Err = Vec<ParseError>;
230
231    fn from_str(s: &str) -> Result<Self, Vec<ParseError>> {
232        crate::parser::parse_ident(s)
233    }
234}
235
236#[cfg(fuzzing)]
237impl<'a> arbitrary::Arbitrary<'a> for Id {
238    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
239        // identifier syntax:
240        // IDENT     := ['_''a'-'z''A'-'Z']['_''a'-'z''A'-'Z''0'-'9']* - RESERVED
241        // BOOL      := 'true' | 'false'
242        // RESERVED  := BOOL | 'if' | 'then' | 'else' | 'in' | 'like' | 'has'
243
244        let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
245        let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
246        // the set of the first character of an identifier
247        let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
248        // the set of the remaining characters of an identifier
249        let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
250        // identifier character count minus 1
251        let remaining_length = u.int_in_range(0..=16)?;
252        let mut cs = vec![*u.choose(&head_letters)?];
253        cs.extend(
254            (0..remaining_length)
255                .map(|_| u.choose(&tail_letters))
256                .collect::<Result<Vec<&char>, _>>()?,
257        );
258        let mut s: String = cs.into_iter().collect();
259        // Should the parsing fails, the string should be reserved word.
260        // Append a `_` to create a valid Id.
261        if crate::parser::parse_ident(&s).is_err() {
262            s.push('_');
263        }
264        Ok(Self::new_unchecked(s))
265    }
266
267    fn size_hint(depth: usize) -> (usize, Option<usize>) {
268        arbitrary::size_hint::and_all(&[
269            // for arbitrary length
270            <usize as arbitrary::Arbitrary>::size_hint(depth),
271            // for arbitrary choices
272            // we use the size hint of a vector of `u8` to get an underestimate of bytes required by the sequence of choices.
273            <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
274        ])
275    }
276}