1use crate::{
20 ast::{Eid, EntityUID, InternalName, Name, UnreservedId},
21 entities::CedarValueJson,
22 est::Annotations,
23 extensions::Extensions,
24 parser::Loc,
25 validator::{SchemaError, ValidatorSchemaFragment},
26 FromNormalizedStr,
27};
28use educe::Educe;
29use itertools::Itertools;
30use nonempty::{nonempty, NonEmpty};
31use serde::{
32 de::{MapAccess, Visitor},
33 ser::SerializeMap,
34 Deserialize, Deserializer, Serialize, Serializer,
35};
36use serde_with::serde_as;
37use smol_str::{SmolStr, ToSmolStr};
38use std::hash::Hash;
39use std::{
40 collections::{BTreeMap, HashMap, HashSet},
41 fmt::Display,
42 marker::PhantomData,
43 str::FromStr,
44};
45use thiserror::Error;
46
47use crate::validator::{
48 cedar_schema::{
49 self, fmt::ToCedarSchemaSyntaxError, parser::parse_cedar_schema_fragment, SchemaWarning,
50 },
51 err::{schema_errors::*, Result},
52 AllDefs, CedarSchemaError, CedarSchemaParseError, ConditionalName, RawName, ReferenceType,
53};
54
55#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
57#[educe(PartialEq, Eq)]
58#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
59pub struct CommonType<N> {
60 #[serde(flatten)]
62 pub ty: Type<N>,
63 #[serde(default)]
65 #[serde(skip_serializing_if = "Annotations::is_empty")]
66 pub annotations: Annotations,
67 #[serde(skip)]
73 #[educe(PartialEq(ignore))]
74 pub loc: Option<Loc>,
75}
76
77#[derive(Educe, Debug, Clone, Deserialize)]
97#[educe(PartialEq, Eq)]
98#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
99#[serde(transparent)]
100#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
101#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
102#[cfg_attr(feature = "wasm", serde(rename = "SchemaJson"))]
103pub struct Fragment<N>(
104 #[serde(deserialize_with = "deserialize_schema_fragment")]
105 #[cfg_attr(
106 feature = "wasm",
107 tsify(type = "Record<string, NamespaceDefinition<N>>")
108 )]
109 pub BTreeMap<Option<Name>, NamespaceDefinition<N>>,
110);
111
112fn deserialize_schema_fragment<'de, D, N: Deserialize<'de> + From<RawName>>(
114 deserializer: D,
115) -> std::result::Result<BTreeMap<Option<Name>, NamespaceDefinition<N>>, D::Error>
116where
117 D: Deserializer<'de>,
118{
119 let raw: BTreeMap<SmolStr, NamespaceDefinition<N>> =
120 serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
121 Ok(BTreeMap::from_iter(
122 raw.into_iter()
123 .map(|(key, value)| {
124 let key = if key.is_empty() {
125 if !value.annotations.is_empty() {
126 Err(serde::de::Error::custom(
127 "annotations are not allowed on the empty namespace".to_string(),
128 ))?
129 }
130 None
131 } else {
132 Some(Name::from_normalized_str(&key).map_err(|err| {
133 serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
134 })?)
135 };
136 Ok((key, value))
137 })
138 .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition<N>)>, D::Error>>(
139 )?,
140 ))
141}
142
143impl<N: Serialize> Serialize for Fragment<N> {
144 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
146 where
147 S: Serializer,
148 {
149 let mut map = serializer.serialize_map(Some(self.0.len()))?;
150 for (k, v) in &self.0 {
151 let k: SmolStr = match k {
152 None => "".into(),
153 Some(name) => name.to_smolstr(),
154 };
155 map.serialize_entry(&k, &v)?;
156 }
157 map.end()
158 }
159}
160
161impl Fragment<RawName> {
162 pub fn from_json_str(json: &str) -> Result<Self> {
165 serde_json::from_str(json).map_err(|e| JsonDeserializationError::new(e, Some(json)).into())
166 }
167
168 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
171 serde_json::from_value(json).map_err(|e| JsonDeserializationError::new(e, None).into())
172 }
173
174 pub fn from_json_file(file: impl std::io::Read) -> Result<Self> {
176 serde_json::from_reader(file).map_err(|e| JsonDeserializationError::new(e, None).into())
177 }
178
179 pub fn from_cedarschema_str<'a>(
181 src: &str,
182 extensions: &Extensions<'a>,
183 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
184 {
185 parse_cedar_schema_fragment(src, extensions)
186 .map_err(|e| CedarSchemaParseError::new(e, src).into())
187 }
188
189 pub fn from_cedarschema_file<'a>(
191 mut file: impl std::io::Read,
192 extensions: &'a Extensions<'_>,
193 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
194 {
195 let mut src = String::new();
196 file.read_to_string(&mut src)?;
197 Self::from_cedarschema_str(&src, extensions)
198 }
199
200 pub fn to_internal_name_fragment_with_resolved_types(
204 &self,
205 ) -> std::result::Result<Fragment<InternalName>, SchemaError> {
206 let validator_fragment = ValidatorSchemaFragment::from_schema_fragment(self.clone())?;
207
208 let mut all_defs = AllDefs::single_fragment(&validator_fragment);
209
210 let cedar_namespace = InternalName::__cedar();
212
213 let primitives_as_internal_names: Vec<InternalName> = ["Bool", "Long", "String"]
214 .into_iter()
215 .map(|n| {
216 #[expect(clippy::unwrap_used, reason = "these are all valid InternalName's")]
217 InternalName::parse_unqualified_name(n).unwrap()
218 })
219 .collect();
220
221 for tyname in &primitives_as_internal_names {
222 all_defs.mark_as_defined_as_common_type(tyname.qualify_with(Some(&cedar_namespace)));
224 if !all_defs.is_defined_as_common(tyname) && !all_defs.is_defined_as_entity(tyname) {
226 all_defs.mark_as_defined_as_common_type(tyname.clone());
227 }
228 }
229
230 for ext_type in Extensions::all_available().ext_types() {
233 all_defs.mark_as_defined_as_common_type(
234 ext_type.as_ref().qualify_with(Some(&cedar_namespace)),
235 );
236 if !all_defs.is_defined_as_common(ext_type.as_ref())
237 && !all_defs.is_defined_as_entity(ext_type.as_ref())
238 {
239 all_defs.mark_as_defined_as_common_type(ext_type.as_ref().qualify_with(None));
240 }
241 }
242
243 let conditional_fragment = Fragment(
245 self.0
246 .iter()
247 .map(|(ns_name, ns_def)| {
248 let internal_ns_name = ns_name.as_ref().map(|name| name.clone().into());
249 let conditional_ns_def = ns_def
250 .clone()
251 .conditionally_qualify_type_references(internal_ns_name.as_ref());
252 (ns_name.clone(), conditional_ns_def)
253 })
254 .collect(),
255 );
256
257 let internal_name_fragment = Fragment(
259 conditional_fragment
260 .0
261 .into_iter()
262 .map(|(ns_name, ns_def)| {
263 ns_def
264 .fully_qualify_type_references(&all_defs)
265 .map(|resolved_ns_def| (ns_name, resolved_ns_def))
266 })
267 .collect::<Result<BTreeMap<_, _>>>()?,
268 );
269
270 internal_name_fragment.resolve_entity_or_common_types(&all_defs)
272 }
273}
274
275impl<N: Display> Fragment<N> {
276 pub fn to_cedarschema(&self) -> std::result::Result<String, ToCedarSchemaSyntaxError> {
278 let src = cedar_schema::fmt::json_schema_to_cedar_schema_str(self)?;
279 Ok(src)
280 }
281}
282
283impl Fragment<InternalName> {
284 pub fn resolve_entity_or_common_types(
286 self,
287 all_defs: &AllDefs,
288 ) -> Result<Fragment<InternalName>> {
289 Ok(Fragment(
290 self.0
291 .into_iter()
292 .map(|(ns_name, ns_def)| {
293 Ok((ns_name, ns_def.resolve_entity_or_common_types(all_defs)?))
294 })
295 .collect::<Result<_>>()?,
296 ))
297 }
298}
299
300#[derive(Educe, Debug, Clone, Serialize)]
303#[educe(PartialEq, Eq, PartialOrd, Ord, Hash)]
304#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
305#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
306pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
307
308impl From<CommonTypeId> for UnreservedId {
309 fn from(value: CommonTypeId) -> Self {
310 value.0
311 }
312}
313
314impl AsRef<UnreservedId> for CommonTypeId {
315 fn as_ref(&self) -> &UnreservedId {
316 &self.0
317 }
318}
319
320impl CommonTypeId {
321 pub fn new(id: UnreservedId) -> std::result::Result<Self, ReservedCommonTypeBasenameError> {
323 if Self::is_reserved_schema_keyword(&id) {
324 Err(ReservedCommonTypeBasenameError { id })
325 } else {
326 Ok(Self(id))
327 }
328 }
329
330 pub fn unchecked(id: UnreservedId) -> Self {
333 Self(id)
334 }
335
336 fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
341 matches!(
342 id.as_ref(),
343 "Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
344 )
345 }
346
347 #[cfg(feature = "arbitrary")]
350 fn make_into_valid_common_type_id(id: &UnreservedId) -> Self {
351 Self::new(id.clone()).unwrap_or_else(|_| {
352 #[expect(
353 clippy::unwrap_used,
354 reason = "`_Bool`, `_Record`, and etc are valid unreserved names."
355 )]
356 let new_id = format!("_{id}").parse().unwrap();
357 #[expect(
358 clippy::unwrap_used,
359 reason = "`_Bool`, `_Record`, and etc are valid common type basenames."
360 )]
361 Self::new(new_id).unwrap()
362 })
363 }
364}
365
366impl Display for CommonTypeId {
367 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
368 self.0.fmt(f)
369 }
370}
371
372#[cfg(feature = "arbitrary")]
373impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
374 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
375 let id: UnreservedId = u.arbitrary()?;
376 Ok(CommonTypeId::make_into_valid_common_type_id(&id))
377 }
378
379 fn size_hint(depth: usize) -> (usize, Option<usize>) {
380 <UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
381 }
382}
383
384impl<'de> Deserialize<'de> for CommonTypeId {
386 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
387 where
388 D: Deserializer<'de>,
389 {
390 UnreservedId::deserialize(deserializer).and_then(|id| {
391 CommonTypeId::new(id).map_err(|e| serde::de::Error::custom(format!("{e}")))
392 })
393 }
394}
395
396#[derive(Debug, Error, PartialEq, Eq, Clone)]
398#[error("this is reserved and cannot be the basename of a common-type declaration: {id}")]
399pub struct ReservedCommonTypeBasenameError {
400 pub(crate) id: UnreservedId,
402}
403
404#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
414#[educe(PartialEq, Eq)]
415#[serde_as]
416#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
417#[serde(bound(serialize = "N: Serialize"))]
418#[serde(deny_unknown_fields)]
419#[serde(rename_all = "camelCase")]
420#[doc(hidden)]
421#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
422#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
423pub struct NamespaceDefinition<N> {
424 #[serde(default)]
425 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
426 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
427 pub common_types: BTreeMap<CommonTypeId, CommonType<N>>,
428 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
429 pub entity_types: BTreeMap<UnreservedId, EntityType<N>>,
430 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
431 pub actions: BTreeMap<SmolStr, ActionType<N>>,
432 #[serde(default)]
434 #[serde(skip_serializing_if = "Annotations::is_empty")]
435 pub annotations: Annotations,
436
437 #[cfg(feature = "extended-schema")]
438 #[serde(skip)]
439 #[educe(Eq(ignore))]
440 pub loc: Option<Loc>,
441}
442
443#[cfg(test)]
444impl<N> NamespaceDefinition<N> {
445 pub fn new(
448 entity_types: impl IntoIterator<Item = (UnreservedId, EntityType<N>)>,
449 actions: impl IntoIterator<Item = (SmolStr, ActionType<N>)>,
450 ) -> Self {
451 Self {
452 common_types: BTreeMap::new(),
453 entity_types: entity_types.into_iter().collect(),
454 actions: actions.into_iter().collect(),
455 annotations: Annotations::new(),
456 #[cfg(feature = "extended-schema")]
457 loc: None,
458 }
459 }
460}
461
462impl NamespaceDefinition<RawName> {
463 pub fn conditionally_qualify_type_references(
465 self,
466 ns: Option<&InternalName>,
467 ) -> NamespaceDefinition<ConditionalName> {
468 NamespaceDefinition {
469 common_types: self
470 .common_types
471 .into_iter()
472 .map(|(k, v)| {
473 (
474 k,
475 CommonType {
476 ty: v.ty.conditionally_qualify_type_references(ns),
477 annotations: v.annotations,
478 loc: v.loc,
479 },
480 )
481 })
482 .collect(),
483 entity_types: self
484 .entity_types
485 .into_iter()
486 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
487 .collect(),
488 actions: self
489 .actions
490 .into_iter()
491 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
492 .collect(),
493 annotations: self.annotations,
494 #[cfg(feature = "extended-schema")]
495 loc: self.loc,
496 }
497 }
498}
499
500impl NamespaceDefinition<ConditionalName> {
501 pub fn fully_qualify_type_references(
508 self,
509 all_defs: &AllDefs,
510 ) -> Result<NamespaceDefinition<InternalName>> {
511 Ok(NamespaceDefinition {
512 common_types: self
513 .common_types
514 .into_iter()
515 .map(|(k, v)| {
516 Ok((
517 k,
518 CommonType {
519 ty: v.ty.fully_qualify_type_references(all_defs)?,
520 annotations: v.annotations,
521 loc: v.loc,
522 },
523 ))
524 })
525 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
526 entity_types: self
527 .entity_types
528 .into_iter()
529 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
530 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
531 actions: self
532 .actions
533 .into_iter()
534 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
535 .collect::<Result<_>>()?,
536 annotations: self.annotations,
537 #[cfg(feature = "extended-schema")]
538 loc: self.loc,
539 })
540 }
541}
542
543enum ResolvedTypeVariant {
545 TypeVariant(TypeVariant<InternalName>),
546 CommonTypeRef(InternalName),
547}
548
549impl NamespaceDefinition<InternalName> {
550 pub fn resolve_entity_or_common_types(
552 self,
553 all_defs: &AllDefs,
554 ) -> Result<NamespaceDefinition<InternalName>> {
555 Ok(NamespaceDefinition {
556 common_types: self
557 .common_types
558 .into_iter()
559 .map(|(k, v)| {
560 Ok((
561 k,
562 CommonType {
563 ty: v.ty.resolve_entity_or_common_type(all_defs)?,
564 annotations: v.annotations,
565 loc: v.loc,
566 },
567 ))
568 })
569 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
570 entity_types: self
571 .entity_types
572 .into_iter()
573 .map(|(k, v)| Ok((k, v.resolve_entity_type_entity_or_common(all_defs)?)))
574 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
575 actions: self
576 .actions
577 .into_iter()
578 .map(|(k, v)| Ok((k, v.resolve_action_type_entity_or_common(all_defs)?)))
579 .collect::<Result<_>>()?,
580 annotations: self.annotations,
581 #[cfg(feature = "extended-schema")]
582 loc: self.loc,
583 })
584 }
585}
586
587#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
591#[serde(untagged)]
592#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
593#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
594pub enum EntityTypeKind<N> {
595 Standard(StandardEntityType<N>),
597 Enum {
600 #[serde(rename = "enum")]
601 #[cfg_attr(feature = "wasm", tsify(type = "NonEmpty<string>"))]
603 choices: NonEmpty<Eid>,
604 },
605}
606
607#[derive(Educe, Debug, Clone, Serialize)]
616#[educe(PartialEq, Eq)]
617#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
618pub struct EntityType<N> {
619 #[serde(flatten)]
621 pub kind: EntityTypeKind<N>,
622 #[serde(default)]
624 #[serde(skip_serializing_if = "Annotations::is_empty")]
625 pub annotations: Annotations,
626 #[serde(skip)]
632 #[educe(PartialEq(ignore))]
633 pub loc: Option<Loc>,
634}
635
636impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for EntityType<N> {
637 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
638 where
639 D: serde::Deserializer<'de>,
640 {
641 #[derive(Default)]
643 enum RealOption<T> {
644 Some(T),
645 #[default]
646 None,
647 }
648 impl<'de, T: Deserialize<'de>> Deserialize<'de> for RealOption<T> {
649 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
650 where
651 D: Deserializer<'de>,
652 {
653 T::deserialize(deserializer).map(Self::Some)
654 }
655 }
656
657 impl<T> From<RealOption<T>> for Option<T> {
658 fn from(value: RealOption<T>) -> Self {
659 match value {
660 RealOption::Some(v) => Self::Some(v),
661 RealOption::None => None,
662 }
663 }
664 }
665
666 #[derive(Deserialize)]
670 #[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
671 #[serde(deny_unknown_fields)]
672 #[serde(rename_all = "camelCase")]
673 struct Everything<N> {
674 #[serde(default)]
675 member_of_types: RealOption<Vec<N>>,
676 #[serde(default)]
677 shape: RealOption<AttributesOrContext<N>>,
678 #[serde(default)]
679 tags: RealOption<Type<N>>,
680 #[serde(default)]
681 #[serde(rename = "enum")]
682 choices: RealOption<NonEmpty<Eid>>,
683 #[serde(default)]
684 annotations: Annotations,
685 }
686
687 let value: Everything<N> = Everything::deserialize(deserializer)?;
688 if let Some(choices) = value.choices.into() {
692 let mut unexpected_fields: Vec<&str> = vec![];
693 if Option::<Vec<N>>::from(value.member_of_types).is_some() {
694 unexpected_fields.push("memberOfTypes");
695 }
696 if Option::<AttributesOrContext<N>>::from(value.shape).is_some() {
697 unexpected_fields.push("shape");
698 }
699 if Option::<Type<N>>::from(value.tags).is_some() {
700 unexpected_fields.push("tags");
701 }
702 if !unexpected_fields.is_empty() {
703 return Err(serde::de::Error::custom(format!(
704 "unexpected field: {}",
705 unexpected_fields.into_iter().join(", ")
706 )));
707 }
708 Ok(EntityType {
709 kind: EntityTypeKind::Enum { choices },
710 annotations: value.annotations,
711 loc: None,
712 })
713 } else {
714 Ok(EntityType {
715 kind: EntityTypeKind::Standard(StandardEntityType {
716 member_of_types: Option::from(value.member_of_types).unwrap_or_default(),
717 shape: Option::from(value.shape).unwrap_or_default(),
718 tags: Option::from(value.tags),
719 }),
720 annotations: value.annotations,
721 loc: None,
722 })
723 }
724 }
725}
726
727#[derive(Debug, Clone, Serialize, Deserialize, Educe)]
730#[educe(PartialEq, Eq)]
731#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
732#[serde(rename_all = "camelCase")]
733#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
734#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
735pub struct StandardEntityType<N> {
736 #[serde(skip_serializing_if = "Vec::is_empty")]
739 #[serde(default)]
740 pub member_of_types: Vec<N>,
741 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
743 #[serde(default)]
744 pub shape: AttributesOrContext<N>,
745 #[serde(skip_serializing_if = "Option::is_none")]
747 #[serde(default)]
748 pub tags: Option<Type<N>>,
749}
750
751#[cfg(test)]
752impl<N> From<StandardEntityType<N>> for EntityType<N> {
753 fn from(value: StandardEntityType<N>) -> Self {
754 Self {
755 kind: EntityTypeKind::Standard(value),
756 annotations: Annotations::new(),
757 loc: None,
758 }
759 }
760}
761
762impl EntityType<RawName> {
763 pub fn conditionally_qualify_type_references(
765 self,
766 ns: Option<&InternalName>,
767 ) -> EntityType<ConditionalName> {
768 let Self {
769 kind,
770 annotations,
771 loc,
772 } = self;
773 match kind {
774 EntityTypeKind::Enum { choices } => EntityType {
775 kind: EntityTypeKind::Enum { choices },
776 annotations,
777 loc,
778 },
779 EntityTypeKind::Standard(ty) => EntityType {
780 kind: EntityTypeKind::Standard(StandardEntityType {
781 member_of_types: ty
782 .member_of_types
783 .into_iter()
784 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
786 shape: ty.shape.conditionally_qualify_type_references(ns),
787 tags: ty
788 .tags
789 .map(|ty| ty.conditionally_qualify_type_references(ns)),
790 }),
791 annotations,
792 loc,
793 },
794 }
795 }
796}
797
798impl EntityType<ConditionalName> {
799 pub fn fully_qualify_type_references(
806 self,
807 all_defs: &AllDefs,
808 ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
809 let Self {
810 kind,
811 annotations,
812 loc,
813 } = self;
814 Ok(match kind {
815 EntityTypeKind::Enum { choices } => EntityType {
816 kind: EntityTypeKind::Enum { choices },
817 annotations,
818 loc,
819 },
820 EntityTypeKind::Standard(ty) => EntityType {
821 kind: EntityTypeKind::Standard(StandardEntityType {
822 member_of_types: ty
823 .member_of_types
824 .into_iter()
825 .map(|cname| cname.resolve(all_defs))
826 .collect::<std::result::Result<_, _>>()?,
827 shape: ty.shape.fully_qualify_type_references(all_defs)?,
828 tags: ty
829 .tags
830 .map(|ty| ty.fully_qualify_type_references(all_defs))
831 .transpose()?,
832 }),
833 annotations,
834 loc,
835 },
836 })
837 }
838}
839
840#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
847#[educe(PartialEq, Eq)]
848#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
849#[serde(transparent)]
850#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
851#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
852pub struct AttributesOrContext<N>(
853 pub Type<N>,
856);
857
858impl<N> AttributesOrContext<N> {
859 pub fn into_inner(self) -> Type<N> {
861 self.0
862 }
863
864 pub fn is_empty_record(&self) -> bool {
866 self.0.is_empty_record()
867 }
868
869 pub fn loc(&self) -> Option<&Loc> {
871 self.0.loc()
872 }
873}
874
875impl<N> Default for AttributesOrContext<N> {
876 fn default() -> Self {
877 Self::from(RecordType::default())
878 }
879}
880
881impl<N: Display> Display for AttributesOrContext<N> {
882 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
883 self.0.fmt(f)
884 }
885}
886
887impl<N> From<RecordType<N>> for AttributesOrContext<N> {
888 fn from(rty: RecordType<N>) -> AttributesOrContext<N> {
889 Self(Type::Type {
890 ty: TypeVariant::Record(rty),
891 loc: None,
892 })
893 }
894}
895
896impl AttributesOrContext<RawName> {
897 pub fn conditionally_qualify_type_references(
899 self,
900 ns: Option<&InternalName>,
901 ) -> AttributesOrContext<ConditionalName> {
902 AttributesOrContext(self.0.conditionally_qualify_type_references(ns))
903 }
904}
905
906impl AttributesOrContext<ConditionalName> {
907 pub fn fully_qualify_type_references(
914 self,
915 all_defs: &AllDefs,
916 ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
917 Ok(AttributesOrContext(
918 self.0.fully_qualify_type_references(all_defs)?,
919 ))
920 }
921}
922
923#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
931#[educe(PartialEq, Eq)]
932#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
933#[serde(deny_unknown_fields)]
934#[serde(rename_all = "camelCase")]
935#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
936#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
937pub struct ActionType<N> {
938 #[serde(default)]
941 #[serde(skip_serializing_if = "Option::is_none")]
942 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
943 #[serde(default)]
945 #[serde(skip_serializing_if = "Option::is_none")]
946 pub applies_to: Option<ApplySpec<N>>,
947 #[serde(default)]
949 #[serde(skip_serializing_if = "Option::is_none")]
950 pub member_of: Option<Vec<ActionEntityUID<N>>>,
951 #[serde(default)]
953 #[serde(skip_serializing_if = "Annotations::is_empty")]
954 pub annotations: Annotations,
955 #[serde(skip)]
961 #[educe(PartialEq(ignore))]
962 pub loc: Option<Loc>,
963
964 #[cfg(feature = "extended-schema")]
966 #[serde(skip)]
967 #[educe(PartialEq(ignore))]
968 pub(crate) defn_loc: Option<Loc>,
969}
970
971impl ActionType<RawName> {
972 pub fn conditionally_qualify_type_references(
974 self,
975 ns: Option<&InternalName>,
976 ) -> ActionType<ConditionalName> {
977 ActionType {
978 attributes: self.attributes,
979 applies_to: self
980 .applies_to
981 .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
982 member_of: self.member_of.map(|v| {
983 v.into_iter()
984 .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
985 .collect()
986 }),
987 annotations: self.annotations,
988 loc: self.loc,
989 #[cfg(feature = "extended-schema")]
990 defn_loc: self.defn_loc,
991 }
992 }
993}
994
995impl ActionType<ConditionalName> {
996 pub fn fully_qualify_type_references(
1003 self,
1004 all_defs: &AllDefs,
1005 ) -> Result<ActionType<InternalName>> {
1006 Ok(ActionType {
1007 attributes: self.attributes,
1008 applies_to: self
1009 .applies_to
1010 .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
1011 .transpose()?,
1012 member_of: self
1013 .member_of
1014 .map(|v| {
1015 v.into_iter()
1016 .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
1017 .collect::<std::result::Result<_, ActionNotDefinedError>>()
1018 })
1019 .transpose()?,
1020 annotations: self.annotations,
1021 loc: self.loc,
1022 #[cfg(feature = "extended-schema")]
1023 defn_loc: self.defn_loc,
1024 })
1025 }
1026}
1027
1028#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1038#[educe(PartialEq, Eq)]
1039#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1040#[serde(deny_unknown_fields)]
1041#[serde(rename_all = "camelCase")]
1042#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1043#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1044pub struct ApplySpec<N> {
1045 pub resource_types: Vec<N>,
1047 pub principal_types: Vec<N>,
1049 #[serde(default)]
1051 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
1052 pub context: AttributesOrContext<N>,
1053}
1054
1055impl ApplySpec<RawName> {
1056 pub fn conditionally_qualify_type_references(
1058 self,
1059 ns: Option<&InternalName>,
1060 ) -> ApplySpec<ConditionalName> {
1061 ApplySpec {
1062 resource_types: self
1063 .resource_types
1064 .into_iter()
1065 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
1067 principal_types: self
1068 .principal_types
1069 .into_iter()
1070 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
1072 context: self.context.conditionally_qualify_type_references(ns),
1073 }
1074 }
1075}
1076
1077impl ApplySpec<ConditionalName> {
1078 pub fn fully_qualify_type_references(
1085 self,
1086 all_defs: &AllDefs,
1087 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
1088 Ok(ApplySpec {
1089 resource_types: self
1090 .resource_types
1091 .into_iter()
1092 .map(|cname| cname.resolve(all_defs))
1093 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1094 principal_types: self
1095 .principal_types
1096 .into_iter()
1097 .map(|cname| cname.resolve(all_defs))
1098 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1099 context: self.context.fully_qualify_type_references(all_defs)?,
1100 })
1101 }
1102}
1103
1104#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1106#[educe(PartialEq, Eq, Hash)]
1107#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1108#[serde(deny_unknown_fields)]
1109#[serde(rename_all = "camelCase")]
1110#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1111#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1112pub struct ActionEntityUID<N> {
1113 pub id: SmolStr,
1115
1116 #[serde(rename = "type")]
1127 #[serde(default)]
1128 #[serde(skip_serializing_if = "Option::is_none")]
1129 pub ty: Option<N>,
1130 #[cfg(feature = "extended-schema")]
1131 #[serde(skip)]
1132 pub loc: Option<Loc>,
1134}
1135
1136impl ActionEntityUID<RawName> {
1137 pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
1140 Self {
1141 id,
1142 ty,
1143 #[cfg(feature = "extended-schema")]
1144 loc: None,
1145 }
1146 }
1147
1148 pub fn default_type(id: SmolStr) -> Self {
1153 Self {
1154 id,
1155 ty: None,
1156 #[cfg(feature = "extended-schema")]
1157 loc: None,
1158 }
1159 }
1160
1161 #[cfg(feature = "extended-schema")]
1166 pub fn default_type_with_loc(id: SmolStr, loc: Option<Loc>) -> Self {
1167 Self { id, ty: None, loc }
1168 }
1169}
1170
1171impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
1172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1173 if let Some(ty) = &self.ty {
1174 write!(f, "{ty}::")?
1175 } else {
1176 write!(f, "Action::")?
1177 }
1178 write!(f, "\"{}\"", self.id.escape_debug())
1179 }
1180}
1181
1182impl ActionEntityUID<RawName> {
1183 pub fn conditionally_qualify_type_references(
1185 self,
1186 ns: Option<&InternalName>,
1187 ) -> ActionEntityUID<ConditionalName> {
1188 ActionEntityUID {
1191 id: self.id,
1192 ty: {
1193 #[expect(clippy::expect_used, reason = "this is a valid raw name")]
1194 let raw_name = self
1195 .ty
1196 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1197 Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
1198 },
1199 #[cfg(feature = "extended-schema")]
1200 loc: None,
1201 }
1202 }
1203
1204 pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
1206 ActionEntityUID {
1209 id: self.id,
1210 ty: {
1211 #[expect(clippy::expect_used, reason = "this is a valid raw name")]
1212 let raw_name = self
1213 .ty
1214 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1215 Some(raw_name.qualify_with(ns))
1216 },
1217 #[cfg(feature = "extended-schema")]
1218 loc: self.loc,
1219 }
1220 }
1221}
1222
1223impl ActionEntityUID<ConditionalName> {
1224 pub fn ty(&self) -> &ConditionalName {
1226 #[expect(clippy::expect_used, reason = "by INVARIANT on self.ty")]
1227 self.ty.as_ref().expect("by INVARIANT on self.ty")
1228 }
1229
1230 pub fn fully_qualify_type_references(
1238 self,
1239 all_defs: &AllDefs,
1240 ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
1241 for possibility in self.possibilities() {
1242 if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
1246 if all_defs.is_defined_as_action(&euid) {
1247 return Ok(possibility);
1248 }
1249 }
1250 }
1251 Err(ActionNotDefinedError(nonempty!(self)))
1252 }
1253
1254 pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
1258 self.ty()
1261 .possibilities()
1262 .map(|possibility| ActionEntityUID {
1263 id: self.id.clone(),
1264 ty: Some(possibility.clone()),
1265 #[cfg(feature = "extended-schema")]
1266 loc: None,
1267 })
1268 }
1269
1270 pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
1274 ActionEntityUID {
1275 id: self.id.clone(),
1276 ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
1277 #[cfg(feature = "extended-schema")]
1278 loc: None,
1279 }
1280 }
1281}
1282
1283impl ActionEntityUID<Name> {
1284 pub fn ty(&self) -> &Name {
1286 #[expect(clippy::expect_used, reason = "by INVARIANT on self.ty")]
1287 self.ty.as_ref().expect("by INVARIANT on self.ty")
1288 }
1289}
1290
1291impl ActionEntityUID<InternalName> {
1292 pub fn ty(&self) -> &InternalName {
1294 #[expect(clippy::expect_used, reason = "by INVARIANT on self.ty")]
1295 self.ty.as_ref().expect("by INVARIANT on self.ty")
1296 }
1297}
1298
1299impl From<ActionEntityUID<Name>> for EntityUID {
1300 fn from(aeuid: ActionEntityUID<Name>) -> Self {
1301 EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
1302 }
1303}
1304
1305impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
1306 type Error = <InternalName as TryInto<Name>>::Error;
1307 fn try_from(
1308 aeuid: ActionEntityUID<InternalName>,
1309 ) -> std::result::Result<Self, <InternalName as TryInto<Name>>::Error> {
1310 let ty = Name::try_from(aeuid.ty().clone())?;
1311 #[cfg(feature = "extended-schema")]
1312 let loc = aeuid.loc;
1313 #[cfg(not(feature = "extended-schema"))]
1314 let loc = None;
1315 Ok(EntityUID::from_components(
1316 ty.into(),
1317 Eid::new(aeuid.id),
1318 loc,
1319 ))
1320 }
1321}
1322
1323impl From<EntityUID> for ActionEntityUID<Name> {
1324 fn from(euid: EntityUID) -> Self {
1325 let (ty, id) = euid.components();
1326 ActionEntityUID {
1327 ty: Some(ty.into()),
1328 id: id.into_smolstr(),
1329 #[cfg(feature = "extended-schema")]
1330 loc: None,
1331 }
1332 }
1333}
1334
1335#[derive(Educe, Debug, Clone, Serialize)]
1342#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1343#[serde(untagged)]
1348#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1349#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1350pub enum Type<N> {
1351 Type {
1355 #[serde(flatten)]
1357 ty: TypeVariant<N>,
1358 #[serde(skip)]
1364 #[educe(PartialEq(ignore))]
1365 #[educe(PartialOrd(ignore))]
1366 loc: Option<Loc>,
1367 },
1368 CommonTypeRef {
1374 #[serde(rename = "type")]
1379 type_name: N,
1380 #[serde(skip)]
1386 #[educe(PartialEq(ignore))]
1387 #[educe(PartialOrd(ignore))]
1388 loc: Option<Loc>,
1389 },
1390}
1391
1392impl<N> Type<N> {
1393 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
1396 match self {
1397 Type::Type {
1398 ty: TypeVariant::Record(RecordType { attributes, .. }),
1399 ..
1400 } => attributes
1401 .values()
1402 .map(|ty| ty.ty.common_type_references())
1403 .fold(Box::new(std::iter::empty()), |it, tys| {
1404 Box::new(it.chain(tys))
1405 }),
1406 Type::Type {
1407 ty: TypeVariant::Set { element },
1408 ..
1409 } => element.common_type_references(),
1410 Type::Type {
1411 ty: TypeVariant::EntityOrCommon { type_name },
1412 ..
1413 } => Box::new(std::iter::once(type_name)),
1414 Type::CommonTypeRef { type_name, .. } => Box::new(std::iter::once(type_name)),
1415 _ => Box::new(std::iter::empty()),
1416 }
1417 }
1418
1419 pub fn is_extension(&self) -> Option<bool> {
1425 match self {
1426 Self::Type {
1427 ty: TypeVariant::Extension { .. },
1428 ..
1429 } => Some(true),
1430 Self::Type {
1431 ty: TypeVariant::Set { element },
1432 ..
1433 } => element.is_extension(),
1434 Self::Type {
1435 ty: TypeVariant::Record(RecordType { attributes, .. }),
1436 ..
1437 } => attributes
1438 .values()
1439 .try_fold(false, |a, e| match e.ty.is_extension() {
1440 Some(true) => Some(true),
1441 Some(false) => Some(a),
1442 None => None,
1443 }),
1444 Self::Type { .. } => Some(false),
1445 Self::CommonTypeRef { .. } => None,
1446 }
1447 }
1448
1449 pub fn is_empty_record(&self) -> bool {
1452 match self {
1453 Self::Type {
1454 ty: TypeVariant::Record(rty),
1455 ..
1456 } => rty.is_empty_record(),
1457 _ => false,
1458 }
1459 }
1460
1461 pub fn loc(&self) -> Option<&Loc> {
1463 match self {
1464 Self::Type { loc, .. } => loc.as_ref(),
1465 Self::CommonTypeRef { loc, .. } => loc.as_ref(),
1466 }
1467 }
1468
1469 pub fn with_loc(self, new_loc: Option<Loc>) -> Self {
1471 match self {
1472 Self::Type { ty, loc: _loc } => Self::Type { ty, loc: new_loc },
1473 Self::CommonTypeRef {
1474 type_name,
1475 loc: _loc,
1476 } => Self::CommonTypeRef {
1477 type_name,
1478 loc: new_loc,
1479 },
1480 }
1481 }
1482}
1483
1484impl Type<RawName> {
1485 pub fn conditionally_qualify_type_references(
1487 self,
1488 ns: Option<&InternalName>,
1489 ) -> Type<ConditionalName> {
1490 match self {
1491 Self::Type { ty, loc } => Type::Type {
1492 ty: ty.conditionally_qualify_type_references(ns),
1493 loc,
1494 },
1495 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1496 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
1497 loc,
1498 },
1499 }
1500 }
1501
1502 fn into_n<N: From<RawName>>(self) -> Type<N> {
1503 match self {
1504 Self::Type { ty, loc } => Type::Type {
1505 ty: ty.into_n(),
1506 loc,
1507 },
1508 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1509 type_name: type_name.into(),
1510 loc,
1511 },
1512 }
1513 }
1514}
1515
1516impl Type<ConditionalName> {
1517 pub fn fully_qualify_type_references(
1523 self,
1524 all_defs: &AllDefs,
1525 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1526 match self {
1527 Self::Type { ty, loc } => Ok(Type::Type {
1528 ty: ty.fully_qualify_type_references(all_defs)?,
1529 loc,
1530 }),
1531 Self::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef {
1532 type_name: type_name.resolve(all_defs)?,
1533 loc,
1534 }),
1535 }
1536 }
1537}
1538
1539impl Type<InternalName> {
1540 pub fn resolve_entity_or_common_type(
1542 self,
1543 all_defs: &AllDefs,
1544 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1545 match self {
1546 Type::Type { ty, loc } => match ty.resolve_type_variant_entity_or_common(all_defs)? {
1547 ResolvedTypeVariant::TypeVariant(resolved_ty) => Ok(Type::Type {
1548 ty: resolved_ty,
1549 loc,
1550 }),
1551 ResolvedTypeVariant::CommonTypeRef(type_name) => {
1552 Ok(Type::CommonTypeRef { type_name, loc })
1553 }
1554 },
1555 Type::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef { type_name, loc }),
1556 }
1557 }
1558}
1559
1560impl TypeVariant<InternalName> {
1561 fn resolve_type_variant_entity_or_common(
1563 self,
1564 all_defs: &AllDefs,
1565 ) -> std::result::Result<ResolvedTypeVariant, TypeNotDefinedError> {
1566 match self {
1567 TypeVariant::EntityOrCommon { type_name } => {
1568 if all_defs.is_defined_as_common(&type_name) {
1570 Ok(ResolvedTypeVariant::CommonTypeRef(type_name))
1571 } else if all_defs.is_defined_as_entity(&type_name) {
1572 Ok(ResolvedTypeVariant::TypeVariant(TypeVariant::Entity {
1573 name: type_name,
1574 }))
1575 } else {
1576 Err(TypeNotDefinedError {
1578 undefined_types: NonEmpty {
1579 head: ConditionalName::unconditional(
1580 type_name,
1581 ReferenceType::CommonOrEntity,
1582 ),
1583 tail: vec![],
1584 },
1585 })
1586 }
1587 }
1588 TypeVariant::Set { element } => {
1589 Ok(ResolvedTypeVariant::TypeVariant(TypeVariant::Set {
1590 element: Box::new(element.resolve_entity_or_common_type(all_defs)?),
1591 }))
1592 }
1593 TypeVariant::Record(record_type) => Ok(ResolvedTypeVariant::TypeVariant(
1594 TypeVariant::Record(record_type.resolve_record_type_entity_or_common(all_defs)?),
1595 )),
1596 other => Ok(ResolvedTypeVariant::TypeVariant(other)),
1598 }
1599 }
1600}
1601
1602impl RecordType<InternalName> {
1603 fn resolve_record_type_entity_or_common(
1605 self,
1606 all_defs: &AllDefs,
1607 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1608 Ok(RecordType {
1609 attributes: self
1610 .attributes
1611 .into_iter()
1612 .map(|(k, v)| Ok((k, v.resolve_type_of_attribute_entity_or_common(all_defs)?)))
1613 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1614 additional_attributes: self.additional_attributes,
1615 })
1616 }
1617}
1618
1619impl TypeOfAttribute<InternalName> {
1620 fn resolve_type_of_attribute_entity_or_common(
1622 self,
1623 all_defs: &AllDefs,
1624 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
1625 Ok(TypeOfAttribute {
1626 ty: self.ty.resolve_entity_or_common_type(all_defs)?,
1627 required: self.required,
1628 annotations: self.annotations,
1629 #[cfg(feature = "extended-schema")]
1630 loc: self.loc,
1631 })
1632 }
1633}
1634
1635impl EntityType<InternalName> {
1636 fn resolve_entity_type_entity_or_common(
1638 self,
1639 all_defs: &AllDefs,
1640 ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
1641 Ok(EntityType {
1642 kind: match self.kind {
1643 EntityTypeKind::Standard(standard) => {
1644 EntityTypeKind::Standard(StandardEntityType {
1645 member_of_types: standard.member_of_types, shape: standard
1647 .shape
1648 .resolve_attributes_or_context_entity_or_common(all_defs)?,
1649 tags: standard
1650 .tags
1651 .map(|tags| tags.resolve_entity_or_common_type(all_defs))
1652 .transpose()?,
1653 })
1654 }
1655 EntityTypeKind::Enum { choices } => EntityTypeKind::Enum { choices },
1656 },
1657 annotations: self.annotations,
1658 loc: self.loc,
1659 })
1660 }
1661}
1662
1663impl ActionType<InternalName> {
1664 fn resolve_action_type_entity_or_common(
1666 self,
1667 all_defs: &AllDefs,
1668 ) -> std::result::Result<ActionType<InternalName>, TypeNotDefinedError> {
1669 let new_apply_spec = self
1670 .applies_to
1671 .clone()
1672 .map(|apply_spec| apply_spec.resolve_apply_spec_entity_or_common(all_defs))
1673 .transpose()?;
1674 Ok(ActionType::<InternalName> {
1675 applies_to: new_apply_spec,
1676 ..self
1677 })
1678 }
1679}
1680
1681impl ApplySpec<InternalName> {
1682 fn resolve_apply_spec_entity_or_common(
1684 self,
1685 all_defs: &AllDefs,
1686 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
1687 Ok(ApplySpec {
1688 resource_types: self.resource_types, principal_types: self.principal_types, context: self
1691 .context
1692 .resolve_attributes_or_context_entity_or_common(all_defs)?,
1693 })
1694 }
1695}
1696
1697impl AttributesOrContext<InternalName> {
1698 fn resolve_attributes_or_context_entity_or_common(
1700 self,
1701 all_defs: &AllDefs,
1702 ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
1703 Ok(AttributesOrContext(
1704 self.0.resolve_entity_or_common_type(all_defs)?,
1705 ))
1706 }
1707}
1708
1709impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1710 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1711 where
1712 D: serde::Deserializer<'de>,
1713 {
1714 deserializer.deserialize_any(TypeVisitor {
1715 _phantom: PhantomData,
1716 })
1717 }
1718}
1719
1720#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1722#[serde(field_identifier, rename_all = "camelCase")]
1723enum TypeFields {
1724 Type,
1725 Element,
1726 Attributes,
1727 AdditionalAttributes,
1728 Name,
1729}
1730
1731macro_rules! type_field_name {
1735 (Type) => {
1736 "type"
1737 };
1738 (Element) => {
1739 "element"
1740 };
1741 (Attributes) => {
1742 "attributes"
1743 };
1744 (AdditionalAttributes) => {
1745 "additionalAttributes"
1746 };
1747 (Name) => {
1748 "name"
1749 };
1750}
1751
1752impl TypeFields {
1753 fn as_str(&self) -> &'static str {
1754 match self {
1755 TypeFields::Type => type_field_name!(Type),
1756 TypeFields::Element => type_field_name!(Element),
1757 TypeFields::Attributes => type_field_name!(Attributes),
1758 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1759 TypeFields::Name => type_field_name!(Name),
1760 }
1761 }
1762}
1763
1764#[derive(Debug, Deserialize)]
1769struct AttributesTypeMap(
1770 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1771 BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1772);
1773
1774struct TypeVisitor<N> {
1775 _phantom: PhantomData<N>,
1776}
1777
1778impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1779 type Value = Type<N>;
1780
1781 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1782 formatter.write_str("builtin type or reference to type defined in commonTypes")
1783 }
1784
1785 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1786 where
1787 M: MapAccess<'de>,
1788 {
1789 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1790
1791 let mut type_name: Option<SmolStr> = None;
1792 let mut element: Option<Type<N>> = None;
1793 let mut attributes: Option<AttributesTypeMap> = None;
1794 let mut additional_attributes: Option<bool> = None;
1795 let mut name: Option<SmolStr> = None;
1796
1797 while let Some(key) = map.next_key()? {
1801 match key {
1802 TypeField => {
1803 if type_name.is_some() {
1804 return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1805 }
1806 type_name = Some(map.next_value()?);
1807 }
1808 Element => {
1809 if element.is_some() {
1810 return Err(serde::de::Error::duplicate_field(Element.as_str()));
1811 }
1812 element = Some(map.next_value()?);
1813 }
1814 Attributes => {
1815 if attributes.is_some() {
1816 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1817 }
1818 attributes = Some(map.next_value()?);
1819 }
1820 AdditionalAttributes => {
1821 if additional_attributes.is_some() {
1822 return Err(serde::de::Error::duplicate_field(
1823 AdditionalAttributes.as_str(),
1824 ));
1825 }
1826 additional_attributes = Some(map.next_value()?);
1827 }
1828 Name => {
1829 if name.is_some() {
1830 return Err(serde::de::Error::duplicate_field(Name.as_str()));
1831 }
1832 name = Some(map.next_value()?);
1833 }
1834 }
1835 }
1836
1837 Self::build_schema_type::<M>(
1838 type_name.as_ref(),
1839 element,
1840 attributes,
1841 additional_attributes,
1842 name,
1843 )
1844 }
1845}
1846
1847impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1848 fn build_schema_type<M>(
1853 type_name: Option<&SmolStr>,
1854 element: Option<Type<N>>,
1855 attributes: Option<AttributesTypeMap>,
1856 additional_attributes: Option<bool>,
1857 name: Option<SmolStr>,
1858 ) -> std::result::Result<Type<N>, M::Error>
1859 where
1860 M: MapAccess<'de>,
1861 {
1862 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1863 let mut remaining_fields = [
1865 (TypeField, type_name.is_some()),
1866 (Element, element.is_some()),
1867 (Attributes, attributes.is_some()),
1868 (AdditionalAttributes, additional_attributes.is_some()),
1869 (Name, name.is_some()),
1870 ]
1871 .into_iter()
1872 .filter(|(_, present)| *present)
1873 .map(|(field, _)| field)
1874 .collect::<HashSet<_>>();
1875
1876 match type_name {
1877 Some(s) => {
1878 remaining_fields.remove(&TypeField);
1880 let error_if_fields = |fs: &[TypeFields],
1883 expected: &'static [&'static str]|
1884 -> std::result::Result<(), M::Error> {
1885 for f in fs {
1886 if remaining_fields.contains(f) {
1887 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1888 }
1889 }
1890 Ok(())
1891 };
1892 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1893 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1894 };
1895 match s.as_str() {
1896 "String" => {
1897 error_if_any_fields()?;
1898 Ok(Type::Type {
1899 ty: TypeVariant::String,
1900 loc: None,
1901 })
1902 }
1903 "Long" => {
1904 error_if_any_fields()?;
1905 Ok(Type::Type {
1906 ty: TypeVariant::Long,
1907 loc: None,
1908 })
1909 }
1910 "Boolean" => {
1911 error_if_any_fields()?;
1912 Ok(Type::Type {
1913 ty: TypeVariant::Boolean,
1914 loc: None,
1915 })
1916 }
1917 "Set" => {
1918 error_if_fields(
1919 &[Attributes, AdditionalAttributes, Name],
1920 &[type_field_name!(Element)],
1921 )?;
1922
1923 match element {
1924 Some(element) => Ok(Type::Type {
1925 ty: TypeVariant::Set {
1926 element: Box::new(element),
1927 },
1928 loc: None,
1929 }),
1930 None => Err(serde::de::Error::missing_field(Element.as_str())),
1931 }
1932 }
1933 "Record" => {
1934 error_if_fields(
1935 &[Element, Name],
1936 &[
1937 type_field_name!(Attributes),
1938 type_field_name!(AdditionalAttributes),
1939 ],
1940 )?;
1941
1942 if let Some(attributes) = attributes {
1943 let additional_attributes =
1944 additional_attributes.unwrap_or_else(partial_schema_default);
1945 Ok(Type::Type {
1946 ty: TypeVariant::Record(RecordType {
1947 attributes: attributes
1948 .0
1949 .into_iter()
1950 .map(
1951 |(
1952 k,
1953 TypeOfAttribute {
1954 ty,
1955 required,
1956 annotations,
1957 #[cfg(feature = "extended-schema")]
1958 loc,
1959 },
1960 )| {
1961 (
1962 k,
1963 TypeOfAttribute {
1964 ty: ty.into_n(),
1965 required,
1966 annotations,
1967 #[cfg(feature = "extended-schema")]
1968 loc,
1969 },
1970 )
1971 },
1972 )
1973 .collect(),
1974 additional_attributes,
1975 }),
1976 loc: None,
1977 })
1978 } else {
1979 Err(serde::de::Error::missing_field(Attributes.as_str()))
1980 }
1981 }
1982 "Entity" => {
1983 error_if_fields(
1984 &[Element, Attributes, AdditionalAttributes],
1985 &[type_field_name!(Name)],
1986 )?;
1987 match name {
1988 Some(name) => Ok(Type::Type {
1989 ty: TypeVariant::Entity {
1990 name: RawName::from_normalized_str(&name)
1991 .map_err(|err| {
1992 serde::de::Error::custom(format!(
1993 "invalid entity type `{name}`: {err}"
1994 ))
1995 })?
1996 .into(),
1997 },
1998 loc: None,
1999 }),
2000 None => Err(serde::de::Error::missing_field(Name.as_str())),
2001 }
2002 }
2003 "EntityOrCommon" => {
2004 error_if_fields(
2005 &[Element, Attributes, AdditionalAttributes],
2006 &[type_field_name!(Name)],
2007 )?;
2008 match name {
2009 Some(name) => Ok(Type::Type {
2010 ty: TypeVariant::EntityOrCommon {
2011 type_name: RawName::from_normalized_str(&name)
2012 .map_err(|err| {
2013 serde::de::Error::custom(format!(
2014 "invalid entity or common type `{name}`: {err}"
2015 ))
2016 })?
2017 .into(),
2018 },
2019 loc: None,
2020 }),
2021 None => Err(serde::de::Error::missing_field(Name.as_str())),
2022 }
2023 }
2024 "Extension" => {
2025 error_if_fields(
2026 &[Element, Attributes, AdditionalAttributes],
2027 &[type_field_name!(Name)],
2028 )?;
2029
2030 match name {
2031 Some(name) => Ok(Type::Type {
2032 ty: TypeVariant::Extension {
2033 name: UnreservedId::from_normalized_str(&name).map_err(
2034 |err| {
2035 serde::de::Error::custom(format!(
2036 "invalid extension type `{name}`: {err}"
2037 ))
2038 },
2039 )?,
2040 },
2041 loc: None,
2042 }),
2043 None => Err(serde::de::Error::missing_field(Name.as_str())),
2044 }
2045 }
2046 type_name => {
2047 error_if_any_fields()?;
2048 Ok(Type::CommonTypeRef {
2049 type_name: N::from(RawName::from_normalized_str(type_name).map_err(
2050 |err| {
2051 serde::de::Error::custom(format!(
2052 "invalid common type `{type_name}`: {err}"
2053 ))
2054 },
2055 )?),
2056 loc: None,
2057 })
2058 }
2059 }
2060 }
2061 None => Err(serde::de::Error::missing_field(TypeField.as_str())),
2062 }
2063 }
2064}
2065
2066impl<N> From<TypeVariant<N>> for Type<N> {
2067 fn from(ty: TypeVariant<N>) -> Self {
2068 Self::Type { ty, loc: None }
2069 }
2070}
2071
2072#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2078#[educe(PartialEq, Eq, PartialOrd, Ord)]
2079#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2080#[serde(rename_all = "camelCase")]
2081#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
2082#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
2083pub struct RecordType<N> {
2084 pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
2086 #[serde(default = "partial_schema_default")]
2088 #[serde(skip_serializing_if = "is_partial_schema_default")]
2089 pub additional_attributes: bool,
2090}
2091
2092impl<N> Default for RecordType<N> {
2093 fn default() -> Self {
2094 Self {
2095 attributes: BTreeMap::new(),
2096 additional_attributes: partial_schema_default(),
2097 }
2098 }
2099}
2100
2101impl<N> RecordType<N> {
2102 pub fn is_empty_record(&self) -> bool {
2104 self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
2105 }
2106}
2107
2108impl RecordType<RawName> {
2109 pub fn conditionally_qualify_type_references(
2111 self,
2112 ns: Option<&InternalName>,
2113 ) -> RecordType<ConditionalName> {
2114 RecordType {
2115 attributes: self
2116 .attributes
2117 .into_iter()
2118 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
2119 .collect(),
2120 additional_attributes: self.additional_attributes,
2121 }
2122 }
2123}
2124
2125impl RecordType<ConditionalName> {
2126 pub fn fully_qualify_type_references(
2133 self,
2134 all_defs: &AllDefs,
2135 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
2136 Ok(RecordType {
2137 attributes: self
2138 .attributes
2139 .into_iter()
2140 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
2141 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
2142 additional_attributes: self.additional_attributes,
2143 })
2144 }
2145}
2146
2147#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2155#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
2156#[serde(tag = "type")]
2157#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2158#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
2159#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
2160pub enum TypeVariant<N> {
2161 String,
2163 Long,
2165 Boolean,
2167 Set {
2169 element: Box<Type<N>>,
2171 },
2172 Record(RecordType<N>),
2174 Entity {
2176 name: N,
2181 },
2182 EntityOrCommon {
2184 #[serde(rename = "name")]
2205 type_name: N,
2206 },
2207 Extension {
2209 name: UnreservedId,
2211 },
2212}
2213
2214impl TypeVariant<RawName> {
2215 pub fn conditionally_qualify_type_references(
2217 self,
2218 ns: Option<&InternalName>,
2219 ) -> TypeVariant<ConditionalName> {
2220 match self {
2221 Self::Boolean => TypeVariant::Boolean,
2222 Self::Long => TypeVariant::Long,
2223 Self::String => TypeVariant::String,
2224 Self::Extension { name } => TypeVariant::Extension { name },
2225 Self::Entity { name } => TypeVariant::Entity {
2226 name: name.conditionally_qualify_with(ns, ReferenceType::Entity), },
2228 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
2229 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
2230 },
2231 Self::Set { element } => TypeVariant::Set {
2232 element: Box::new(element.conditionally_qualify_type_references(ns)),
2233 },
2234 Self::Record(RecordType {
2235 attributes,
2236 additional_attributes,
2237 }) => TypeVariant::Record(RecordType {
2238 attributes: BTreeMap::from_iter(attributes.into_iter().map(
2239 |(
2240 attr,
2241 TypeOfAttribute {
2242 ty,
2243 required,
2244 annotations,
2245 #[cfg(feature = "extended-schema")]
2246 loc,
2247 },
2248 )| {
2249 (
2250 attr,
2251 TypeOfAttribute {
2252 ty: ty.conditionally_qualify_type_references(ns),
2253 required,
2254 annotations,
2255 #[cfg(feature = "extended-schema")]
2256 loc,
2257 },
2258 )
2259 },
2260 )),
2261 additional_attributes,
2262 }),
2263 }
2264 }
2265
2266 fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
2267 match self {
2268 Self::Boolean => TypeVariant::Boolean,
2269 Self::Long => TypeVariant::Long,
2270 Self::String => TypeVariant::String,
2271 Self::Entity { name } => TypeVariant::Entity { name: name.into() },
2272 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
2273 type_name: type_name.into(),
2274 },
2275 Self::Record(RecordType {
2276 attributes,
2277 additional_attributes,
2278 }) => TypeVariant::Record(RecordType {
2279 attributes: attributes
2280 .into_iter()
2281 .map(|(k, v)| (k, v.into_n()))
2282 .collect(),
2283 additional_attributes,
2284 }),
2285 Self::Set { element } => TypeVariant::Set {
2286 element: Box::new(element.into_n()),
2287 },
2288 Self::Extension { name } => TypeVariant::Extension { name },
2289 }
2290 }
2291}
2292
2293impl TypeVariant<ConditionalName> {
2294 pub fn fully_qualify_type_references(
2301 self,
2302 all_defs: &AllDefs,
2303 ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
2304 match self {
2305 Self::Boolean => Ok(TypeVariant::Boolean),
2306 Self::Long => Ok(TypeVariant::Long),
2307 Self::String => Ok(TypeVariant::String),
2308 Self::Extension { name } => Ok(TypeVariant::Extension { name }),
2309 Self::Entity { name } => Ok(TypeVariant::Entity {
2310 name: name.resolve(all_defs)?,
2311 }),
2312 Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
2313 type_name: type_name.resolve(all_defs)?,
2314 }),
2315 Self::Set { element } => Ok(TypeVariant::Set {
2316 element: Box::new(element.fully_qualify_type_references(all_defs)?),
2317 }),
2318 Self::Record(RecordType {
2319 attributes,
2320 additional_attributes,
2321 }) => Ok(TypeVariant::Record(RecordType {
2322 attributes: attributes
2323 .into_iter()
2324 .map(
2325 |(
2326 attr,
2327 TypeOfAttribute {
2328 ty,
2329 required,
2330 annotations,
2331 #[cfg(feature = "extended-schema")]
2332 loc,
2333 },
2334 )| {
2335 Ok((
2336 attr,
2337 TypeOfAttribute {
2338 ty: ty.fully_qualify_type_references(all_defs)?,
2339 required,
2340 annotations,
2341 #[cfg(feature = "extended-schema")]
2342 loc,
2343 },
2344 ))
2345 },
2346 )
2347 .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
2348 additional_attributes,
2349 })),
2350 }
2351 }
2352}
2353
2354#[expect(
2356 clippy::trivially_copy_pass_by_ref,
2357 reason = "Reference required to work with derived serde serialize implementation"
2358)]
2359fn is_partial_schema_default(b: &bool) -> bool {
2360 *b == partial_schema_default()
2361}
2362
2363#[cfg(feature = "arbitrary")]
2364#[expect(clippy::panic, reason = "property testing code")]
2365impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
2366 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
2367 use std::collections::BTreeSet;
2368
2369 Ok(Type::Type {
2370 ty: match u.int_in_range::<u8>(1..=8)? {
2371 1 => TypeVariant::String,
2372 2 => TypeVariant::Long,
2373 3 => TypeVariant::Boolean,
2374 4 => TypeVariant::Set {
2375 element: Box::new(u.arbitrary()?),
2376 },
2377 5 => {
2378 let attributes = {
2379 let attr_names: BTreeSet<String> = u.arbitrary()?;
2380 attr_names
2381 .into_iter()
2382 .map(|attr_name| {
2383 Ok((attr_name.into(), u.arbitrary::<TypeOfAttribute<RawName>>()?))
2384 })
2385 .collect::<arbitrary::Result<_>>()?
2386 };
2387 TypeVariant::Record(RecordType {
2388 attributes,
2389 additional_attributes: u.arbitrary()?,
2390 })
2391 }
2392 6 => TypeVariant::Entity {
2393 name: u.arbitrary()?,
2394 },
2395 7 => TypeVariant::Extension {
2396 #[expect(clippy::unwrap_used, reason = "`ipaddr` is a valid `UnreservedId`")]
2397 name: "ipaddr".parse().unwrap(),
2398 },
2399 8 => TypeVariant::Extension {
2400 #[expect(clippy::unwrap_used, reason = "`decimal` is a valid `UnreservedId`")]
2401 name: "decimal".parse().unwrap(),
2402 },
2403 n => panic!("bad index: {n}"),
2404 },
2405 loc: None,
2406 })
2407 }
2408 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
2409 (1, None) }
2411}
2412
2413#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2432#[educe(PartialEq, Eq, PartialOrd, Ord)]
2433#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2434pub struct TypeOfAttribute<N> {
2435 #[serde(flatten)]
2437 pub ty: Type<N>,
2438 #[serde(default)]
2440 #[serde(skip_serializing_if = "Annotations::is_empty")]
2441 pub annotations: Annotations,
2442 #[serde(default = "record_attribute_required_default")]
2444 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
2445 pub required: bool,
2446
2447 #[cfg(feature = "extended-schema")]
2449 #[educe(Eq(ignore))]
2450 #[serde(skip)]
2451 pub loc: Option<Loc>,
2452}
2453
2454impl TypeOfAttribute<RawName> {
2455 fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
2456 TypeOfAttribute {
2457 ty: self.ty.into_n(),
2458
2459 required: self.required,
2460 annotations: self.annotations,
2461 #[cfg(feature = "extended-schema")]
2462 loc: self.loc,
2463 }
2464 }
2465
2466 pub fn conditionally_qualify_type_references(
2468 self,
2469 ns: Option<&InternalName>,
2470 ) -> TypeOfAttribute<ConditionalName> {
2471 TypeOfAttribute {
2472 ty: self.ty.conditionally_qualify_type_references(ns),
2473 required: self.required,
2474 annotations: self.annotations,
2475 #[cfg(feature = "extended-schema")]
2476 loc: self.loc,
2477 }
2478 }
2479}
2480
2481impl TypeOfAttribute<ConditionalName> {
2482 pub fn fully_qualify_type_references(
2489 self,
2490 all_defs: &AllDefs,
2491 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
2492 Ok(TypeOfAttribute {
2493 ty: self.ty.fully_qualify_type_references(all_defs)?,
2494 required: self.required,
2495 annotations: self.annotations,
2496 #[cfg(feature = "extended-schema")]
2497 loc: self.loc,
2498 })
2499 }
2500}
2501
2502#[cfg(feature = "arbitrary")]
2503impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
2504 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2505 Ok(Self {
2506 ty: u.arbitrary::<Type<RawName>>()?,
2507 required: u.arbitrary()?,
2508 annotations: u.arbitrary()?,
2509 #[cfg(feature = "extended-schema")]
2510 loc: None,
2511 })
2512 }
2513
2514 fn size_hint(depth: usize) -> (usize, Option<usize>) {
2515 arbitrary::size_hint::and_all(&[
2516 <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
2517 <bool as arbitrary::Arbitrary>::size_hint(depth),
2518 <crate::est::Annotations as arbitrary::Arbitrary>::size_hint(depth),
2519 ])
2520 }
2521}
2522
2523#[expect(
2525 clippy::trivially_copy_pass_by_ref,
2526 reason = "Reference required to work with derived serde serialize implementation"
2527)]
2528fn is_record_attribute_required_default(b: &bool) -> bool {
2529 *b == record_attribute_required_default()
2530}
2531
2532fn partial_schema_default() -> bool {
2535 false
2536}
2537
2538fn record_attribute_required_default() -> bool {
2540 true
2541}
2542
2543#[cfg(test)]
2544mod test {
2545 use crate::{
2546 extensions::Extensions,
2547 test_utils::{expect_err, ExpectedErrorMessageBuilder},
2548 };
2549 use cool_asserts::assert_matches;
2550
2551 use crate::validator::ValidatorSchema;
2552
2553 use super::*;
2554
2555 #[test]
2556 fn test_entity_type_parser1() {
2557 let user = r#"
2558 {
2559 "memberOfTypes" : ["UserGroup"]
2560 }
2561 "#;
2562 assert_matches!(serde_json::from_str::<EntityType<RawName>>(user), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2563 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
2564 assert_eq!(
2565 et.shape,
2566 AttributesOrContext(Type::Type {
2567 ty: TypeVariant::Record(RecordType {
2568 attributes: BTreeMap::new(),
2569 additional_attributes: false
2570 }),
2571 loc: None
2572 }),
2573 );});
2574 }
2575
2576 #[test]
2577 fn test_entity_type_parser2() {
2578 let src = r#"
2579 { }
2580 "#;
2581 assert_matches!(serde_json::from_str::<EntityType<RawName>>(src), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2582 assert_eq!(et.member_of_types.len(), 0);
2583 assert_eq!(
2584 et.shape,
2585 AttributesOrContext(Type::Type {
2586 ty: TypeVariant::Record(RecordType {
2587 attributes: BTreeMap::new(),
2588 additional_attributes: false
2589 }),
2590 loc: None
2591 }),
2592 );});
2593 }
2594
2595 #[test]
2596 fn test_action_type_parser1() {
2597 let src = r#"
2598 {
2599 "appliesTo" : {
2600 "resourceTypes": ["Album"],
2601 "principalTypes": ["User"]
2602 },
2603 "memberOf": [{"id": "readWrite"}]
2604 }
2605 "#;
2606 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2607 let spec = ApplySpec {
2608 resource_types: vec!["Album".parse().unwrap()],
2609 principal_types: vec!["User".parse().unwrap()],
2610 context: AttributesOrContext::default(),
2611 };
2612 assert_eq!(at.applies_to, Some(spec));
2613 assert_eq!(
2614 at.member_of,
2615 Some(vec![ActionEntityUID {
2616 ty: None,
2617 id: "readWrite".into(),
2618 #[cfg(feature = "extended-schema")]
2619 loc: None
2620 }])
2621 );
2622 }
2623
2624 #[test]
2625 fn test_action_type_parser2() {
2626 let src = r#"
2627 { }
2628 "#;
2629 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2630 assert_eq!(at.applies_to, None);
2631 assert!(at.member_of.is_none());
2632 }
2633
2634 #[test]
2635 fn test_schema_file_parser() {
2636 let src = serde_json::json!(
2637 {
2638 "entityTypes": {
2639
2640 "User": {
2641 "memberOfTypes": ["UserGroup"]
2642 },
2643 "Photo": {
2644 "memberOfTypes": ["Album", "Account"]
2645 },
2646
2647 "Album": {
2648 "memberOfTypes": ["Album", "Account"]
2649 },
2650 "Account": { },
2651 "UserGroup": { }
2652 },
2653
2654 "actions": {
2655 "readOnly": { },
2656 "readWrite": { },
2657 "createAlbum": {
2658 "appliesTo" : {
2659 "resourceTypes": ["Account", "Album"],
2660 "principalTypes": ["User"]
2661 },
2662 "memberOf": [{"id": "readWrite"}]
2663 },
2664 "addPhotoToAlbum": {
2665 "appliesTo" : {
2666 "resourceTypes": ["Album"],
2667 "principalTypes": ["User"]
2668 },
2669 "memberOf": [{"id": "readWrite"}]
2670 },
2671 "viewPhoto": {
2672 "appliesTo" : {
2673 "resourceTypes": ["Photo"],
2674 "principalTypes": ["User"]
2675 },
2676 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2677 },
2678 "viewComments": {
2679 "appliesTo" : {
2680 "resourceTypes": ["Photo"],
2681 "principalTypes": ["User"]
2682 },
2683 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2684 }
2685 }
2686 });
2687 let schema_file: NamespaceDefinition<RawName> =
2688 serde_json::from_value(src).expect("Parse Error");
2689
2690 assert_eq!(schema_file.entity_types.len(), 5);
2691 assert_eq!(schema_file.actions.len(), 6);
2692 }
2693
2694 #[test]
2695 fn test_parse_namespaces() {
2696 let src = r#"
2697 {
2698 "foo::foo::bar::baz": {
2699 "entityTypes": {},
2700 "actions": {}
2701 }
2702 }"#;
2703 let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
2704 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
2705 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
2706 }
2707
2708 #[test]
2709 #[should_panic(expected = "unknown field `requiredddddd`")]
2710 fn test_schema_file_with_misspelled_required() {
2711 let src = serde_json::json!(
2712 {
2713 "entityTypes": {
2714 "User": {
2715 "shape": {
2716 "type": "Record",
2717 "attributes": {
2718 "favorite": {
2719 "type": "Entity",
2720 "name": "Photo",
2721 "requiredddddd": false
2722 }
2723 }
2724 }
2725 }
2726 },
2727 "actions": {}
2728 });
2729 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2730 println!("{schema:#?}");
2731 }
2732
2733 #[test]
2734 #[should_panic(expected = "unknown field `nameeeeee`")]
2735 fn test_schema_file_with_misspelled_field() {
2736 let src = serde_json::json!(
2737 {
2738 "entityTypes": {
2739 "User": {
2740 "shape": {
2741 "type": "Record",
2742 "attributes": {
2743 "favorite": {
2744 "type": "Entity",
2745 "nameeeeee": "Photo",
2746 }
2747 }
2748 }
2749 }
2750 },
2751 "actions": {}
2752 });
2753 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2754 println!("{schema:#?}");
2755 }
2756
2757 #[test]
2758 #[should_panic(expected = "unknown field `extra`")]
2759 fn test_schema_file_with_extra_field() {
2760 let src = serde_json::json!(
2761 {
2762 "entityTypes": {
2763 "User": {
2764 "shape": {
2765 "type": "Record",
2766 "attributes": {
2767 "favorite": {
2768 "type": "Entity",
2769 "name": "Photo",
2770 "extra": "Should not exist"
2771 }
2772 }
2773 }
2774 }
2775 },
2776 "actions": {}
2777 });
2778 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2779 println!("{schema:#?}");
2780 }
2781
2782 #[test]
2783 #[should_panic(expected = "unknown field `memberOfTypes`")]
2784 fn test_schema_file_with_misplaced_field() {
2785 let src = serde_json::json!(
2786 {
2787 "entityTypes": {
2788 "User": {
2789 "shape": {
2790 "memberOfTypes": [],
2791 "type": "Record",
2792 "attributes": {
2793 "favorite": {
2794 "type": "Entity",
2795 "name": "Photo",
2796 }
2797 }
2798 }
2799 }
2800 },
2801 "actions": {}
2802 });
2803 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2804 println!("{schema:#?}");
2805 }
2806
2807 #[test]
2808 fn schema_file_with_missing_field() {
2809 let src = serde_json::json!(
2810 {
2811 "": {
2812 "entityTypes": {
2813 "User": {
2814 "shape": {
2815 "type": "Record",
2816 "attributes": {
2817 "favorite": {
2818 "type": "Entity",
2819 }
2820 }
2821 }
2822 }
2823 },
2824 "actions": {}
2825 }
2826 });
2827 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2828 assert_matches!(schema, Err(e) => {
2829 expect_err(
2830 &src,
2831 &miette::Report::new(e),
2832 &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2833 .build());
2834 });
2835 }
2836
2837 #[test]
2838 #[should_panic(expected = "missing field `type`")]
2839 fn schema_file_with_missing_type() {
2840 let src = serde_json::json!(
2841 {
2842 "entityTypes": {
2843 "User": {
2844 "shape": { }
2845 }
2846 },
2847 "actions": {}
2848 });
2849 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2850 println!("{schema:#?}");
2851 }
2852
2853 #[test]
2854 fn schema_file_unexpected_malformed_attribute() {
2855 let src = serde_json::json!(
2856 { "": {
2857 "entityTypes": {
2858 "User": {
2859 "shape": {
2860 "type": "Record",
2861 "attributes": {
2862 "a": {
2863 "type": "Long",
2864 "attributes": {
2865 "b": {"foo": "bar"}
2866 }
2867 }
2868 }
2869 }
2870 }
2871 },
2872 "actions": {}
2873 }});
2874 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2875 assert_matches!(schema, Err(e) => {
2876 expect_err(
2877 "",
2878 &miette::Report::new(e),
2879 &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2880 );
2881 });
2882 }
2883
2884 #[test]
2885 fn error_in_nested_attribute_fails_fast_top_level_attr() {
2886 let src = serde_json::json!(
2887 {
2888 "": {
2889 "entityTypes": {
2890 "User": {
2891 "shape": {
2892 "type": "Record",
2893 "attributes": {
2894 "foo": {
2895 "type": "Record",
2896 "element": { "type": "Long" }
2898 },
2899 "bar": { "type": "Long" }
2900 }
2901 }
2902 }
2903 },
2904 "actions": {}
2905 }
2906 }
2907 );
2908
2909 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2910 assert_matches!(schema, Err(e) => {
2911 expect_err(
2912 "",
2913 &miette::Report::new(e),
2914 &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2915 );
2916 });
2917 }
2918
2919 #[test]
2920 fn error_in_nested_attribute_fails_fast_nested_attr() {
2921 let src = serde_json::json!(
2922 { "": {
2923 "entityTypes": {
2924 "a": {
2925 "shape": {
2926 "type": "Record",
2927 "attributes": {
2928 "foo": { "type": "Entity", "name": "b" },
2929 "baz": { "type": "Record",
2930 "attributes": {
2931 "z": "Boolean"
2933 }
2934 }
2935 }
2936 }
2937 },
2938 "b": {}
2939 }
2940 } }
2941 );
2942
2943 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2944 assert_matches!(schema, Err(e) => {
2945 expect_err(
2946 "",
2947 &miette::Report::new(e),
2948 &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2949 );
2950 });
2951 }
2952
2953 #[test]
2954 fn missing_namespace() {
2955 let src = r#"
2956 {
2957 "entityTypes": { "User": { } },
2958 "actions": {}
2959 }"#;
2960 let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2961 assert_matches!(schema, Err(e) => {
2962 expect_err(
2963 src,
2964 &miette::Report::new(e),
2965 &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations` at line 3 column 35"#)
2966 .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2967 .build());
2968 });
2969 }
2970
2971 #[test]
2972 fn test_to_internal_name_fragment_with_resolved_types() {
2973 let schema_str = r#"
2974 entity User = { "name": String };
2975 action sendMessage appliesTo {principal: User, resource: User};
2976 "#;
2977
2978 let (json_schema_fragment, _warnings) =
2979 parse_cedar_schema_fragment(schema_str, &Extensions::all_available()).unwrap();
2980
2981 let result = json_schema_fragment.to_internal_name_fragment_with_resolved_types();
2982 assert_matches!(result, Ok(resolved_fragment) => {
2983 let json_value = serde_json::to_value(&resolved_fragment).unwrap();
2984 let json_str = serde_json::to_string(&json_value).unwrap();
2985 assert!(!json_str.contains("EntityOrCommon"));
2987 assert!(json_str.contains("User"));
2989 assert!(json_str.contains("sendMessage"));
2990 });
2991 }
2992}
2993
2994#[cfg(test)]
2996mod strengthened_types {
2997 use cool_asserts::assert_matches;
2998
2999 use super::{
3000 ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
3001 };
3002
3003 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
3006 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
3007 }
3008
3009 #[test]
3010 fn invalid_namespace() {
3011 let src = serde_json::json!(
3012 {
3013 "\n" : {
3014 "entityTypes": {},
3015 "actions": {}
3016 }
3017 });
3018 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3019 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
3020
3021 let src = serde_json::json!(
3022 {
3023 "1" : {
3024 "entityTypes": {},
3025 "actions": {}
3026 }
3027 });
3028 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3029 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
3030
3031 let src = serde_json::json!(
3032 {
3033 "*1" : {
3034 "entityTypes": {},
3035 "actions": {}
3036 }
3037 });
3038 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3039 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
3040
3041 let src = serde_json::json!(
3042 {
3043 "::" : {
3044 "entityTypes": {},
3045 "actions": {}
3046 }
3047 });
3048 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3049 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
3050
3051 let src = serde_json::json!(
3052 {
3053 "A::" : {
3054 "entityTypes": {},
3055 "actions": {}
3056 }
3057 });
3058 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3059 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
3060 }
3061
3062 #[test]
3063 fn invalid_common_type() {
3064 let src = serde_json::json!(
3065 {
3066 "entityTypes": {},
3067 "actions": {},
3068 "commonTypes": {
3069 "" : {
3070 "type": "String"
3071 }
3072 }
3073 });
3074 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3075 assert_error_matches(schema, "invalid id ``: unexpected end of input");
3076
3077 let src = serde_json::json!(
3078 {
3079 "entityTypes": {},
3080 "actions": {},
3081 "commonTypes": {
3082 "~" : {
3083 "type": "String"
3084 }
3085 }
3086 });
3087 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3088 assert_error_matches(schema, "invalid id `~`: invalid token");
3089
3090 let src = serde_json::json!(
3091 {
3092 "entityTypes": {},
3093 "actions": {},
3094 "commonTypes": {
3095 "A::B" : {
3096 "type": "String"
3097 }
3098 }
3099 });
3100 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3101 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
3102 }
3103
3104 #[test]
3105 fn invalid_entity_type() {
3106 let src = serde_json::json!(
3107 {
3108 "entityTypes": {
3109 "": {}
3110 },
3111 "actions": {}
3112 });
3113 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3114 assert_error_matches(schema, "invalid id ``: unexpected end of input");
3115
3116 let src = serde_json::json!(
3117 {
3118 "entityTypes": {
3119 "*": {}
3120 },
3121 "actions": {}
3122 });
3123 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3124 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
3125
3126 let src = serde_json::json!(
3127 {
3128 "entityTypes": {
3129 "A::B": {}
3130 },
3131 "actions": {}
3132 });
3133 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
3134 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
3135 }
3136
3137 #[test]
3138 fn invalid_member_of_types() {
3139 let src = serde_json::json!(
3140 {
3141 "memberOfTypes": [""]
3142 });
3143 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
3144 assert_error_matches(schema, "invalid name ``: unexpected end of input");
3145
3146 let src = serde_json::json!(
3147 {
3148 "memberOfTypes": ["*"]
3149 });
3150 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
3151 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
3152
3153 let src = serde_json::json!(
3154 {
3155 "memberOfTypes": ["A::"]
3156 });
3157 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
3158 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
3159
3160 let src = serde_json::json!(
3161 {
3162 "memberOfTypes": ["::A"]
3163 });
3164 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
3165 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
3166 }
3167
3168 #[test]
3169 fn invalid_apply_spec() {
3170 let src = serde_json::json!(
3171 {
3172 "resourceTypes": [""]
3173 });
3174 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
3175 assert_error_matches(schema, "invalid name ``: unexpected end of input");
3176
3177 let src = serde_json::json!(
3178 {
3179 "resourceTypes": ["*"]
3180 });
3181 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
3182 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
3183
3184 let src = serde_json::json!(
3185 {
3186 "resourceTypes": ["A::"]
3187 });
3188 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
3189 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
3190
3191 let src = serde_json::json!(
3192 {
3193 "resourceTypes": ["::A"]
3194 });
3195 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
3196 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
3197 }
3198
3199 #[test]
3200 fn invalid_schema_entity_types() {
3201 let src = serde_json::json!(
3202 {
3203 "type": "Entity",
3204 "name": ""
3205 });
3206 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3207 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
3208
3209 let src = serde_json::json!(
3210 {
3211 "type": "Entity",
3212 "name": "*"
3213 });
3214 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3215 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
3216
3217 let src = serde_json::json!(
3218 {
3219 "type": "Entity",
3220 "name": "::A"
3221 });
3222 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3223 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
3224
3225 let src = serde_json::json!(
3226 {
3227 "type": "Entity",
3228 "name": "A::"
3229 });
3230 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3231 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
3232 }
3233
3234 #[test]
3235 fn invalid_action_euid() {
3236 let src = serde_json::json!(
3237 {
3238 "id": "action",
3239 "type": ""
3240 });
3241 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
3242 assert_error_matches(schema, "invalid name ``: unexpected end of input");
3243
3244 let src = serde_json::json!(
3245 {
3246 "id": "action",
3247 "type": "*"
3248 });
3249 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
3250 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
3251
3252 let src = serde_json::json!(
3253 {
3254 "id": "action",
3255 "type": "Action::"
3256 });
3257 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
3258 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
3259
3260 let src = serde_json::json!(
3261 {
3262 "id": "action",
3263 "type": "::Action"
3264 });
3265 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
3266 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
3267 }
3268
3269 #[test]
3270 fn invalid_schema_common_types() {
3271 let src = serde_json::json!(
3272 {
3273 "type": ""
3274 });
3275 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3276 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
3277
3278 let src = serde_json::json!(
3279 {
3280 "type": "*"
3281 });
3282 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3283 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
3284
3285 let src = serde_json::json!(
3286 {
3287 "type": "::A"
3288 });
3289 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3290 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
3291
3292 let src = serde_json::json!(
3293 {
3294 "type": "A::"
3295 });
3296 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3297 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
3298 }
3299
3300 #[test]
3301 fn invalid_schema_extension_types() {
3302 let src = serde_json::json!(
3303 {
3304 "type": "Extension",
3305 "name": ""
3306 });
3307 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3308 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
3309
3310 let src = serde_json::json!(
3311 {
3312 "type": "Extension",
3313 "name": "*"
3314 });
3315 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3316 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
3317
3318 let src = serde_json::json!(
3319 {
3320 "type": "Extension",
3321 "name": "__cedar::decimal"
3322 });
3323 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3324 assert_error_matches(
3325 schema,
3326 "invalid extension type `__cedar::decimal`: unexpected token `::`",
3327 );
3328
3329 let src = serde_json::json!(
3330 {
3331 "type": "Extension",
3332 "name": "__cedar::"
3333 });
3334 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3335 assert_error_matches(
3336 schema,
3337 "invalid extension type `__cedar::`: unexpected token `::`",
3338 );
3339
3340 let src = serde_json::json!(
3341 {
3342 "type": "Extension",
3343 "name": "::__cedar"
3344 });
3345 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3346 assert_error_matches(
3347 schema,
3348 "invalid extension type `::__cedar`: unexpected token `::`",
3349 );
3350 }
3351}
3352
3353#[cfg(test)]
3355mod entity_tags {
3356 use super::*;
3357 use crate::test_utils::{expect_err, ExpectedErrorMessageBuilder};
3358 use cool_asserts::assert_matches;
3359 use serde_json::json;
3360
3361 #[track_caller]
3363 fn example_json_schema() -> serde_json::Value {
3364 json!({"": {
3365 "entityTypes": {
3366 "User" : {
3367 "shape" : {
3368 "type" : "Record",
3369 "attributes" : {
3370 "jobLevel" : {
3371 "type" : "Long"
3372 },
3373 }
3374 },
3375 "tags" : {
3376 "type" : "Set",
3377 "element": { "type": "String" }
3378 }
3379 },
3380 "Document" : {
3381 "shape" : {
3382 "type" : "Record",
3383 "attributes" : {
3384 "owner" : {
3385 "type" : "Entity",
3386 "name" : "User"
3387 },
3388 }
3389 },
3390 "tags" : {
3391 "type" : "Set",
3392 "element": { "type": "String" }
3393 }
3394 }
3395 },
3396 "actions": {}
3397 }})
3398 }
3399
3400 #[test]
3401 fn roundtrip() {
3402 let json = example_json_schema();
3403 let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
3404 let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
3405 assert_eq!(json, serialized_json_schema);
3406 }
3407
3408 #[test]
3409 fn basic() {
3410 let json = example_json_schema();
3411 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3412 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3413 assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3414 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });});
3416 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(doc), ..} => {
3417 assert_matches!(&doc.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3418 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });
3420 })})
3421 }
3422
3423 #[test]
3425 fn tag_type_is_common_type() {
3426 let json = json!({"": {
3427 "commonTypes": {
3428 "T": { "type": "String" },
3429 },
3430 "entityTypes": {
3431 "User" : {
3432 "shape" : {
3433 "type" : "Record",
3434 "attributes" : {
3435 "jobLevel" : {
3436 "type" : "Long"
3437 },
3438 }
3439 },
3440 "tags" : { "type" : "T" },
3441 },
3442 },
3443 "actions": {}
3444 }});
3445 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3446 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType {kind: EntityTypeKind::Standard(user), ..} => {
3447 assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name, .. }) => {
3448 assert_eq!(&format!("{type_name}"), "T");
3449 });
3450 })});
3451 }
3452
3453 #[test]
3455 fn tag_type_is_entity_type() {
3456 let json = json!({"": {
3457 "entityTypes": {
3458 "User" : {
3459 "shape" : {
3460 "type" : "Record",
3461 "attributes" : {
3462 "jobLevel" : {
3463 "type" : "Long"
3464 },
3465 }
3466 },
3467 "tags" : { "type" : "Entity", "name": "User" },
3468 },
3469 },
3470 "actions": {}
3471 }});
3472 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3473 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3474 assert_matches!(&user.tags, Some(Type::Type{ ty: TypeVariant::Entity{ name }, ..}) => {
3475 assert_eq!(&format!("{name}"), "User");
3476 });
3477 })});
3478 }
3479
3480 #[test]
3482 fn bad_tags() {
3483 let json = json!({"": {
3484 "entityTypes": {
3485 "User": {
3486 "shape": {
3487 "type": "Record",
3488 "attributes": {
3489 "jobLevel": {
3490 "type": "Long"
3491 },
3492 },
3493 "tags": { "type": "String" },
3494 }
3495 },
3496 },
3497 "actions": {}
3498 }});
3499 assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
3500 expect_err(
3501 &json,
3502 &miette::Report::new(e),
3503 &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
3504 .build(),
3505 );
3506 });
3507 }
3508}
3509
3510#[cfg(test)]
3512mod test_json_roundtrip {
3513 use super::*;
3514
3515 #[track_caller] fn roundtrip(schema: &Fragment<RawName>) {
3517 let json = serde_json::to_value(schema.clone()).unwrap();
3518 let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
3519 assert_eq!(schema, &new_schema);
3520 }
3521
3522 #[test]
3523 fn empty_namespace() {
3524 let fragment = Fragment(BTreeMap::from([(None, NamespaceDefinition::new([], []))]));
3525 roundtrip(&fragment);
3526 }
3527
3528 #[test]
3529 fn nonempty_namespace() {
3530 let fragment = Fragment(BTreeMap::from([(
3531 Some("a".parse().unwrap()),
3532 NamespaceDefinition::new([], []),
3533 )]));
3534 roundtrip(&fragment);
3535 }
3536
3537 #[test]
3538 fn nonempty_entity_types() {
3539 let fragment = Fragment(BTreeMap::from([(
3540 None,
3541 NamespaceDefinition::new(
3542 [(
3543 "a".parse().unwrap(),
3544 EntityType {
3545 kind: EntityTypeKind::Standard(StandardEntityType {
3546 member_of_types: vec!["a".parse().unwrap()],
3547 shape: AttributesOrContext(Type::Type {
3548 ty: TypeVariant::Record(RecordType {
3549 attributes: BTreeMap::new(),
3550 additional_attributes: false,
3551 }),
3552 loc: None,
3553 }),
3554 tags: None,
3555 }),
3556 annotations: Annotations::new(),
3557 loc: None,
3558 },
3559 )],
3560 [(
3561 "action".into(),
3562 ActionType {
3563 attributes: None,
3564 applies_to: Some(ApplySpec {
3565 resource_types: vec!["a".parse().unwrap()],
3566 principal_types: vec!["a".parse().unwrap()],
3567 context: AttributesOrContext(Type::Type {
3568 ty: TypeVariant::Record(RecordType {
3569 attributes: BTreeMap::new(),
3570 additional_attributes: false,
3571 }),
3572 loc: None,
3573 }),
3574 }),
3575 member_of: None,
3576 annotations: Annotations::new(),
3577 loc: None,
3578 #[cfg(feature = "extended-schema")]
3579 defn_loc: None,
3580 },
3581 )],
3582 ),
3583 )]));
3584 roundtrip(&fragment);
3585 }
3586
3587 #[test]
3588 fn multiple_namespaces() {
3589 let fragment = Fragment(BTreeMap::from([
3590 (
3591 Some("foo".parse().unwrap()),
3592 NamespaceDefinition::new(
3593 [(
3594 "a".parse().unwrap(),
3595 EntityType {
3596 kind: EntityTypeKind::Standard(StandardEntityType {
3597 member_of_types: vec!["a".parse().unwrap()],
3598 shape: AttributesOrContext(Type::Type {
3599 ty: TypeVariant::Record(RecordType {
3600 attributes: BTreeMap::new(),
3601 additional_attributes: false,
3602 }),
3603 loc: None,
3604 }),
3605 tags: None,
3606 }),
3607 annotations: Annotations::new(),
3608 loc: None,
3609 },
3610 )],
3611 [],
3612 ),
3613 ),
3614 (
3615 None,
3616 NamespaceDefinition::new(
3617 [],
3618 [(
3619 "action".into(),
3620 ActionType {
3621 attributes: None,
3622 applies_to: Some(ApplySpec {
3623 resource_types: vec!["foo::a".parse().unwrap()],
3624 principal_types: vec!["foo::a".parse().unwrap()],
3625 context: AttributesOrContext(Type::Type {
3626 ty: TypeVariant::Record(RecordType {
3627 attributes: BTreeMap::new(),
3628 additional_attributes: false,
3629 }),
3630 loc: None,
3631 }),
3632 }),
3633 member_of: None,
3634 annotations: Annotations::new(),
3635 loc: None,
3636 #[cfg(feature = "extended-schema")]
3637 defn_loc: None,
3638 },
3639 )],
3640 ),
3641 ),
3642 ]));
3643 roundtrip(&fragment);
3644 }
3645}
3646
3647#[cfg(test)]
3652mod test_duplicates_error {
3653 use super::*;
3654
3655 #[test]
3656 #[should_panic(expected = "invalid entry: found duplicate key")]
3657 fn namespace() {
3658 let src = r#"{
3659 "Foo": {
3660 "entityTypes" : {},
3661 "actions": {}
3662 },
3663 "Foo": {
3664 "entityTypes" : {},
3665 "actions": {}
3666 }
3667 }"#;
3668 Fragment::from_json_str(src).unwrap();
3669 }
3670
3671 #[test]
3672 #[should_panic(expected = "invalid entry: found duplicate key")]
3673 fn entity_type() {
3674 let src = r#"{
3675 "Foo": {
3676 "entityTypes" : {
3677 "Bar": {},
3678 "Bar": {}
3679 },
3680 "actions": {}
3681 }
3682 }"#;
3683 Fragment::from_json_str(src).unwrap();
3684 }
3685
3686 #[test]
3687 #[should_panic(expected = "invalid entry: found duplicate key")]
3688 fn action() {
3689 let src = r#"{
3690 "Foo": {
3691 "entityTypes" : {},
3692 "actions": {
3693 "Bar": {},
3694 "Bar": {}
3695 }
3696 }
3697 }"#;
3698 Fragment::from_json_str(src).unwrap();
3699 }
3700
3701 #[test]
3702 #[should_panic(expected = "invalid entry: found duplicate key")]
3703 fn common_types() {
3704 let src = r#"{
3705 "Foo": {
3706 "entityTypes" : {},
3707 "actions": { },
3708 "commonTypes": {
3709 "Bar": {"type": "Long"},
3710 "Bar": {"type": "String"}
3711 }
3712 }
3713 }"#;
3714 Fragment::from_json_str(src).unwrap();
3715 }
3716
3717 #[test]
3718 #[should_panic(expected = "invalid entry: found duplicate key")]
3719 fn record_type() {
3720 let src = r#"{
3721 "Foo": {
3722 "entityTypes" : {
3723 "Bar": {
3724 "shape": {
3725 "type": "Record",
3726 "attributes": {
3727 "Baz": {"type": "Long"},
3728 "Baz": {"type": "String"}
3729 }
3730 }
3731 }
3732 },
3733 "actions": { }
3734 }
3735 }"#;
3736 Fragment::from_json_str(src).unwrap();
3737 }
3738
3739 #[test]
3740 #[should_panic(expected = "missing field `resourceTypes`")]
3741 fn missing_resource() {
3742 let src = r#"{
3743 "Foo": {
3744 "entityTypes" : {},
3745 "actions": {
3746 "foo" : {
3747 "appliesTo" : {
3748 "principalTypes" : ["a"]
3749 }
3750 }
3751 }
3752 }
3753 }"#;
3754 Fragment::from_json_str(src).unwrap();
3755 }
3756
3757 #[test]
3758 #[should_panic(expected = "missing field `principalTypes`")]
3759 fn missing_principal() {
3760 let src = r#"{
3761 "Foo": {
3762 "entityTypes" : {},
3763 "actions": {
3764 "foo" : {
3765 "appliesTo" : {
3766 "resourceTypes" : ["a"]
3767 }
3768 }
3769 }
3770 }
3771 }"#;
3772 Fragment::from_json_str(src).unwrap();
3773 }
3774
3775 #[test]
3776 #[should_panic(expected = "missing field `resourceTypes`")]
3777 fn missing_both() {
3778 let src = r#"{
3779 "Foo": {
3780 "entityTypes" : {},
3781 "actions": {
3782 "foo" : {
3783 "appliesTo" : {
3784 }
3785 }
3786 }
3787 }
3788 }"#;
3789 Fragment::from_json_str(src).unwrap();
3790 }
3791}
3792
3793#[cfg(test)]
3794mod annotations {
3795 use crate::validator::RawName;
3796 use cool_asserts::assert_matches;
3797
3798 use super::Fragment;
3799
3800 #[test]
3801 fn empty_namespace() {
3802 let src = serde_json::json!(
3803 {
3804 "" : {
3805 "entityTypes": {},
3806 "actions": {},
3807 "annotations": {
3808 "doc": "this is a doc"
3809 }
3810 }
3811 });
3812 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3813 assert_matches!(schema, Err(err) => {
3814 assert_eq!(&err.to_string(), "annotations are not allowed on the empty namespace");
3815 });
3816 }
3817
3818 #[test]
3819 fn basic() {
3820 let src = serde_json::json!(
3821 {
3822 "N" : {
3823 "entityTypes": {},
3824 "actions": {},
3825 "annotations": {
3826 "doc": "this is a doc"
3827 }
3828 }
3829 });
3830 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3831 assert_matches!(schema, Ok(_));
3832
3833 let src = serde_json::json!(
3834 {
3835 "N" : {
3836 "entityTypes": {
3837 "a": {
3838 "annotations": {
3839 "a": "",
3840 "d": null,
3842 "b": "c",
3843 },
3844 "shape": {
3845 "type": "Long",
3846 }
3847 }
3848 },
3849 "actions": {},
3850 "annotations": {
3851 "doc": "this is a doc"
3852 }
3853 }
3854 });
3855 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3856 assert_matches!(schema, Ok(_));
3857
3858 let src = serde_json::json!(
3859 {
3860 "N" : {
3861 "entityTypes": {
3862 "a": {
3863 "annotations": {
3864 "a": "",
3865 "b": "c",
3866 },
3867 "shape": {
3868 "type": "Long",
3869 }
3870 }
3871 },
3872 "actions": {
3873 "a": {
3874 "annotations": {
3875 "doc": "this is a doc"
3876 },
3877 "appliesTo": {
3878 "principalTypes": ["A"],
3879 "resourceTypes": ["B"],
3880 }
3881 },
3882 },
3883 "annotations": {
3884 "doc": "this is a doc"
3885 }
3886 }
3887 });
3888 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3889 assert_matches!(schema, Ok(_));
3890
3891 let src = serde_json::json!({
3892 "N": {
3893 "entityTypes": {},
3894 "actions": {},
3895 "commonTypes": {
3896 "Task": {
3897 "annotations": {
3898 "doc": "a common type representing a task"
3899 },
3900 "type": "Record",
3901 "attributes": {
3902 "id": {
3903 "type": "Long",
3904 "annotations": {
3905 "doc": "task id"
3906 }
3907 },
3908 "name": {
3909 "type": "String"
3910 },
3911 "state": {
3912 "type": "String"
3913 }
3914 }
3915 }}}});
3916 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3917 assert_matches!(schema, Ok(_));
3918
3919 let src = serde_json::json!({
3920 "N": {
3921 "entityTypes": {
3922 "User" : {
3923 "shape" : {
3924 "type" : "Record",
3925 "attributes" : {
3926 "name" : {
3927 "annotations": {
3928 "a": null,
3929 },
3930 "type" : "String"
3931 },
3932 "age" : {
3933 "type" : "Long"
3934 }
3935 }
3936 }
3937 }
3938 },
3939 "actions": {},
3940 "commonTypes": {}
3941 }});
3942 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3943 assert_matches!(schema, Ok(_));
3944
3945 let src = serde_json::json!({
3947 "N": {
3948 "entityTypes": {
3949 "User" : {
3950 "shape" : {
3951 "type" : "Record",
3952 "attributes" : {
3953 "name" : {
3954 "annotations": {
3955 "first_layer": "b"
3956 },
3957 "type" : "Record",
3958 "attributes": {
3959 "a": {
3960 "type": "Record",
3961 "annotations": {
3962 "second_layer": "d"
3963 },
3964 "attributes": {
3965 "...": {
3966 "annotations": {
3967 "last_layer": null,
3968 },
3969 "type": "Long"
3970 }
3971 }
3972 }
3973 }
3974 },
3975 "age" : {
3976 "type" : "Long"
3977 }
3978 }
3979 }
3980 }
3981 },
3982 "actions": {},
3983 "commonTypes": {}
3984 }});
3985 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3986 assert_matches!(schema, Ok(_));
3987 }
3988
3989 #[track_caller]
3990 fn test_unknown_fields(src: serde_json::Value, field: &str, expected: &str) {
3991 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3992 assert_matches!(schema, Err(errs) => {
3993 assert_eq!(errs.to_string(), format!("unknown field {field}, expected one of {expected}"));
3994 });
3995 }
3996
3997 const ENTITY_TYPE_EXPECTED_ATTRIBUTES: &str =
3998 "`memberOfTypes`, `shape`, `tags`, `enum`, `annotations`";
3999 const NAMESPACE_EXPECTED_ATTRIBUTES: &str =
4000 "`commonTypes`, `entityTypes`, `actions`, `annotations`";
4001 const ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES: &str =
4002 "`type`, `element`, `attributes`, `additionalAttributes`, `name`";
4003 const APPLIES_TO_EXPECTED_ATTRIBUTES: &str = "`resourceTypes`, `principalTypes`, `context`";
4004
4005 #[test]
4006 fn unknown_fields() {
4007 let src = serde_json::json!(
4008 {
4009 "N": {
4010 "entityTypes": {
4011 "UserGroup": {
4012 "shape44": {
4013 "type": "Record",
4014 "attributes": {}
4015 },
4016 "memberOfTypes": [
4017 "UserGroup"
4018 ]
4019 }},
4020 "actions": {},
4021 }});
4022 test_unknown_fields(src, "`shape44`", ENTITY_TYPE_EXPECTED_ATTRIBUTES);
4023
4024 let src = serde_json::json!(
4025 {
4026 "N": {
4027 "entityTypes": {},
4028 "actions": {},
4029 "commonTypes": {
4030 "C": {
4031 "type": "Set",
4032 "element": {
4033 "annotations": {
4034 "doc": "this is a doc"
4035 },
4036 "type": "Long"
4037 }
4038 }
4039 }}});
4040 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4041
4042 let src = serde_json::json!(
4043 {
4044 "N": {
4045 "entityTypes": {},
4046 "actions": {},
4047 "commonTypes": {
4048 "C": {
4049 "type": "Long",
4050 "foo": 1,
4051 "annotations": {
4052 "doc": "this is a doc"
4053 },
4054 }}}});
4055 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4056
4057 let src = serde_json::json!(
4058 {
4059 "N": {
4060 "entityTypes": {},
4061 "actions": {},
4062 "commonTypes": {
4063 "C": {
4064 "type": "Record",
4065 "attributes": {
4066 "a": {
4067 "annotations": {
4068 "doc": "this is a doc"
4069 },
4070 "type": "Long",
4071 "foo": 2,
4072 "required": true,
4073 }
4074 },
4075 }}}});
4076 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4077
4078 let src = serde_json::json!(
4079 {
4080 "N": {
4081 "entityTypes": {},
4082 "actions": {},
4083 "commonTypes": {
4084 "C": {
4085 "type": "Record",
4086 "attributes": {
4087 "a": {
4088 "annotations": {
4089 "doc": "this is a doc"
4090 },
4091 "type": "Record",
4092 "attributes": {
4093 "b": {
4094 "annotations": {
4095 "doc": "this is a doc"
4096 },
4097 "type": "Long",
4098 "bar": 3,
4099 },
4100 },
4101 "required": true,
4102 }
4103 },
4104 }}}});
4105 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4106
4107 let src = serde_json::json!(
4108 {
4109 "N": {
4110 "entityTypes": {
4111 "UserGroup": {
4112 "shape": {
4113 "annotations": {
4114 "doc": "this is a doc"
4115 },
4116 "type": "Record",
4117 "attributes": {}
4118 },
4119 "memberOfTypes": [
4120 "UserGroup"
4121 ]
4122 }},
4123 "actions": {},
4124 }});
4125 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4126
4127 let src = serde_json::json!(
4128 {
4129 "N": {
4130 "entityTypes": {},
4131 "actions": {
4132 "a": {
4133 "appliesTo": {
4134 "annotations": {
4135 "doc": "this is a doc"
4136 },
4137 "principalTypes": ["A"],
4138 "resourceTypes": ["B"],
4139 }
4140 },
4141 },
4142 }});
4143 test_unknown_fields(src, "`annotations`", APPLIES_TO_EXPECTED_ATTRIBUTES);
4144
4145 let src = serde_json::json!(
4146 {
4147 "N" : {
4148 "entityTypes": {},
4149 "actions": {},
4150 "foo": "",
4151 "annotations": {
4152 "doc": "this is a doc"
4153 }
4154 }
4155 });
4156 test_unknown_fields(src, "`foo`", NAMESPACE_EXPECTED_ATTRIBUTES);
4157
4158 let src = serde_json::json!(
4159 {
4160 "" : {
4161 "entityTypes": {},
4162 "actions": {},
4163 "commonTypes": {
4164 "a": {
4165 "type": "Long",
4166 "annotations": {
4167 "foo": ""
4168 },
4169 "bar": 1,
4170 }
4171 }
4172 }
4173 });
4174 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4175
4176 let src = serde_json::json!(
4177 {
4178 "N" : {
4179 "entityTypes": {},
4180 "actions": {},
4181 "commonTypes": {
4182 "a": {
4183 "type": "Record",
4184 "annotations": {
4185 "foo": ""
4186 },
4187 "attributes": {
4188 "a": {
4189 "bar": 1,
4190 "type": "Long"
4191 }
4192 }
4193 }
4194 }
4195 }
4196 });
4197 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
4198 }
4199}
4200
4201#[cfg(test)]
4202#[expect(clippy::collection_is_never_read, reason = "testing code")]
4203mod ord {
4204 use super::{InternalName, RawName, Type, TypeVariant};
4205 use std::collections::BTreeSet;
4206
4207 #[test]
4209 fn type_ord() {
4210 let mut set: BTreeSet<Type<RawName>> = BTreeSet::default();
4211 set.insert(Type::Type {
4212 ty: TypeVariant::String,
4213 loc: None,
4214 });
4215 let mut set: BTreeSet<Type<InternalName>> = BTreeSet::default();
4216 set.insert(Type::Type {
4217 ty: TypeVariant::String,
4218 loc: None,
4219 });
4220 }
4221}
4222
4223#[cfg(test)]
4224#[expect(clippy::indexing_slicing, reason = "tests")]
4225mod enumerated_entity_types {
4226 use cool_asserts::assert_matches;
4227 use nonempty::nonempty;
4228
4229 use crate::{
4230 ast::Eid,
4231 validator::{
4232 json_schema::{EntityType, EntityTypeKind, Fragment},
4233 RawName,
4234 },
4235 };
4236
4237 #[test]
4238 fn basic() {
4239 let src = serde_json::json!({
4240 "": {
4241 "entityTypes": {
4242 "Foo": {
4243 "enum": ["foo", "bar"],
4244 "annotations": {
4245 "a": "b",
4246 }
4247 },
4248 },
4249 "actions": {},
4250 }
4251 });
4252 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
4253 assert_matches!(schema, Ok(frag) => {
4254 assert_matches!(&frag.0[&None].entity_types[&"Foo".parse().unwrap()], EntityType {
4255 kind: EntityTypeKind::Enum {choices},
4256 ..
4257 } => {
4258 assert_eq!(choices, &nonempty![Eid::new("foo"), Eid::new("bar")]);
4259 });
4260 });
4261
4262 let src = serde_json::json!({
4263 "": {
4264 "entityTypes": {
4265 "Foo": {
4266 "enum": [],
4267 "annotations": {
4268 "a": "b",
4269 }
4270 },
4271 },
4272 "actions": {},
4273 }
4274 });
4275 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
4276 assert_matches!(schema, Err(errs) => {
4277 assert_eq!(errs.to_string(), "the vector provided was empty, NonEmpty needs at least one element");
4279 });
4280
4281 let src = serde_json::json!({
4282 "": {
4283 "entityTypes": {
4284 "Foo": {
4285 "enum": null,
4286 },
4287 },
4288 "actions": {},
4289 }
4290 });
4291 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
4292 assert_matches!(schema, Err(errs) => {
4293 assert_eq!(errs.to_string(), "invalid type: null, expected a sequence");
4294 });
4295
4296 let src = serde_json::json!({
4297 "": {
4298 "entityTypes": {
4299 "Foo": {
4300 "enum": ["foo"],
4301 "memberOfTypes": ["bar"],
4302 },
4303 },
4304 "actions": {},
4305 }
4306 });
4307 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
4308 assert_matches!(schema, Err(errs) => {
4309 assert_eq!(errs.to_string(), "unexpected field: memberOfTypes");
4310 });
4311 }
4312}