cedar_policy_core/ast/
id.rs1use serde::{Deserialize, Deserializer, Serialize};
18use smol_str::SmolStr;
19
20use crate::{
21 ast::is_normalized_ident, default_from_normalized_str, parser::err::ParseErrors,
22 FromNormalizedStr,
23};
24
25use super::{InternalName, ReservedNameError};
26
27const RESERVED_ID: &str = "__cedar";
28
29#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
36pub struct Id(SmolStr);
37
38impl Id {
39 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> Id {
53 Id(s.into())
54 }
55
56 pub fn into_smolstr(self) -> SmolStr {
58 self.0
59 }
60
61 pub fn is_reserved(&self) -> bool {
65 self.as_ref() == RESERVED_ID
66 }
67}
68
69impl AsRef<str> for Id {
70 fn as_ref(&self) -> &str {
71 &self.0
72 }
73}
74
75impl std::fmt::Display for Id {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 write!(f, "{}", &self.0)
78 }
79}
80
81impl std::str::FromStr for Id {
83 type Err = ParseErrors;
84
85 fn from_str(s: &str) -> Result<Self, Self::Err> {
86 crate::parser::parse_ident(s)
87 }
88}
89
90impl FromNormalizedStr for Id {
91 fn describe_self() -> &'static str {
92 "Id"
93 }
94
95 fn from_normalized_str(s: &str) -> Result<Self, ParseErrors> {
98 if is_normalized_ident(s) {
99 Ok(Self::new_unchecked(s))
100 } else {
101 default_from_normalized_str(s, Self::describe_self)
104 }
105 }
106}
107
108#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
110#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
111#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
112pub struct UnreservedId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] pub(crate) Id);
113
114impl From<UnreservedId> for Id {
115 fn from(value: UnreservedId) -> Self {
116 value.0
117 }
118}
119
120impl TryFrom<Id> for UnreservedId {
121 type Error = ReservedNameError;
122 fn try_from(value: Id) -> Result<Self, Self::Error> {
123 if value.is_reserved() {
124 Err(ReservedNameError(InternalName::unqualified_name(
125 value, None,
126 )))
127 } else {
128 Ok(Self(value))
129 }
130 }
131}
132
133impl AsRef<Id> for UnreservedId {
134 fn as_ref(&self) -> &Id {
135 &self.0
136 }
137}
138
139impl AsRef<str> for UnreservedId {
140 fn as_ref(&self) -> &str {
141 self.0.as_ref()
142 }
143}
144
145impl std::fmt::Display for UnreservedId {
146 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147 self.0.fmt(f)
148 }
149}
150
151impl std::str::FromStr for UnreservedId {
152 type Err = ParseErrors;
153 fn from_str(s: &str) -> Result<Self, Self::Err> {
154 Id::from_str(s).and_then(|id| id.try_into().map_err(ParseErrors::singleton))
155 }
156}
157
158impl FromNormalizedStr for UnreservedId {
159 fn describe_self() -> &'static str {
160 "Unreserved Id"
161 }
162}
163
164impl UnreservedId {
165 pub(crate) fn empty() -> Self {
167 #[allow(clippy::unwrap_used)]
169 Id("".into()).try_into().unwrap()
170 }
171
172 pub fn into_smolstr(self) -> SmolStr {
174 self.0.into_smolstr()
175 }
176}
177
178struct IdVisitor;
179
180impl serde::de::Visitor<'_> for IdVisitor {
181 type Value = Id;
182
183 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 formatter.write_str("a valid id")
185 }
186
187 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
188 where
189 E: serde::de::Error,
190 {
191 Id::from_normalized_str(value)
192 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
193 }
194}
195
196impl<'de> Deserialize<'de> for Id {
199 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
200 where
201 D: Deserializer<'de>,
202 {
203 deserializer.deserialize_str(IdVisitor)
204 }
205}
206
207impl<'de> Deserialize<'de> for UnreservedId {
210 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211 where
212 D: Deserializer<'de>,
213 {
214 deserializer
215 .deserialize_str(IdVisitor)
216 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
217 }
218}
219
220#[cfg(feature = "arbitrary")]
221impl<'a> arbitrary::Arbitrary<'a> for Id {
222 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
223 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
229 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
230 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
232 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
234 let remaining_length = u.int_in_range(0..=16)?;
236 let mut cs = vec![*u.choose(&head_letters)?];
237 cs.extend(
238 (0..remaining_length)
239 .map(|_| u.choose(&tail_letters))
240 .collect::<Result<Vec<&char>, _>>()?,
241 );
242 let mut s: String = cs.into_iter().collect();
243 if crate::parser::parse_ident(&s).is_err() {
246 s.push('_');
247 }
248 Ok(Self::new_unchecked(s))
249 }
250
251 fn size_hint(depth: usize) -> (usize, Option<usize>) {
252 arbitrary::size_hint::and_all(&[
253 <usize as arbitrary::Arbitrary>::size_hint(depth),
255 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
258 ])
259 }
260}
261
262#[cfg(feature = "arbitrary")]
263impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
264 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
265 let id: Id = u.arbitrary()?;
266 match UnreservedId::try_from(id.clone()) {
267 Ok(id) => Ok(id),
268 Err(_) => {
269 #[allow(clippy::unwrap_used)]
271 let new_id = format!("_{id}").parse().unwrap();
272 Ok(new_id)
273 }
274 }
275 }
276
277 fn size_hint(depth: usize) -> (usize, Option<usize>) {
278 <Id as arbitrary::Arbitrary>::size_hint(depth)
279 }
280}
281
282#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
287#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
288#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
289pub struct AnyId(SmolStr);
290
291impl AnyId {
292 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
302 AnyId(s.into())
303 }
304
305 pub fn into_smolstr(self) -> SmolStr {
307 self.0
308 }
309}
310
311struct AnyIdVisitor;
312
313impl serde::de::Visitor<'_> for AnyIdVisitor {
314 type Value = AnyId;
315
316 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
317 formatter.write_str("any id")
318 }
319
320 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
321 where
322 E: serde::de::Error,
323 {
324 AnyId::from_normalized_str(value)
325 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
326 }
327}
328
329impl<'de> Deserialize<'de> for AnyId {
332 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
333 where
334 D: Deserializer<'de>,
335 {
336 deserializer.deserialize_str(AnyIdVisitor)
337 }
338}
339
340impl AsRef<str> for AnyId {
341 fn as_ref(&self) -> &str {
342 &self.0
343 }
344}
345
346impl std::fmt::Display for AnyId {
347 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
348 write!(f, "{}", &self.0)
349 }
350}
351
352impl std::str::FromStr for AnyId {
354 type Err = ParseErrors;
355
356 fn from_str(s: &str) -> Result<Self, Self::Err> {
357 crate::parser::parse_anyid(s)
358 }
359}
360
361impl FromNormalizedStr for AnyId {
362 fn describe_self() -> &'static str {
363 "AnyId"
364 }
365}
366
367#[cfg(feature = "arbitrary")]
368impl<'a> arbitrary::Arbitrary<'a> for AnyId {
369 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
370 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
374 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
375 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
377 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
379 let remaining_length = u.int_in_range(0..=16)?;
381 let mut cs = vec![*u.choose(&head_letters)?];
382 cs.extend(
383 (0..remaining_length)
384 .map(|_| u.choose(&tail_letters))
385 .collect::<Result<Vec<&char>, _>>()?,
386 );
387 let s: String = cs.into_iter().collect();
388 debug_assert!(
389 crate::parser::parse_anyid(&s).is_ok(),
390 "all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
391 );
392 Ok(Self::new_unchecked(s))
393 }
394
395 fn size_hint(depth: usize) -> (usize, Option<usize>) {
396 arbitrary::size_hint::and_all(&[
397 <usize as arbitrary::Arbitrary>::size_hint(depth),
399 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
402 ])
403 }
404}
405
406#[allow(clippy::panic)]
408#[cfg(test)]
409mod test {
410 use super::*;
411
412 #[test]
413 fn normalized_id() {
414 Id::from_normalized_str("foo").expect("should be OK");
415 Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
416 Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
417 Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
418 Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
419 Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
420 Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
421 }
422}