cedar_policy_core/ast/
id.rs1use serde::{Deserialize, Deserializer, Serialize};
18use smol_str::SmolStr;
19
20use crate::{parser::err::ParseErrors, FromNormalizedStr};
21
22use super::{InternalName, ReservedNameError};
23
24const RESERVED_ID: &str = "__cedar";
25
26#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
33pub struct Id(SmolStr);
34
35impl Id {
36 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> Id {
50 Id(s.into())
51 }
52
53 pub fn into_smolstr(self) -> SmolStr {
55 self.0
56 }
57
58 pub fn is_reserved(&self) -> bool {
62 self.as_ref() == RESERVED_ID
63 }
64}
65
66impl AsRef<str> for Id {
67 fn as_ref(&self) -> &str {
68 &self.0
69 }
70}
71
72impl std::fmt::Display for Id {
73 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
74 write!(f, "{}", &self.0)
75 }
76}
77
78impl std::str::FromStr for Id {
80 type Err = ParseErrors;
81
82 fn from_str(s: &str) -> Result<Self, Self::Err> {
83 crate::parser::parse_ident(s)
84 }
85}
86
87impl FromNormalizedStr for Id {
88 fn describe_self() -> &'static str {
89 "Id"
90 }
91}
92
93#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
95#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
96#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
97pub struct UnreservedId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] pub(crate) Id);
98
99impl From<UnreservedId> for Id {
100 fn from(value: UnreservedId) -> Self {
101 value.0
102 }
103}
104
105impl TryFrom<Id> for UnreservedId {
106 type Error = ReservedNameError;
107 fn try_from(value: Id) -> Result<Self, Self::Error> {
108 if value.is_reserved() {
109 Err(ReservedNameError(InternalName::unqualified_name(value)))
110 } else {
111 Ok(Self(value))
112 }
113 }
114}
115
116impl AsRef<Id> for UnreservedId {
117 fn as_ref(&self) -> &Id {
118 &self.0
119 }
120}
121
122impl AsRef<str> for UnreservedId {
123 fn as_ref(&self) -> &str {
124 self.0.as_ref()
125 }
126}
127
128impl std::fmt::Display for UnreservedId {
129 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130 self.0.fmt(f)
131 }
132}
133
134impl std::str::FromStr for UnreservedId {
135 type Err = ParseErrors;
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 Id::from_str(s).and_then(|id| id.try_into().map_err(ParseErrors::singleton))
138 }
139}
140
141impl FromNormalizedStr for UnreservedId {
142 fn describe_self() -> &'static str {
143 "Unreserved Id"
144 }
145}
146
147impl UnreservedId {
148 pub(crate) fn empty() -> Self {
150 #[allow(clippy::unwrap_used)]
152 Id("".into()).try_into().unwrap()
153 }
154}
155
156struct IdVisitor;
157
158impl serde::de::Visitor<'_> for IdVisitor {
159 type Value = Id;
160
161 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 formatter.write_str("a valid id")
163 }
164
165 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
166 where
167 E: serde::de::Error,
168 {
169 Id::from_normalized_str(value)
170 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
171 }
172}
173
174impl<'de> Deserialize<'de> for Id {
177 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
178 where
179 D: Deserializer<'de>,
180 {
181 deserializer.deserialize_str(IdVisitor)
182 }
183}
184
185impl<'de> Deserialize<'de> for UnreservedId {
188 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
189 where
190 D: Deserializer<'de>,
191 {
192 deserializer
193 .deserialize_str(IdVisitor)
194 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
195 }
196}
197
198#[cfg(feature = "arbitrary")]
199impl<'a> arbitrary::Arbitrary<'a> for Id {
200 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
201 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
207 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
208 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
210 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
212 let remaining_length = u.int_in_range(0..=16)?;
214 let mut cs = vec![*u.choose(&head_letters)?];
215 cs.extend(
216 (0..remaining_length)
217 .map(|_| u.choose(&tail_letters))
218 .collect::<Result<Vec<&char>, _>>()?,
219 );
220 let mut s: String = cs.into_iter().collect();
221 if crate::parser::parse_ident(&s).is_err() {
224 s.push('_');
225 }
226 Ok(Self::new_unchecked(s))
227 }
228
229 fn size_hint(depth: usize) -> (usize, Option<usize>) {
230 arbitrary::size_hint::and_all(&[
231 <usize as arbitrary::Arbitrary>::size_hint(depth),
233 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
236 ])
237 }
238}
239
240#[cfg(feature = "arbitrary")]
241impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
242 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
243 let id: Id = u.arbitrary()?;
244 match UnreservedId::try_from(id.clone()) {
245 Ok(id) => Ok(id),
246 Err(_) => {
247 #[allow(clippy::unwrap_used)]
249 let new_id = format!("_{id}").parse().unwrap();
250 Ok(new_id)
251 }
252 }
253 }
254
255 fn size_hint(depth: usize) -> (usize, Option<usize>) {
256 <Id as arbitrary::Arbitrary>::size_hint(depth)
257 }
258}
259
260#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
265#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
266#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
267pub struct AnyId(SmolStr);
268
269impl AnyId {
270 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
280 AnyId(s.into())
281 }
282
283 pub fn into_smolstr(self) -> SmolStr {
285 self.0
286 }
287}
288
289struct AnyIdVisitor;
290
291impl serde::de::Visitor<'_> for AnyIdVisitor {
292 type Value = AnyId;
293
294 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
295 formatter.write_str("any id")
296 }
297
298 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
299 where
300 E: serde::de::Error,
301 {
302 AnyId::from_normalized_str(value)
303 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
304 }
305}
306
307impl<'de> Deserialize<'de> for AnyId {
310 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
311 where
312 D: Deserializer<'de>,
313 {
314 deserializer.deserialize_str(AnyIdVisitor)
315 }
316}
317
318impl AsRef<str> for AnyId {
319 fn as_ref(&self) -> &str {
320 &self.0
321 }
322}
323
324impl std::fmt::Display for AnyId {
325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326 write!(f, "{}", &self.0)
327 }
328}
329
330impl std::str::FromStr for AnyId {
332 type Err = ParseErrors;
333
334 fn from_str(s: &str) -> Result<Self, Self::Err> {
335 crate::parser::parse_anyid(s)
336 }
337}
338
339impl FromNormalizedStr for AnyId {
340 fn describe_self() -> &'static str {
341 "AnyId"
342 }
343}
344
345#[cfg(feature = "arbitrary")]
346impl<'a> arbitrary::Arbitrary<'a> for AnyId {
347 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
348 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
352 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
353 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
355 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
357 let remaining_length = u.int_in_range(0..=16)?;
359 let mut cs = vec![*u.choose(&head_letters)?];
360 cs.extend(
361 (0..remaining_length)
362 .map(|_| u.choose(&tail_letters))
363 .collect::<Result<Vec<&char>, _>>()?,
364 );
365 let s: String = cs.into_iter().collect();
366 debug_assert!(
367 crate::parser::parse_anyid(&s).is_ok(),
368 "all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
369 );
370 Ok(Self::new_unchecked(s))
371 }
372
373 fn size_hint(depth: usize) -> (usize, Option<usize>) {
374 arbitrary::size_hint::and_all(&[
375 <usize as arbitrary::Arbitrary>::size_hint(depth),
377 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
380 ])
381 }
382}
383
384#[allow(clippy::panic)]
386#[cfg(test)]
387mod test {
388 use super::*;
389
390 #[test]
391 fn normalized_id() {
392 Id::from_normalized_str("foo").expect("should be OK");
393 Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
394 Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
395 Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
396 Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
397 Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
398 Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
399 }
400}