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