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
158struct IdVisitor;
159
160impl serde::de::Visitor<'_> for IdVisitor {
161 type Value = Id;
162
163 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 formatter.write_str("a valid id")
165 }
166
167 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
168 where
169 E: serde::de::Error,
170 {
171 Id::from_normalized_str(value)
172 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
173 }
174}
175
176impl<'de> Deserialize<'de> for Id {
179 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
180 where
181 D: Deserializer<'de>,
182 {
183 deserializer.deserialize_str(IdVisitor)
184 }
185}
186
187impl<'de> Deserialize<'de> for UnreservedId {
190 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
191 where
192 D: Deserializer<'de>,
193 {
194 deserializer
195 .deserialize_str(IdVisitor)
196 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
197 }
198}
199
200#[cfg(feature = "arbitrary")]
201impl<'a> arbitrary::Arbitrary<'a> for Id {
202 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
203 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
209 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
210 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
212 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
214 let remaining_length = u.int_in_range(0..=16)?;
216 let mut cs = vec![*u.choose(&head_letters)?];
217 cs.extend(
218 (0..remaining_length)
219 .map(|_| u.choose(&tail_letters))
220 .collect::<Result<Vec<&char>, _>>()?,
221 );
222 let mut s: String = cs.into_iter().collect();
223 if crate::parser::parse_ident(&s).is_err() {
226 s.push('_');
227 }
228 Ok(Self::new_unchecked(s))
229 }
230
231 fn size_hint(depth: usize) -> (usize, Option<usize>) {
232 arbitrary::size_hint::and_all(&[
233 <usize as arbitrary::Arbitrary>::size_hint(depth),
235 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
238 ])
239 }
240}
241
242#[cfg(feature = "arbitrary")]
243impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
244 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
245 let id: Id = u.arbitrary()?;
246 match UnreservedId::try_from(id.clone()) {
247 Ok(id) => Ok(id),
248 Err(_) => {
249 #[allow(clippy::unwrap_used)]
251 let new_id = format!("_{id}").parse().unwrap();
252 Ok(new_id)
253 }
254 }
255 }
256
257 fn size_hint(depth: usize) -> (usize, Option<usize>) {
258 <Id as arbitrary::Arbitrary>::size_hint(depth)
259 }
260}
261
262#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
267#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
268#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
269pub struct AnyId(SmolStr);
270
271impl AnyId {
272 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
282 AnyId(s.into())
283 }
284
285 pub fn into_smolstr(self) -> SmolStr {
287 self.0
288 }
289}
290
291struct AnyIdVisitor;
292
293impl serde::de::Visitor<'_> for AnyIdVisitor {
294 type Value = AnyId;
295
296 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 formatter.write_str("any id")
298 }
299
300 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
301 where
302 E: serde::de::Error,
303 {
304 AnyId::from_normalized_str(value)
305 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
306 }
307}
308
309impl<'de> Deserialize<'de> for AnyId {
312 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
313 where
314 D: Deserializer<'de>,
315 {
316 deserializer.deserialize_str(AnyIdVisitor)
317 }
318}
319
320impl AsRef<str> for AnyId {
321 fn as_ref(&self) -> &str {
322 &self.0
323 }
324}
325
326impl std::fmt::Display for AnyId {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 write!(f, "{}", &self.0)
329 }
330}
331
332impl std::str::FromStr for AnyId {
334 type Err = ParseErrors;
335
336 fn from_str(s: &str) -> Result<Self, Self::Err> {
337 crate::parser::parse_anyid(s)
338 }
339}
340
341impl FromNormalizedStr for AnyId {
342 fn describe_self() -> &'static str {
343 "AnyId"
344 }
345}
346
347#[cfg(feature = "arbitrary")]
348impl<'a> arbitrary::Arbitrary<'a> for AnyId {
349 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
350 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
354 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
355 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
357 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
359 let remaining_length = u.int_in_range(0..=16)?;
361 let mut cs = vec![*u.choose(&head_letters)?];
362 cs.extend(
363 (0..remaining_length)
364 .map(|_| u.choose(&tail_letters))
365 .collect::<Result<Vec<&char>, _>>()?,
366 );
367 let s: String = cs.into_iter().collect();
368 debug_assert!(
369 crate::parser::parse_anyid(&s).is_ok(),
370 "all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
371 );
372 Ok(Self::new_unchecked(s))
373 }
374
375 fn size_hint(depth: usize) -> (usize, Option<usize>) {
376 arbitrary::size_hint::and_all(&[
377 <usize as arbitrary::Arbitrary>::size_hint(depth),
379 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
382 ])
383 }
384}
385
386#[allow(clippy::panic)]
388#[cfg(test)]
389mod test {
390 use super::*;
391
392 #[test]
393 fn normalized_id() {
394 Id::from_normalized_str("foo").expect("should be OK");
395 Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
396 Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
397 Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
398 Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
399 Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
400 Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
401 }
402}