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