1use crate::ast::*;
18use crate::entities::{err::EntitiesError, json::err::JsonSerializationError, EntityJson};
19use crate::evaluator::{EvaluationError, RestrictedEvaluator};
20use crate::extensions::Extensions;
21use crate::parser::err::ParseErrors;
22use crate::parser::Loc;
23use crate::transitive_closure::TCNode;
24use crate::FromNormalizedStr;
25use educe::Educe;
26use itertools::Itertools;
27use miette::Diagnostic;
28use serde::{de::Deserializer, ser::Serializer, Deserialize, Serialize};
29use smol_str::SmolStr;
30use std::collections::{BTreeMap, HashMap, HashSet};
31use std::str::FromStr;
32use std::sync::Arc;
33use thiserror::Error;
34
35#[cfg(feature = "tolerant-ast")]
36static ERROR_NAME: std::sync::LazyLock<Name> =
37 std::sync::LazyLock::new(|| Name(InternalName::from(Id::new_unchecked("EntityTypeError"))));
38
39#[cfg(feature = "tolerant-ast")]
40static EID_ERROR_STR: &str = "Eid::Error";
41
42#[cfg(feature = "tolerant-ast")]
43static ENTITY_TYPE_ERROR_STR: &str = "EntityType::Error";
44
45#[cfg(feature = "tolerant-ast")]
46static ENTITY_UID_ERROR_STR: &str = "EntityUID::Error";
47
48pub static ACTION_ENTITY_TYPE: &str = "Action";
50
51#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
52#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
53pub enum EntityType {
55 EntityType(Name),
57 #[cfg(feature = "tolerant-ast")]
58 ErrorEntityType,
60}
61
62impl<'de> Deserialize<'de> for EntityType {
63 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
64 where
65 D: Deserializer<'de>,
66 {
67 let name = Name::deserialize(deserializer)?;
68 Ok(EntityType::EntityType(name))
69 }
70}
71
72impl Serialize for EntityType {
73 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
74 where
75 S: Serializer,
76 {
77 match self {
78 EntityType::EntityType(name) => name.serialize(serializer),
79 #[cfg(feature = "tolerant-ast")]
80 EntityType::ErrorEntityType => serializer.serialize_str(ENTITY_TYPE_ERROR_STR),
81 }
82 }
83}
84
85impl EntityType {
86 pub fn is_action(&self) -> bool {
91 match self {
92 EntityType::EntityType(name) => {
93 name.as_ref().basename() == &Id::new_unchecked(ACTION_ENTITY_TYPE)
94 }
95 #[cfg(feature = "tolerant-ast")]
96 EntityType::ErrorEntityType => false,
97 }
98 }
99
100 pub fn name(&self) -> &Name {
102 match self {
103 EntityType::EntityType(name) => name,
104 #[cfg(feature = "tolerant-ast")]
105 EntityType::ErrorEntityType => &ERROR_NAME,
106 }
107 }
108
109 pub fn loc(&self) -> Option<&Loc> {
111 match self {
112 EntityType::EntityType(name) => name.as_ref().loc(),
113 #[cfg(feature = "tolerant-ast")]
114 EntityType::ErrorEntityType => None,
115 }
116 }
117
118 pub fn with_loc(&self, loc: Option<&Loc>) -> Self {
120 match self {
121 EntityType::EntityType(name) => EntityType::EntityType(Name(InternalName {
122 id: name.0.id.clone(),
123 path: name.0.path.clone(),
124 loc: loc.cloned(),
125 })),
126 #[cfg(feature = "tolerant-ast")]
127 EntityType::ErrorEntityType => self.clone(),
128 }
129 }
130
131 pub fn qualify_with(&self, namespace: Option<&Name>) -> Self {
133 match self {
134 EntityType::EntityType(name) => Self::EntityType(name.qualify_with_name(namespace)),
135 #[cfg(feature = "tolerant-ast")]
136 EntityType::ErrorEntityType => Self::ErrorEntityType,
137 }
138 }
139
140 pub fn from_normalized_str(src: &str) -> Result<Self, ParseErrors> {
142 Name::from_normalized_str(src).map(Into::into)
143 }
144}
145
146impl From<Name> for EntityType {
147 fn from(n: Name) -> Self {
148 Self::EntityType(n)
149 }
150}
151
152impl From<EntityType> for Name {
153 fn from(ty: EntityType) -> Name {
154 match ty {
155 EntityType::EntityType(name) => name,
156 #[cfg(feature = "tolerant-ast")]
157 EntityType::ErrorEntityType => ERROR_NAME.clone(),
158 }
159 }
160}
161
162impl AsRef<Name> for EntityType {
163 fn as_ref(&self) -> &Name {
164 match self {
165 EntityType::EntityType(name) => name,
166 #[cfg(feature = "tolerant-ast")]
167 EntityType::ErrorEntityType => &ERROR_NAME,
168 }
169 }
170}
171
172impl FromStr for EntityType {
173 type Err = ParseErrors;
174
175 fn from_str(s: &str) -> Result<Self, Self::Err> {
176 s.parse().map(Self::EntityType)
177 }
178}
179
180impl std::fmt::Display for EntityType {
181 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182 match self {
183 EntityType::EntityType(name) => write!(f, "{name}"),
184 #[cfg(feature = "tolerant-ast")]
185 EntityType::ErrorEntityType => write!(f, "{ENTITY_TYPE_ERROR_STR}"),
186 }
187 }
188}
189
190#[derive(Educe, Serialize, Deserialize, Debug, Clone)]
192#[serde(rename = "EntityUID")]
193#[educe(PartialEq, Eq, Hash, PartialOrd, Ord)]
194pub struct EntityUIDImpl {
195 ty: EntityType,
197 eid: Eid,
199 #[serde(skip)]
201 #[educe(PartialEq(ignore))]
202 #[educe(Hash(ignore))]
203 #[educe(PartialOrd(ignore))]
204 loc: Option<Loc>,
205}
206
207impl EntityUIDImpl {
208 pub fn loc(&self) -> Option<Loc> {
210 self.loc.clone()
211 }
212}
213
214#[derive(Educe, Debug, Clone)]
216#[educe(PartialEq, Eq, Hash, PartialOrd, Ord)]
217pub enum EntityUID {
218 EntityUID(EntityUIDImpl),
220 #[cfg(feature = "tolerant-ast")]
221 Error,
223}
224
225impl<'de> Deserialize<'de> for EntityUID {
226 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
227 where
228 D: Deserializer<'de>,
229 {
230 let uid_impl = EntityUIDImpl::deserialize(deserializer)?;
231 Ok(EntityUID::EntityUID(uid_impl))
232 }
233}
234
235impl Serialize for EntityUID {
236 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
237 where
238 S: Serializer,
239 {
240 match self {
241 EntityUID::EntityUID(uid_impl) => uid_impl.serialize(serializer),
242 #[cfg(feature = "tolerant-ast")]
243 EntityUID::Error => serializer.serialize_str(ENTITY_UID_ERROR_STR),
244 }
245 }
246}
247
248impl StaticallyTyped for EntityUID {
249 fn type_of(&self) -> Type {
250 match self {
251 EntityUID::EntityUID(entity_uid) => Type::Entity {
252 ty: entity_uid.ty.clone(),
253 },
254 #[cfg(feature = "tolerant-ast")]
255 EntityUID::Error => Type::Entity {
256 ty: EntityType::ErrorEntityType,
257 },
258 }
259 }
260}
261
262#[cfg(test)]
263impl EntityUID {
264 pub(crate) fn with_eid(eid: &str) -> Self {
267 Self::EntityUID(EntityUIDImpl {
268 ty: Self::test_entity_type(),
269 eid: Eid::Eid(eid.into()),
270 loc: None,
271 })
272 }
273
274 pub(crate) fn test_entity_type() -> EntityType {
276 let name = Name::parse_unqualified_name("test_entity_type")
277 .expect("test_entity_type should be a valid identifier");
278 EntityType::EntityType(name)
279 }
280}
281
282impl EntityUID {
283 pub fn with_eid_and_type(typename: &str, eid: &str) -> Result<Self, ParseErrors> {
285 Ok(Self::EntityUID(EntityUIDImpl {
286 ty: EntityType::EntityType(Name::parse_unqualified_name(typename)?),
287 eid: Eid::Eid(eid.into()),
288 loc: None,
289 }))
290 }
291
292 pub fn components(self) -> (EntityType, Eid) {
295 match self {
296 EntityUID::EntityUID(entity_uid) => (entity_uid.ty, entity_uid.eid),
297 #[cfg(feature = "tolerant-ast")]
298 EntityUID::Error => (EntityType::ErrorEntityType, Eid::ErrorEid),
299 }
300 }
301
302 pub fn loc(&self) -> Option<&Loc> {
304 match self {
305 EntityUID::EntityUID(entity_uid) => entity_uid.loc.as_ref(),
306 #[cfg(feature = "tolerant-ast")]
307 EntityUID::Error => None,
308 }
309 }
310
311 pub fn from_components(ty: EntityType, eid: Eid, loc: Option<Loc>) -> Self {
313 Self::EntityUID(EntityUIDImpl { ty, eid, loc })
314 }
315
316 pub fn entity_type(&self) -> &EntityType {
318 match self {
319 EntityUID::EntityUID(entity_uid) => &entity_uid.ty,
320 #[cfg(feature = "tolerant-ast")]
321 EntityUID::Error => &EntityType::ErrorEntityType,
322 }
323 }
324
325 pub fn eid(&self) -> &Eid {
327 match self {
328 EntityUID::EntityUID(entity_uid) => &entity_uid.eid,
329 #[cfg(feature = "tolerant-ast")]
330 EntityUID::Error => &Eid::ErrorEid,
331 }
332 }
333
334 pub fn is_action(&self) -> bool {
336 self.entity_type().is_action()
337 }
338}
339
340impl std::fmt::Display for EntityUID {
341 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342 write!(f, "{}::\"{}\"", self.entity_type(), self.eid().escaped())
343 }
344}
345
346impl std::str::FromStr for EntityUID {
348 type Err = ParseErrors;
349
350 fn from_str(s: &str) -> Result<Self, Self::Err> {
351 crate::parser::parse_euid(s)
352 }
353}
354
355impl FromNormalizedStr for EntityUID {
356 fn describe_self() -> &'static str {
357 "Entity UID"
358 }
359}
360
361#[cfg(feature = "arbitrary")]
362impl<'a> arbitrary::Arbitrary<'a> for EntityUID {
363 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
364 Ok(Self::EntityUID(EntityUIDImpl {
365 ty: u.arbitrary()?,
366 eid: u.arbitrary()?,
367 loc: None,
368 }))
369 }
370}
371
372#[derive(PartialEq, Eq, Debug, Clone, Hash, PartialOrd, Ord)]
382pub enum Eid {
383 Eid(SmolStr),
385 #[cfg(feature = "tolerant-ast")]
386 ErrorEid,
388}
389
390impl<'de> Deserialize<'de> for Eid {
391 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
392 where
393 D: Deserializer<'de>,
394 {
395 let value = String::deserialize(deserializer)?;
396 Ok(Eid::Eid(SmolStr::from(value)))
397 }
398}
399
400impl Serialize for Eid {
401 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
402 where
403 S: Serializer,
404 {
405 match self {
406 Eid::Eid(s) => s.serialize(serializer),
407 #[cfg(feature = "tolerant-ast")]
408 Eid::ErrorEid => serializer.serialize_str(EID_ERROR_STR),
409 }
410 }
411}
412
413impl Eid {
414 pub fn new(eid: impl Into<SmolStr>) -> Self {
416 Eid::Eid(eid.into())
417 }
418
419 pub fn escaped(&self) -> SmolStr {
421 match self {
422 Eid::Eid(smol_str) => smol_str.escape_debug().collect(),
423 #[cfg(feature = "tolerant-ast")]
424 Eid::ErrorEid => SmolStr::new_static(EID_ERROR_STR),
425 }
426 }
427
428 pub fn into_smolstr(self) -> SmolStr {
430 match self {
431 Eid::Eid(smol_str) => smol_str,
432 #[cfg(feature = "tolerant-ast")]
433 Eid::ErrorEid => SmolStr::new_static(EID_ERROR_STR),
434 }
435 }
436}
437
438impl AsRef<str> for Eid {
439 fn as_ref(&self) -> &str {
440 match self {
441 Eid::Eid(smol_str) => smol_str,
442 #[cfg(feature = "tolerant-ast")]
443 Eid::ErrorEid => EID_ERROR_STR,
444 }
445 }
446}
447
448#[cfg(feature = "arbitrary")]
449impl<'a> arbitrary::Arbitrary<'a> for Eid {
450 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
451 let x: String = u.arbitrary()?;
452 Ok(Self::Eid(x.into()))
453 }
454}
455
456#[derive(Debug, Clone)]
458pub struct Entity {
459 uid: EntityUID,
461
462 attrs: BTreeMap<SmolStr, PartialValue>,
466
467 indirect_ancestors: HashSet<EntityUID>,
469
470 parents: HashSet<EntityUID>,
476
477 tags: BTreeMap<SmolStr, PartialValue>,
482}
483
484impl std::hash::Hash for Entity {
485 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
486 self.uid.hash(state);
487 }
488}
489
490impl Entity {
491 pub fn new(
496 uid: EntityUID,
497 attrs: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>,
498 indirect_ancestors: HashSet<EntityUID>,
499 parents: HashSet<EntityUID>,
500 tags: impl IntoIterator<Item = (SmolStr, RestrictedExpr)>,
501 extensions: &Extensions<'_>,
502 ) -> Result<Self, EntityAttrEvaluationError> {
503 let evaluator = RestrictedEvaluator::new(extensions);
504 let evaluate_kvs = |(k, v): (SmolStr, RestrictedExpr), was_attr: bool| {
505 let attr_val = evaluator
506 .partial_interpret(v.as_borrowed())
507 .map_err(|err| EntityAttrEvaluationError {
508 uid: uid.clone(),
509 attr_or_tag: k.clone(),
510 was_attr,
511 err,
512 })?;
513 Ok((k, attr_val))
514 };
515 let evaluated_attrs = attrs
516 .into_iter()
517 .map(|kv| evaluate_kvs(kv, true))
518 .collect::<Result<_, EntityAttrEvaluationError>>()?;
519 let evaluated_tags = tags
520 .into_iter()
521 .map(|kv| evaluate_kvs(kv, false))
522 .collect::<Result<_, EntityAttrEvaluationError>>()?;
523 Ok(Entity {
524 uid,
525 attrs: evaluated_attrs,
526 indirect_ancestors,
527 parents,
528 tags: evaluated_tags,
529 })
530 }
531
532 pub fn new_with_attr_partial_value(
541 uid: EntityUID,
542 attrs: impl IntoIterator<Item = (SmolStr, PartialValue)>,
543 indirect_ancestors: HashSet<EntityUID>,
544 parents: HashSet<EntityUID>,
545 tags: impl IntoIterator<Item = (SmolStr, PartialValue)>,
546 ) -> Self {
547 Self {
548 uid,
549 attrs: attrs.into_iter().collect(),
550 indirect_ancestors,
551 parents,
552 tags: tags.into_iter().collect(),
553 }
554 }
555
556 pub fn uid(&self) -> &EntityUID {
558 &self.uid
559 }
560
561 pub fn get(&self, attr: &str) -> Option<&PartialValue> {
563 self.attrs.get(attr)
564 }
565
566 pub fn get_tag(&self, tag: &str) -> Option<&PartialValue> {
568 self.tags.get(tag)
569 }
570
571 pub fn is_descendant_of(&self, e: &EntityUID) -> bool {
573 self.parents.contains(e) || self.indirect_ancestors.contains(e)
574 }
575
576 pub fn is_indirect_descendant_of(&self, e: &EntityUID) -> bool {
578 self.indirect_ancestors.contains(e)
579 }
580
581 pub fn is_child_of(&self, e: &EntityUID) -> bool {
583 self.parents.contains(e)
584 }
585
586 pub fn ancestors(&self) -> impl Iterator<Item = &EntityUID> {
588 self.parents.iter().chain(self.indirect_ancestors.iter())
589 }
590
591 pub fn indirect_ancestors(&self) -> impl Iterator<Item = &EntityUID> {
593 self.indirect_ancestors.iter()
594 }
595
596 pub fn parents(&self) -> impl Iterator<Item = &EntityUID> {
598 self.parents.iter()
599 }
600
601 pub fn attrs_len(&self) -> usize {
603 self.attrs.len()
604 }
605
606 pub fn tags_len(&self) -> usize {
608 self.tags.len()
609 }
610
611 pub fn keys(&self) -> impl Iterator<Item = &SmolStr> {
613 self.attrs.keys()
614 }
615
616 pub fn tag_keys(&self) -> impl Iterator<Item = &SmolStr> {
618 self.tags.keys()
619 }
620
621 pub fn attrs(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
623 self.attrs.iter()
624 }
625
626 pub fn tags(&self) -> impl Iterator<Item = (&SmolStr, &PartialValue)> {
628 self.tags.iter()
629 }
630
631 pub fn with_uid(uid: EntityUID) -> Self {
633 Self {
634 uid,
635 attrs: BTreeMap::new(),
636 indirect_ancestors: HashSet::new(),
637 parents: HashSet::new(),
638 tags: BTreeMap::new(),
639 }
640 }
641
642 pub fn deep_eq(&self, other: &Self) -> bool {
648 self.uid == other.uid
649 && self.attrs == other.attrs
650 && self.tags == other.tags
651 && (self.ancestors().collect::<HashSet<_>>())
652 == (other.ancestors().collect::<HashSet<_>>())
653 }
654
655 pub fn add_indirect_ancestor(&mut self, uid: EntityUID) {
662 if !self.parents.contains(&uid) {
663 self.indirect_ancestors.insert(uid);
664 }
665 }
666
667 pub fn add_parent(&mut self, uid: EntityUID) {
673 self.indirect_ancestors.remove(&uid);
674 self.parents.insert(uid);
675 }
676
677 pub fn remove_indirect_ancestor(&mut self, uid: &EntityUID) {
683 self.indirect_ancestors.remove(uid);
684 }
685
686 pub fn remove_parent(&mut self, uid: &EntityUID) {
692 self.parents.remove(uid);
693 }
694
695 pub fn remove_all_indirect_ancestors(&mut self) {
700 self.indirect_ancestors.clear();
701 }
702
703 #[allow(clippy::type_complexity)]
705 pub fn into_inner(
706 self,
707 ) -> (
708 EntityUID,
709 HashMap<SmolStr, PartialValue>,
710 HashSet<EntityUID>,
711 HashSet<EntityUID>,
712 HashMap<SmolStr, PartialValue>,
713 ) {
714 (
715 self.uid,
716 self.attrs.into_iter().collect(),
717 self.indirect_ancestors,
718 self.parents,
719 self.tags.into_iter().collect(),
720 )
721 }
722
723 pub fn write_to_json(&self, f: impl std::io::Write) -> Result<(), EntitiesError> {
725 let ejson = EntityJson::from_entity(self)?;
726 serde_json::to_writer_pretty(f, &ejson).map_err(JsonSerializationError::from)?;
727 Ok(())
728 }
729
730 pub fn to_json_value(&self) -> Result<serde_json::Value, EntitiesError> {
732 let ejson = EntityJson::from_entity(self)?;
733 let v = serde_json::to_value(ejson).map_err(JsonSerializationError::from)?;
734 Ok(v)
735 }
736
737 pub fn to_json_string(&self) -> Result<String, EntitiesError> {
739 let ejson = EntityJson::from_entity(self)?;
740 let string = serde_json::to_string(&ejson).map_err(JsonSerializationError::from)?;
741 Ok(string)
742 }
743}
744
745impl PartialEq for Entity {
747 fn eq(&self, other: &Self) -> bool {
748 self.uid() == other.uid()
749 }
750}
751
752impl Eq for Entity {}
753
754impl StaticallyTyped for Entity {
755 fn type_of(&self) -> Type {
756 self.uid.type_of()
757 }
758}
759
760impl TCNode<EntityUID> for Entity {
761 fn get_key(&self) -> EntityUID {
762 self.uid().clone()
763 }
764
765 fn add_edge_to(&mut self, k: EntityUID) {
766 self.add_indirect_ancestor(k);
767 }
768
769 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
770 Box::new(self.ancestors())
771 }
772
773 fn has_edge_to(&self, e: &EntityUID) -> bool {
774 self.is_descendant_of(e)
775 }
776
777 fn reset_edges(&mut self) {
778 self.remove_all_indirect_ancestors()
779 }
780
781 fn direct_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
782 Box::new(self.parents())
783 }
784}
785
786impl TCNode<EntityUID> for Arc<Entity> {
787 fn get_key(&self) -> EntityUID {
788 self.uid().clone()
789 }
790
791 fn add_edge_to(&mut self, k: EntityUID) {
792 Arc::make_mut(self).add_indirect_ancestor(k)
794 }
795
796 fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
797 Box::new(self.ancestors())
798 }
799
800 fn has_edge_to(&self, e: &EntityUID) -> bool {
801 self.is_descendant_of(e)
802 }
803
804 fn reset_edges(&mut self) {
805 Arc::make_mut(self).remove_all_indirect_ancestors()
807 }
808
809 fn direct_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
810 Box::new(self.parents())
811 }
812}
813
814impl std::fmt::Display for Entity {
815 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
816 write!(
817 f,
818 "{}:\n attrs:{}\n ancestors:{}",
819 self.uid,
820 self.attrs
821 .iter()
822 .map(|(k, v)| format!("{k}: {v}"))
823 .join("; "),
824 self.ancestors().join(", ")
825 )
826 }
827}
828
829#[derive(Debug, Diagnostic, Error)]
835#[error("failed to evaluate {} `{attr_or_tag}` of `{uid}`: {err}", if *.was_attr { "attribute" } else { "tag" })]
836pub struct EntityAttrEvaluationError {
837 pub uid: EntityUID,
839 pub attr_or_tag: SmolStr,
841 pub was_attr: bool,
843 #[diagnostic(transparent)]
845 pub err: EvaluationError,
846}
847
848#[cfg(test)]
849mod test {
850 use std::str::FromStr;
851
852 use super::*;
853
854 #[test]
855 fn display() {
856 let e = EntityUID::with_eid("eid");
857 assert_eq!(format!("{e}"), "test_entity_type::\"eid\"");
858 }
859
860 #[test]
861 fn test_euid_equality() {
862 let e1 = EntityUID::with_eid("foo");
863 let e2 = EntityUID::from_components(
864 Name::parse_unqualified_name("test_entity_type")
865 .expect("should be a valid identifier")
866 .into(),
867 Eid::Eid("foo".into()),
868 None,
869 );
870 let e3 = EntityUID::from_components(
871 Name::parse_unqualified_name("Unspecified")
872 .expect("should be a valid identifier")
873 .into(),
874 Eid::Eid("foo".into()),
875 None,
876 );
877
878 assert_eq!(e1, e1);
880 assert_eq!(e2, e2);
881
882 assert_eq!(e1, e2);
884
885 assert!(e1 != e3);
887 }
888
889 #[test]
890 fn action_checker() {
891 let euid = EntityUID::from_str("Action::\"view\"").unwrap();
892 assert!(euid.is_action());
893 let euid = EntityUID::from_str("Foo::Action::\"view\"").unwrap();
894 assert!(euid.is_action());
895 let euid = EntityUID::from_str("Foo::\"view\"").unwrap();
896 assert!(!euid.is_action());
897 let euid = EntityUID::from_str("Action::Foo::\"view\"").unwrap();
898 assert!(!euid.is_action());
899 }
900
901 #[test]
902 fn action_type_is_valid_id() {
903 assert!(Id::from_normalized_str(ACTION_ENTITY_TYPE).is_ok());
904 }
905
906 #[cfg(feature = "tolerant-ast")]
907 #[test]
908 fn error_entity() {
909 use cool_asserts::assert_matches;
910
911 let e = EntityUID::Error;
912 assert_matches!(e.eid(), Eid::ErrorEid);
913 assert_matches!(e.entity_type(), EntityType::ErrorEntityType);
914 assert!(!e.is_action());
915 assert_matches!(e.loc(), None);
916
917 let error_eid = Eid::ErrorEid;
918 assert_eq!(error_eid.escaped(), "Eid::Error");
919
920 let error_type = EntityType::ErrorEntityType;
921 assert!(!error_type.is_action());
922 assert_eq!(error_type.qualify_with(None), EntityType::ErrorEntityType);
923 assert_eq!(
924 error_type.qualify_with(Some(&Name(InternalName::from(Id::new_unchecked(
925 "EntityTypeError"
926 ))))),
927 EntityType::ErrorEntityType
928 );
929
930 assert_eq!(
931 error_type.name(),
932 &Name(InternalName::from(Id::new_unchecked("EntityTypeError")))
933 );
934 assert_eq!(error_type.loc(), None)
935 }
936
937 #[test]
938 fn entity_type_deserialization() {
939 let json = r#""some_entity_type""#;
940 let entity_type: EntityType = serde_json::from_str(json).unwrap();
941 assert_eq!(
942 entity_type.name().0.to_string(),
943 "some_entity_type".to_string()
944 )
945 }
946
947 #[test]
948 fn entity_type_serialization() {
949 let entity_type = EntityType::EntityType(Name(InternalName::from(Id::new_unchecked(
950 "some_entity_type",
951 ))));
952 let serialized = serde_json::to_string(&entity_type).unwrap();
953
954 assert_eq!(serialized, r#""some_entity_type""#);
955 }
956}