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(
110 value, None,
111 )))
112 } else {
113 Ok(Self(value))
114 }
115 }
116}
117
118impl AsRef<Id> for UnreservedId {
119 fn as_ref(&self) -> &Id {
120 &self.0
121 }
122}
123
124impl AsRef<str> for UnreservedId {
125 fn as_ref(&self) -> &str {
126 self.0.as_ref()
127 }
128}
129
130impl std::fmt::Display for UnreservedId {
131 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132 self.0.fmt(f)
133 }
134}
135
136impl std::str::FromStr for UnreservedId {
137 type Err = ParseErrors;
138 fn from_str(s: &str) -> Result<Self, Self::Err> {
139 Id::from_str(s).and_then(|id| id.try_into().map_err(ParseErrors::singleton))
140 }
141}
142
143impl FromNormalizedStr for UnreservedId {
144 fn describe_self() -> &'static str {
145 "Unreserved Id"
146 }
147}
148
149impl UnreservedId {
150 pub(crate) fn empty() -> Self {
152 #[allow(clippy::unwrap_used)]
154 Id("".into()).try_into().unwrap()
155 }
156
157 pub fn into_smolstr(self) -> SmolStr {
159 self.0.into_smolstr()
160 }
161}
162
163struct IdVisitor;
164
165impl serde::de::Visitor<'_> for IdVisitor {
166 type Value = Id;
167
168 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169 formatter.write_str("a valid id")
170 }
171
172 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
173 where
174 E: serde::de::Error,
175 {
176 Id::from_normalized_str(value)
177 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
178 }
179}
180
181impl<'de> Deserialize<'de> for Id {
184 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
185 where
186 D: Deserializer<'de>,
187 {
188 deserializer.deserialize_str(IdVisitor)
189 }
190}
191
192impl<'de> Deserialize<'de> for UnreservedId {
195 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
196 where
197 D: Deserializer<'de>,
198 {
199 deserializer
200 .deserialize_str(IdVisitor)
201 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
202 }
203}
204
205#[cfg(feature = "arbitrary")]
206impl<'a> arbitrary::Arbitrary<'a> for Id {
207 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
208 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
214 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
215 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
217 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
219 let remaining_length = u.int_in_range(0..=16)?;
221 let mut cs = vec![*u.choose(&head_letters)?];
222 cs.extend(
223 (0..remaining_length)
224 .map(|_| u.choose(&tail_letters))
225 .collect::<Result<Vec<&char>, _>>()?,
226 );
227 let mut s: String = cs.into_iter().collect();
228 if crate::parser::parse_ident(&s).is_err() {
231 s.push('_');
232 }
233 Ok(Self::new_unchecked(s))
234 }
235
236 fn size_hint(depth: usize) -> (usize, Option<usize>) {
237 arbitrary::size_hint::and_all(&[
238 <usize as arbitrary::Arbitrary>::size_hint(depth),
240 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
243 ])
244 }
245}
246
247#[cfg(feature = "arbitrary")]
248impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
249 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
250 let id: Id = u.arbitrary()?;
251 match UnreservedId::try_from(id.clone()) {
252 Ok(id) => Ok(id),
253 Err(_) => {
254 #[allow(clippy::unwrap_used)]
256 let new_id = format!("_{id}").parse().unwrap();
257 Ok(new_id)
258 }
259 }
260 }
261
262 fn size_hint(depth: usize) -> (usize, Option<usize>) {
263 <Id as arbitrary::Arbitrary>::size_hint(depth)
264 }
265}
266
267#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
272#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
273#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
274pub struct AnyId(SmolStr);
275
276impl AnyId {
277 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
287 AnyId(s.into())
288 }
289
290 pub fn into_smolstr(self) -> SmolStr {
292 self.0
293 }
294}
295
296struct AnyIdVisitor;
297
298impl serde::de::Visitor<'_> for AnyIdVisitor {
299 type Value = AnyId;
300
301 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
302 formatter.write_str("any id")
303 }
304
305 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
306 where
307 E: serde::de::Error,
308 {
309 AnyId::from_normalized_str(value)
310 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
311 }
312}
313
314impl<'de> Deserialize<'de> for AnyId {
317 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318 where
319 D: Deserializer<'de>,
320 {
321 deserializer.deserialize_str(AnyIdVisitor)
322 }
323}
324
325impl AsRef<str> for AnyId {
326 fn as_ref(&self) -> &str {
327 &self.0
328 }
329}
330
331impl std::fmt::Display for AnyId {
332 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
333 write!(f, "{}", &self.0)
334 }
335}
336
337impl std::str::FromStr for AnyId {
339 type Err = ParseErrors;
340
341 fn from_str(s: &str) -> Result<Self, Self::Err> {
342 crate::parser::parse_anyid(s)
343 }
344}
345
346impl FromNormalizedStr for AnyId {
347 fn describe_self() -> &'static str {
348 "AnyId"
349 }
350}
351
352#[cfg(feature = "arbitrary")]
353impl<'a> arbitrary::Arbitrary<'a> for AnyId {
354 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
355 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
359 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
360 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
362 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
364 let remaining_length = u.int_in_range(0..=16)?;
366 let mut cs = vec![*u.choose(&head_letters)?];
367 cs.extend(
368 (0..remaining_length)
369 .map(|_| u.choose(&tail_letters))
370 .collect::<Result<Vec<&char>, _>>()?,
371 );
372 let s: String = cs.into_iter().collect();
373 debug_assert!(
374 crate::parser::parse_anyid(&s).is_ok(),
375 "all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
376 );
377 Ok(Self::new_unchecked(s))
378 }
379
380 fn size_hint(depth: usize) -> (usize, Option<usize>) {
381 arbitrary::size_hint::and_all(&[
382 <usize as arbitrary::Arbitrary>::size_hint(depth),
384 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
387 ])
388 }
389}
390
391#[allow(clippy::panic)]
393#[cfg(test)]
394mod test {
395 use super::*;
396
397 #[test]
398 fn normalized_id() {
399 Id::from_normalized_str("foo").expect("should be OK");
400 Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
401 Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
402 Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
403 Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
404 Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
405 Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
406 }
407}