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