1use super::{id::Id, PrincipalOrResource, UnreservedId};
18use educe::Educe;
19use itertools::Itertools;
20use miette::Diagnostic;
21use ref_cast::RefCast;
22use regex::Regex;
23use serde::{Deserialize, Deserializer, Serialize, Serializer};
24use smol_str::ToSmolStr;
25use std::collections::HashSet;
26use std::fmt::Display;
27use std::str::FromStr;
28use std::sync::Arc;
29use thiserror::Error;
30
31use crate::parser::err::{ParseError, ParseErrors, ToASTError, ToASTErrorKind};
32use crate::parser::Loc;
33use crate::FromNormalizedStr;
34
35#[derive(Educe, Clone)]
42#[educe(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
43pub struct InternalName {
44 pub(crate) id: Id,
46 pub(crate) path: Arc<Vec<Id>>,
48 #[educe(PartialEq(ignore))]
50 #[educe(Hash(ignore))]
51 #[educe(PartialOrd(ignore))]
52 #[educe(Debug(ignore))]
53 pub(crate) loc: Option<Loc>,
54}
55
56impl From<Id> for InternalName {
58 fn from(value: Id) -> Self {
59 Self::unqualified_name(value, None)
60 }
61}
62
63impl TryFrom<InternalName> for Id {
67 type Error = ();
68 fn try_from(value: InternalName) -> Result<Self, Self::Error> {
69 if value.is_unqualified() {
70 Ok(value.id)
71 } else {
72 Err(())
73 }
74 }
75}
76
77impl InternalName {
78 pub fn new(basename: Id, path: impl IntoIterator<Item = Id>, loc: Option<Loc>) -> Self {
80 Self {
81 id: basename,
82 path: Arc::new(path.into_iter().collect()),
83 loc,
84 }
85 }
86
87 pub fn unqualified_name(id: Id, loc: Option<Loc>) -> Self {
89 Self {
90 id,
91 path: Arc::new(vec![]),
92 loc,
93 }
94 }
95
96 pub fn __cedar() -> Self {
98 Self::unqualified_name(Id::new_unchecked_const("__cedar"), None)
100 }
101
102 pub fn parse_unqualified_name(s: &str) -> Result<Self, ParseErrors> {
105 Ok(Self {
106 id: s.parse()?,
107 path: Arc::new(vec![]),
108 loc: None,
109 })
110 }
111
112 pub fn type_in_namespace(
115 basename: Id,
116 namespace: InternalName,
117 loc: Option<Loc>,
118 ) -> InternalName {
119 let mut path = Arc::unwrap_or_clone(namespace.path);
120 path.push(namespace.id);
121 InternalName::new(basename, path, loc)
122 }
123
124 pub fn loc(&self) -> Option<&Loc> {
126 self.loc.as_ref()
127 }
128
129 pub fn basename(&self) -> &Id {
131 &self.id
132 }
133
134 pub fn namespace_components(&self) -> impl Iterator<Item = &Id> {
136 self.path.iter()
137 }
138
139 pub fn namespace(&self) -> String {
146 self.path.iter().join("::")
147 }
148
149 pub fn qualify_with(&self, namespace: Option<&InternalName>) -> InternalName {
168 if self.is_unqualified() {
169 match namespace {
170 Some(namespace) => Self::new(
171 self.basename().clone(),
172 namespace
173 .namespace_components()
174 .chain(std::iter::once(namespace.basename()))
175 .cloned(),
176 self.loc.clone(),
177 ),
178 None => self.clone(),
179 }
180 } else {
181 self.clone()
182 }
183 }
184
185 pub fn qualify_with_name(&self, namespace: Option<&Name>) -> InternalName {
187 let ns = namespace.map(AsRef::as_ref);
188 self.qualify_with(ns)
189 }
190
191 pub fn is_unqualified(&self) -> bool {
193 self.path.is_empty()
194 }
195
196 pub fn is_reserved(&self) -> bool {
199 self.path
200 .iter()
201 .chain(std::iter::once(&self.id))
202 .any(|id| id.is_reserved())
203 }
204}
205
206impl std::fmt::Display for InternalName {
207 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
208 for elem in self.path.as_ref() {
209 write!(f, "{elem}::")?;
210 }
211 write!(f, "{}", self.id)?;
212 Ok(())
213 }
214}
215
216impl Serialize for InternalName {
219 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
220 where
221 S: Serializer,
222 {
223 self.to_smolstr().serialize(serializer)
224 }
225}
226
227impl std::str::FromStr for InternalName {
229 type Err = ParseErrors;
230
231 fn from_str(s: &str) -> Result<Self, Self::Err> {
232 crate::parser::parse_internal_name(s)
233 }
234}
235
236impl FromNormalizedStr for InternalName {
237 fn describe_self() -> &'static str {
238 "internal name"
239 }
240}
241
242#[cfg(feature = "arbitrary")]
243impl<'a> arbitrary::Arbitrary<'a> for InternalName {
244 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
245 let path_size = u.int_in_range(0..=8)?;
246 Ok(Self {
247 id: u.arbitrary()?,
248 path: Arc::new(
249 (0..path_size)
250 .map(|_| u.arbitrary())
251 .collect::<Result<Vec<Id>, _>>()?,
252 ),
253 loc: None,
254 })
255 }
256}
257
258struct NameVisitor;
259
260impl serde::de::Visitor<'_> for NameVisitor {
261 type Value = InternalName;
262
263 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
264 formatter.write_str("a name consisting of an optional namespace and id")
265 }
266
267 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
268 where
269 E: serde::de::Error,
270 {
271 InternalName::from_normalized_str(value)
272 .map_err(|err| serde::de::Error::custom(format!("invalid name `{value}`: {err}")))
273 }
274}
275
276impl<'de> Deserialize<'de> for InternalName {
279 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280 where
281 D: Deserializer<'de>,
282 {
283 deserializer.deserialize_str(NameVisitor)
284 }
285}
286
287#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
292#[serde(transparent)]
293pub struct SlotId(pub(crate) ValidSlotId);
294
295impl SlotId {
296 pub fn principal() -> Self {
298 Self(ValidSlotId::Principal)
299 }
300
301 pub fn resource() -> Self {
303 Self(ValidSlotId::Resource)
304 }
305
306 pub fn is_principal(&self) -> bool {
308 matches!(self, Self(ValidSlotId::Principal))
309 }
310
311 pub fn is_resource(&self) -> bool {
313 matches!(self, Self(ValidSlotId::Resource))
314 }
315}
316
317impl From<PrincipalOrResource> for SlotId {
318 fn from(v: PrincipalOrResource) -> Self {
319 match v {
320 PrincipalOrResource::Principal => SlotId::principal(),
321 PrincipalOrResource::Resource => SlotId::resource(),
322 }
323 }
324}
325
326impl std::fmt::Display for SlotId {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 write!(f, "{}", self.0)
329 }
330}
331
332#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
334pub(crate) enum ValidSlotId {
335 #[serde(rename = "?principal")]
336 Principal,
337 #[serde(rename = "?resource")]
338 Resource,
339}
340
341impl std::fmt::Display for ValidSlotId {
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 let s = match self {
344 ValidSlotId::Principal => "principal",
345 ValidSlotId::Resource => "resource",
346 };
347 write!(f, "?{s}")
348 }
349}
350
351#[derive(Educe, Debug, Clone)]
353#[educe(PartialEq, Eq, Hash)]
354pub struct Slot {
355 pub id: SlotId,
357 #[educe(PartialEq(ignore))]
359 #[educe(Hash(ignore))]
360 pub loc: Option<Loc>,
361}
362
363#[cfg(test)]
364mod vars_test {
365 use super::*;
366 #[test]
368 fn vars_correct() {
369 SlotId::principal();
370 SlotId::resource();
371 }
372
373 #[test]
374 fn display() {
375 assert_eq!(format!("{}", SlotId::principal()), "?principal")
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, RefCast)]
385#[repr(transparent)]
386#[serde(transparent)]
387pub struct Name(pub(crate) InternalName);
388
389impl From<UnreservedId> for Name {
390 fn from(value: UnreservedId) -> Self {
391 Self::unqualified_name(value)
392 }
393}
394
395impl TryFrom<Name> for UnreservedId {
396 type Error = ();
397 fn try_from(value: Name) -> Result<Self, Self::Error> {
398 if value.0.is_unqualified() {
399 Ok(value.basename())
400 } else {
401 Err(())
402 }
403 }
404}
405
406impl Display for Name {
407 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408 self.0.fmt(f)
409 }
410}
411
412impl FromStr for Name {
413 type Err = ParseErrors;
414 fn from_str(s: &str) -> Result<Self, Self::Err> {
415 let n: InternalName = s.parse()?;
416 n.try_into().map_err(ParseErrors::singleton)
417 }
418}
419
420#[expect(clippy::unwrap_used, reason = "this is a valid Regex pattern")]
421static VALID_ANY_IDENT_REGEX: std::sync::LazyLock<Regex> =
422 std::sync::LazyLock::new(|| Regex::new("^[_a-zA-Z][_a-zA-Z0-9]*$").unwrap());
423
424#[expect(clippy::unwrap_used, reason = "this is a valid Regex pattern")]
425static VALID_NAME_REGEX: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
426 Regex::new("^[_a-zA-Z][_a-zA-Z0-9]*(?:::[_a-zA-Z][_a-zA-Z0-9]*)*$").unwrap()
427});
428
429static RESERVED_IDS: std::sync::LazyLock<HashSet<&'static str>> = std::sync::LazyLock::new(|| {
432 vec![
433 "true", "false", "if", "then", "else", "in", "is", "like", "has",
434 "__cedar",
436 ]
437 .into_iter()
438 .collect()
439});
440
441pub fn is_normalized_ident(s: &str) -> bool {
447 VALID_ANY_IDENT_REGEX.is_match(s) && !RESERVED_IDS.contains(s)
448}
449
450impl FromNormalizedStr for Name {
451 fn from_normalized_str(s: &str) -> Result<Self, ParseErrors> {
452 if !VALID_NAME_REGEX.is_match(s) {
453 return Err(Self::parse_err_from_str(s));
454 }
455
456 let path_parts: Vec<&str> = s.split("::").collect();
457 if path_parts.iter().any(|s| RESERVED_IDS.contains(s)) {
458 return Err(Self::parse_err_from_str(s));
459 }
460
461 if let Some((last, prefix)) = path_parts.split_last() {
462 Ok(Self(InternalName::new(
463 Id::new_unchecked(*last),
464 prefix.iter().map(|part| Id::new_unchecked(*part)),
465 Some(Loc::new(0..(s.len()), s.into())),
466 )))
467 } else {
468 Err(Self::parse_err_from_str(s))
469 }
470 }
471
472 fn describe_self() -> &'static str {
473 "Name"
474 }
475}
476
477impl<'de> Deserialize<'de> for Name {
480 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
481 where
482 D: Deserializer<'de>,
483 {
484 deserializer
485 .deserialize_str(NameVisitor)
486 .and_then(|n| n.try_into().map_err(serde::de::Error::custom))
487 }
488}
489
490impl Name {
491 pub fn parse_unqualified_name(s: &str) -> Result<Self, ParseErrors> {
494 InternalName::parse_unqualified_name(s)
495 .and_then(|n| n.try_into().map_err(ParseErrors::singleton))
496 }
497
498 pub fn unqualified_name(id: UnreservedId) -> Self {
500 Self(InternalName::unqualified_name(id.0, None))
502 }
503
504 pub fn basename_as_ref(&self) -> &Id {
507 self.0.basename()
508 }
509
510 pub fn basename(&self) -> UnreservedId {
513 #![allow(
514 clippy::unwrap_used,
515 reason = "Any component of a `Name` is a `UnreservedId`"
516 )]
517 self.0.basename().clone().try_into().unwrap()
518 }
519
520 pub fn is_unqualified(&self) -> bool {
522 self.0.is_unqualified()
523 }
524
525 pub fn qualify_with(&self, namespace: Option<&InternalName>) -> InternalName {
529 self.0.qualify_with(namespace)
530 }
531
532 pub fn qualify_with_name(&self, namespace: Option<&Self>) -> Self {
537 Self(self.as_ref().qualify_with(namespace.map(|n| n.as_ref())))
540 }
541
542 pub fn loc(&self) -> Option<&Loc> {
544 self.0.loc()
545 }
546
547 fn parse_err_from_str(s: &str) -> ParseErrors {
548 match Self::from_str(s) {
549 Err(parse_err) => parse_err,
550 Ok(parsed) => {
551 let normalized_src = parsed.to_string();
552 let diff_byte = s
553 .bytes()
554 .zip(normalized_src.bytes())
555 .enumerate()
556 .find(|(_, (b0, b1))| b0 != b1)
557 .map(|(idx, _)| idx)
558 .unwrap_or_else(|| s.len().min(normalized_src.len()));
559
560 ParseErrors::singleton(ParseError::ToAST(ToASTError::new(
561 ToASTErrorKind::NonNormalizedString {
562 kind: Self::describe_self(),
563 src: s.to_string(),
564 normalized_src,
565 },
566 Some(Loc::new(diff_byte, s.into())),
567 )))
568 }
569 }
570 }
571}
572
573#[derive(Debug, Clone, PartialEq, Eq, Error, Diagnostic, Hash)]
575#[error("The name `{0}` contains `__cedar`, which is reserved")]
576pub struct ReservedNameError(pub(crate) InternalName);
577
578impl ReservedNameError {
579 pub fn name(&self) -> &InternalName {
581 &self.0
582 }
583}
584
585impl From<ReservedNameError> for ParseError {
586 fn from(value: ReservedNameError) -> Self {
587 ParseError::ToAST(ToASTError::new(
588 value.clone().into(),
589 value.0.loc.clone().or_else(|| {
590 let name_str = value.0.to_string();
591 Some(Loc::new(0..(name_str.len()), name_str.into()))
592 }),
593 ))
594 }
595}
596
597impl TryFrom<InternalName> for Name {
598 type Error = ReservedNameError;
599 fn try_from(value: InternalName) -> Result<Self, Self::Error> {
600 if value.is_reserved() {
601 Err(ReservedNameError(value))
602 } else {
603 Ok(Self(value))
604 }
605 }
606}
607
608impl<'a> TryFrom<&'a InternalName> for &'a Name {
609 type Error = ReservedNameError;
610 fn try_from(value: &'a InternalName) -> Result<&'a Name, ReservedNameError> {
611 if value.is_reserved() {
612 Err(ReservedNameError(value.clone()))
613 } else {
614 Ok(<Name as RefCast>::ref_cast(value))
615 }
616 }
617}
618
619impl From<Name> for InternalName {
620 fn from(value: Name) -> Self {
621 value.0
622 }
623}
624
625impl AsRef<InternalName> for Name {
626 fn as_ref(&self) -> &InternalName {
627 &self.0
628 }
629}
630
631#[cfg(feature = "arbitrary")]
632impl<'a> arbitrary::Arbitrary<'a> for Name {
633 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
634 let path_size = u.int_in_range(0..=8)?;
638 let basename: UnreservedId = u.arbitrary()?;
639 let path: Vec<UnreservedId> = (0..path_size)
640 .map(|_| u.arbitrary())
641 .collect::<Result<Vec<_>, _>>()?;
642 let name = InternalName::new(basename.into(), path.into_iter().map(|id| id.into()), None);
643 #[expect(
644 clippy::unwrap_used,
645 reason = "`name` is made of `UnreservedId`s and thus should be a valid `Name`"
646 )]
647 Ok(name.try_into().unwrap())
648 }
649
650 fn size_hint(depth: usize) -> (usize, Option<usize>) {
651 <InternalName as arbitrary::Arbitrary>::size_hint(depth)
652 }
653}
654
655#[cfg(test)]
656mod test {
657 use super::*;
658
659 #[test]
660 fn normalized_name() {
661 InternalName::from_normalized_str("foo").expect("should be OK");
662 InternalName::from_normalized_str("foo::bar").expect("should be OK");
663 InternalName::from_normalized_str(r#"foo::"bar""#).expect_err("shouldn't be OK");
664 InternalName::from_normalized_str(" foo").expect_err("shouldn't be OK");
665 InternalName::from_normalized_str("foo ").expect_err("shouldn't be OK");
666 InternalName::from_normalized_str("foo\n").expect_err("shouldn't be OK");
667 InternalName::from_normalized_str("foo//comment").expect_err("shouldn't be OK");
668 }
669
670 #[test]
671 fn qualify_with() {
672 assert_eq!(
673 "foo::bar::baz",
674 InternalName::from_normalized_str("baz")
675 .unwrap()
676 .qualify_with(Some(&"foo::bar".parse().unwrap()))
677 .to_smolstr()
678 );
679 assert_eq!(
680 "C::D",
681 InternalName::from_normalized_str("C::D")
682 .unwrap()
683 .qualify_with(Some(&"A::B".parse().unwrap()))
684 .to_smolstr()
685 );
686 assert_eq!(
687 "A::B::C::D",
688 InternalName::from_normalized_str("D")
689 .unwrap()
690 .qualify_with(Some(&"A::B::C".parse().unwrap()))
691 .to_smolstr()
692 );
693 assert_eq!(
694 "B::C::D",
695 InternalName::from_normalized_str("B::C::D")
696 .unwrap()
697 .qualify_with(Some(&"A".parse().unwrap()))
698 .to_smolstr()
699 );
700 assert_eq!(
701 "A",
702 InternalName::from_normalized_str("A")
703 .unwrap()
704 .qualify_with(None)
705 .to_smolstr()
706 )
707 }
708
709 #[test]
710 fn test_reserved() {
711 for n in [
712 "__cedar",
713 "__cedar::A",
714 "__cedar::A::B",
715 "A::__cedar",
716 "A::__cedar::B",
717 ] {
718 assert!(InternalName::from_normalized_str(n).unwrap().is_reserved());
719 }
720
721 for n in ["__cedarr", "A::_cedar", "A::___cedar::B"] {
722 assert!(!InternalName::from_normalized_str(n).unwrap().is_reserved());
723 }
724 }
725
726 #[test]
727 fn test_name_identifier_intersection() {
728 let not_reserved_for_ids = [
729 "permit",
730 "forbid",
731 "when",
732 "unless",
733 "principal",
734 "action",
735 "resource",
736 "context",
737 ];
738
739 for id in not_reserved_for_ids {
740 Name::from_normalized_str(&format!("A::{id}")).unwrap();
741 }
742
743 for id in RESERVED_IDS.iter() {
744 Name::from_normalized_str(&format!("A::{id}")).unwrap_err();
745 }
746 }
747}