1use 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(crate) const fn new_unchecked_const(s: &'static str) -> Self {
60 Id(SmolStr::new_static(s))
61 }
62
63 pub fn into_smolstr(self) -> SmolStr {
65 self.0
66 }
67
68 pub fn is_reserved(&self) -> bool {
72 self.as_ref() == RESERVED_ID
73 }
74
75 pub const fn is_heap_allocated(&self) -> bool {
77 self.0.is_heap_allocated()
78 }
79}
80
81impl AsRef<str> for Id {
82 fn as_ref(&self) -> &str {
83 &self.0
84 }
85}
86
87impl std::fmt::Display for Id {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 write!(f, "{}", &self.0)
90 }
91}
92
93impl std::str::FromStr for Id {
95 type Err = ParseErrors;
96
97 fn from_str(s: &str) -> Result<Self, Self::Err> {
98 crate::parser::parse_ident(s)
99 }
100}
101
102impl FromNormalizedStr for Id {
103 fn describe_self() -> &'static str {
104 "Id"
105 }
106
107 fn from_normalized_str(s: &str) -> Result<Self, ParseErrors> {
110 if is_normalized_ident(s) {
111 Ok(Self::new_unchecked(s))
112 } else {
113 default_from_normalized_str(s, Self::describe_self)
116 }
117 }
118}
119
120#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
122#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
123#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
124pub struct UnreservedId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] pub(crate) Id);
125
126impl From<UnreservedId> for Id {
127 fn from(value: UnreservedId) -> Self {
128 value.0
129 }
130}
131
132impl TryFrom<Id> for UnreservedId {
133 type Error = ReservedNameError;
134 fn try_from(value: Id) -> Result<Self, Self::Error> {
135 if value.is_reserved() {
136 Err(ReservedNameError(InternalName::unqualified_name(
137 value, None,
138 )))
139 } else {
140 Ok(Self(value))
141 }
142 }
143}
144
145impl AsRef<Id> for UnreservedId {
146 fn as_ref(&self) -> &Id {
147 &self.0
148 }
149}
150
151impl AsRef<str> for UnreservedId {
152 fn as_ref(&self) -> &str {
153 self.0.as_ref()
154 }
155}
156
157impl std::fmt::Display for UnreservedId {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 self.0.fmt(f)
160 }
161}
162
163impl std::str::FromStr for UnreservedId {
164 type Err = ParseErrors;
165 fn from_str(s: &str) -> Result<Self, Self::Err> {
166 Id::from_str(s).and_then(|id| id.try_into().map_err(ParseErrors::singleton))
167 }
168}
169
170impl FromNormalizedStr for UnreservedId {
171 fn describe_self() -> &'static str {
172 "Unreserved Id"
173 }
174}
175
176impl UnreservedId {
177 pub(crate) fn empty() -> Self {
179 #[expect(clippy::unwrap_used, reason = "\"\" does not contain `__cedar`")]
180 Id("".into()).try_into().unwrap()
181 }
182
183 pub fn into_smolstr(self) -> SmolStr {
185 self.0.into_smolstr()
186 }
187}
188
189struct IdVisitor;
190
191impl serde::de::Visitor<'_> for IdVisitor {
192 type Value = Id;
193
194 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
195 formatter.write_str("a valid id")
196 }
197
198 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
199 where
200 E: serde::de::Error,
201 {
202 Id::from_normalized_str(value)
203 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
204 }
205}
206
207impl<'de> Deserialize<'de> for Id {
210 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
211 where
212 D: Deserializer<'de>,
213 {
214 deserializer.deserialize_str(IdVisitor)
215 }
216}
217
218impl<'de> Deserialize<'de> for UnreservedId {
221 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
222 where
223 D: Deserializer<'de>,
224 {
225 deserializer
226 .deserialize_str(IdVisitor)
227 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
228 }
229}
230
231#[cfg(feature = "arbitrary")]
232impl<'a> arbitrary::Arbitrary<'a> for Id {
233 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
234 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
240 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
241 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
243 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
245 let remaining_length = u.int_in_range(0..=16)?;
247 let mut cs = vec![*u.choose(&head_letters)?];
248 cs.extend(
249 (0..remaining_length)
250 .map(|_| u.choose(&tail_letters))
251 .collect::<Result<Vec<&char>, _>>()?,
252 );
253 let mut s: String = cs.into_iter().collect();
254 if crate::parser::parse_ident(&s).is_err() {
257 s.push('_');
258 }
259 Ok(Self::new_unchecked(s))
260 }
261
262 fn size_hint(depth: usize) -> (usize, Option<usize>) {
263 arbitrary::size_hint::and_all(&[
264 <usize as arbitrary::Arbitrary>::size_hint(depth),
266 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
269 ])
270 }
271}
272
273#[cfg(feature = "arbitrary")]
274impl<'a> arbitrary::Arbitrary<'a> for UnreservedId {
275 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
276 let id: Id = u.arbitrary()?;
277 match UnreservedId::try_from(id.clone()) {
278 Ok(id) => Ok(id),
279 Err(_) => {
280 #[expect(clippy::unwrap_used, reason = "`___cedar` is a valid unreserved id")]
281 let new_id = format!("_{id}").parse().unwrap();
282 Ok(new_id)
283 }
284 }
285 }
286
287 fn size_hint(depth: usize) -> (usize, Option<usize>) {
288 <Id as arbitrary::Arbitrary>::size_hint(depth)
289 }
290}
291
292#[derive(Serialize, Debug, PartialEq, Eq, Clone, Hash, PartialOrd, Ord)]
297#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
298#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
299pub struct AnyId(SmolStr);
300
301impl AnyId {
302 pub(crate) fn new_unchecked(s: impl Into<SmolStr>) -> AnyId {
312 AnyId(s.into())
313 }
314
315 pub fn into_smolstr(self) -> SmolStr {
317 self.0
318 }
319}
320
321struct AnyIdVisitor;
322
323impl serde::de::Visitor<'_> for AnyIdVisitor {
324 type Value = AnyId;
325
326 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 formatter.write_str("any id")
328 }
329
330 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
331 where
332 E: serde::de::Error,
333 {
334 AnyId::from_normalized_str(value)
335 .map_err(|err| serde::de::Error::custom(format!("invalid id `{value}`: {err}")))
336 }
337}
338
339impl<'de> Deserialize<'de> for AnyId {
342 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
343 where
344 D: Deserializer<'de>,
345 {
346 deserializer.deserialize_str(AnyIdVisitor)
347 }
348}
349
350impl AsRef<str> for AnyId {
351 fn as_ref(&self) -> &str {
352 &self.0
353 }
354}
355
356impl std::fmt::Display for AnyId {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 write!(f, "{}", &self.0)
359 }
360}
361
362impl std::str::FromStr for AnyId {
364 type Err = ParseErrors;
365
366 fn from_str(s: &str) -> Result<Self, Self::Err> {
367 crate::parser::parse_anyid(s)
368 }
369}
370
371impl FromNormalizedStr for AnyId {
372 fn describe_self() -> &'static str {
373 "AnyId"
374 }
375}
376
377#[cfg(feature = "arbitrary")]
378impl<'a> arbitrary::Arbitrary<'a> for AnyId {
379 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
380 let construct_list = |s: &str| s.chars().collect::<Vec<char>>();
384 let list_concat = |s1: &[char], s2: &[char]| [s1, s2].concat();
385 let head_letters = construct_list("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_");
387 let tail_letters = list_concat(&construct_list("0123456789"), &head_letters);
389 let remaining_length = u.int_in_range(0..=16)?;
391 let mut cs = vec![*u.choose(&head_letters)?];
392 cs.extend(
393 (0..remaining_length)
394 .map(|_| u.choose(&tail_letters))
395 .collect::<Result<Vec<&char>, _>>()?,
396 );
397 let s: String = cs.into_iter().collect();
398 debug_assert!(
399 crate::parser::parse_anyid(&s).is_ok(),
400 "all strings constructed this way should be valid AnyIds, but this one is not: {s:?}"
401 );
402 Ok(Self::new_unchecked(s))
403 }
404
405 fn size_hint(depth: usize) -> (usize, Option<usize>) {
406 arbitrary::size_hint::and_all(&[
407 <usize as arbitrary::Arbitrary>::size_hint(depth),
409 <Vec<u8> as arbitrary::Arbitrary>::size_hint(depth),
412 ])
413 }
414}
415
416#[cfg(test)]
417#[expect(clippy::panic, reason = "unit-test code")]
418mod test {
419 use super::*;
420
421 #[test]
422 fn normalized_id() {
423 Id::from_normalized_str("foo").expect("should be OK");
424 Id::from_normalized_str("foo::bar").expect_err("shouldn't be OK");
425 Id::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
426 Id::from_normalized_str(" foo").expect_err("shouldn't be OK");
427 Id::from_normalized_str("foo ").expect_err("shouldn't be OK");
428 Id::from_normalized_str("foo\n").expect_err("shouldn't be OK");
429 Id::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
430 }
431}