1use crate::{
20 ast::{Eid, EntityUID, InternalName, Name, UnreservedId},
21 entities::CedarValueJson,
22 est::Annotations,
23 extensions::Extensions,
24 parser::Loc,
25 FromNormalizedStr,
26};
27use educe::Educe;
28use itertools::Itertools;
29use nonempty::{nonempty, NonEmpty};
30use serde::{
31 de::{MapAccess, Visitor},
32 ser::SerializeMap,
33 Deserialize, Deserializer, Serialize, Serializer,
34};
35use serde_with::serde_as;
36use smol_str::{SmolStr, ToSmolStr};
37use std::hash::Hash;
38use std::{
39 collections::{BTreeMap, HashMap, HashSet},
40 fmt::Display,
41 marker::PhantomData,
42 str::FromStr,
43};
44use thiserror::Error;
45
46use crate::validator::{
47 cedar_schema::{
48 self, fmt::ToCedarSchemaSyntaxError, parser::parse_cedar_schema_fragment, SchemaWarning,
49 },
50 err::{schema_errors::*, Result},
51 AllDefs, CedarSchemaError, CedarSchemaParseError, ConditionalName, RawName, ReferenceType,
52};
53
54#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
56#[educe(PartialEq, Eq)]
57#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
58pub struct CommonType<N> {
59 #[serde(flatten)]
61 pub ty: Type<N>,
62 #[serde(default)]
64 #[serde(skip_serializing_if = "Annotations::is_empty")]
65 pub annotations: Annotations,
66 #[serde(skip)]
72 #[educe(PartialEq(ignore))]
73 pub loc: Option<Loc>,
74}
75
76#[derive(Educe, Debug, Clone, Deserialize)]
96#[educe(PartialEq, Eq)]
97#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
98#[serde(transparent)]
99#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
100#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
101#[cfg_attr(feature = "wasm", serde(rename = "SchemaJson"))]
102pub struct Fragment<N>(
103 #[serde(deserialize_with = "deserialize_schema_fragment")]
104 #[cfg_attr(
105 feature = "wasm",
106 tsify(type = "Record<string, NamespaceDefinition<N>>")
107 )]
108 pub BTreeMap<Option<Name>, NamespaceDefinition<N>>,
109);
110
111fn deserialize_schema_fragment<'de, D, N: Deserialize<'de> + From<RawName>>(
113 deserializer: D,
114) -> std::result::Result<BTreeMap<Option<Name>, NamespaceDefinition<N>>, D::Error>
115where
116 D: Deserializer<'de>,
117{
118 let raw: BTreeMap<SmolStr, NamespaceDefinition<N>> =
119 serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
120 Ok(BTreeMap::from_iter(
121 raw.into_iter()
122 .map(|(key, value)| {
123 let key = if key.is_empty() {
124 if !value.annotations.is_empty() {
125 Err(serde::de::Error::custom(
126 "annotations are not allowed on the empty namespace".to_string(),
127 ))?
128 }
129 None
130 } else {
131 Some(Name::from_normalized_str(&key).map_err(|err| {
132 serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
133 })?)
134 };
135 Ok((key, value))
136 })
137 .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition<N>)>, D::Error>>(
138 )?,
139 ))
140}
141
142impl<N: Serialize> Serialize for Fragment<N> {
143 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
145 where
146 S: Serializer,
147 {
148 let mut map = serializer.serialize_map(Some(self.0.len()))?;
149 for (k, v) in &self.0 {
150 let k: SmolStr = match k {
151 None => "".into(),
152 Some(name) => name.to_smolstr(),
153 };
154 map.serialize_entry(&k, &v)?;
155 }
156 map.end()
157 }
158}
159
160impl Fragment<RawName> {
161 pub fn from_json_str(json: &str) -> Result<Self> {
164 serde_json::from_str(json).map_err(|e| JsonDeserializationError::new(e, Some(json)).into())
165 }
166
167 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
170 serde_json::from_value(json).map_err(|e| JsonDeserializationError::new(e, None).into())
171 }
172
173 pub fn from_json_file(file: impl std::io::Read) -> Result<Self> {
175 serde_json::from_reader(file).map_err(|e| JsonDeserializationError::new(e, None).into())
176 }
177
178 pub fn from_cedarschema_str<'a>(
180 src: &str,
181 extensions: &Extensions<'a>,
182 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
183 {
184 parse_cedar_schema_fragment(src, extensions)
185 .map_err(|e| CedarSchemaParseError::new(e, src).into())
186 }
187
188 pub fn from_cedarschema_file<'a>(
190 mut file: impl std::io::Read,
191 extensions: &'a Extensions<'_>,
192 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
193 {
194 let mut src = String::new();
195 file.read_to_string(&mut src)?;
196 Self::from_cedarschema_str(&src, extensions)
197 }
198}
199
200impl<N: Display> Fragment<N> {
201 pub fn to_cedarschema(&self) -> std::result::Result<String, ToCedarSchemaSyntaxError> {
203 let src = cedar_schema::fmt::json_schema_to_cedar_schema_str(self)?;
204 Ok(src)
205 }
206}
207
208#[derive(Educe, Debug, Clone, Serialize)]
211#[educe(PartialEq, Eq, PartialOrd, Ord, Hash)]
212#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
213#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
214pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
215
216impl From<CommonTypeId> for UnreservedId {
217 fn from(value: CommonTypeId) -> Self {
218 value.0
219 }
220}
221
222impl AsRef<UnreservedId> for CommonTypeId {
223 fn as_ref(&self) -> &UnreservedId {
224 &self.0
225 }
226}
227
228impl CommonTypeId {
229 pub fn new(id: UnreservedId) -> std::result::Result<Self, ReservedCommonTypeBasenameError> {
231 if Self::is_reserved_schema_keyword(&id) {
232 Err(ReservedCommonTypeBasenameError { id })
233 } else {
234 Ok(Self(id))
235 }
236 }
237
238 pub fn unchecked(id: UnreservedId) -> Self {
241 Self(id)
242 }
243
244 fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
249 matches!(
250 id.as_ref(),
251 "Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
252 )
253 }
254
255 #[cfg(feature = "arbitrary")]
258 fn make_into_valid_common_type_id(id: &UnreservedId) -> Self {
259 Self::new(id.clone()).unwrap_or_else(|_| {
260 #[allow(clippy::unwrap_used)]
262 let new_id = format!("_{id}").parse().unwrap();
263 #[allow(clippy::unwrap_used)]
265 Self::new(new_id).unwrap()
266 })
267 }
268}
269
270impl Display for CommonTypeId {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 self.0.fmt(f)
273 }
274}
275
276#[cfg(feature = "arbitrary")]
277impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
278 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
279 let id: UnreservedId = u.arbitrary()?;
280 Ok(CommonTypeId::make_into_valid_common_type_id(&id))
281 }
282
283 fn size_hint(depth: usize) -> (usize, Option<usize>) {
284 <UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
285 }
286}
287
288impl<'de> Deserialize<'de> for CommonTypeId {
290 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
291 where
292 D: Deserializer<'de>,
293 {
294 UnreservedId::deserialize(deserializer).and_then(|id| {
295 CommonTypeId::new(id).map_err(|e| serde::de::Error::custom(format!("{e}")))
296 })
297 }
298}
299
300#[derive(Debug, Error, PartialEq, Eq, Clone)]
302#[error("this is reserved and cannot be the basename of a common-type declaration: {id}")]
303pub struct ReservedCommonTypeBasenameError {
304 pub(crate) id: UnreservedId,
306}
307
308#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
318#[educe(PartialEq, Eq)]
319#[serde_as]
320#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
321#[serde(bound(serialize = "N: Serialize"))]
322#[serde(deny_unknown_fields)]
323#[serde(rename_all = "camelCase")]
324#[doc(hidden)]
325#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
326#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
327pub struct NamespaceDefinition<N> {
328 #[serde(default)]
329 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
330 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
331 pub common_types: BTreeMap<CommonTypeId, CommonType<N>>,
332 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
333 pub entity_types: BTreeMap<UnreservedId, EntityType<N>>,
334 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
335 pub actions: BTreeMap<SmolStr, ActionType<N>>,
336 #[serde(default)]
338 #[serde(skip_serializing_if = "Annotations::is_empty")]
339 pub annotations: Annotations,
340
341 #[cfg(feature = "extended-schema")]
342 #[serde(skip)]
343 #[educe(Eq(ignore))]
344 pub loc: Option<Loc>,
345}
346
347#[cfg(test)]
348impl<N> NamespaceDefinition<N> {
349 pub fn new(
352 entity_types: impl IntoIterator<Item = (UnreservedId, EntityType<N>)>,
353 actions: impl IntoIterator<Item = (SmolStr, ActionType<N>)>,
354 ) -> Self {
355 Self {
356 common_types: BTreeMap::new(),
357 entity_types: entity_types.into_iter().collect(),
358 actions: actions.into_iter().collect(),
359 annotations: Annotations::new(),
360 #[cfg(feature = "extended-schema")]
361 loc: None,
362 }
363 }
364}
365
366impl NamespaceDefinition<RawName> {
367 pub fn conditionally_qualify_type_references(
369 self,
370 ns: Option<&InternalName>,
371 ) -> NamespaceDefinition<ConditionalName> {
372 NamespaceDefinition {
373 common_types: self
374 .common_types
375 .into_iter()
376 .map(|(k, v)| {
377 (
378 k,
379 CommonType {
380 ty: v.ty.conditionally_qualify_type_references(ns),
381 annotations: v.annotations,
382 loc: v.loc,
383 },
384 )
385 })
386 .collect(),
387 entity_types: self
388 .entity_types
389 .into_iter()
390 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
391 .collect(),
392 actions: self
393 .actions
394 .into_iter()
395 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
396 .collect(),
397 annotations: self.annotations,
398 #[cfg(feature = "extended-schema")]
399 loc: self.loc,
400 }
401 }
402}
403
404impl NamespaceDefinition<ConditionalName> {
405 pub fn fully_qualify_type_references(
412 self,
413 all_defs: &AllDefs,
414 ) -> Result<NamespaceDefinition<InternalName>> {
415 Ok(NamespaceDefinition {
416 common_types: self
417 .common_types
418 .into_iter()
419 .map(|(k, v)| {
420 Ok((
421 k,
422 CommonType {
423 ty: v.ty.fully_qualify_type_references(all_defs)?,
424 annotations: v.annotations,
425 loc: v.loc,
426 },
427 ))
428 })
429 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
430 entity_types: self
431 .entity_types
432 .into_iter()
433 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
434 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
435 actions: self
436 .actions
437 .into_iter()
438 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
439 .collect::<Result<_>>()?,
440 annotations: self.annotations,
441 #[cfg(feature = "extended-schema")]
442 loc: self.loc,
443 })
444 }
445}
446
447#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
451#[serde(untagged)]
452#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
453#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
454pub enum EntityTypeKind<N> {
455 Standard(StandardEntityType<N>),
457 Enum {
460 #[serde(rename = "enum")]
461 choices: NonEmpty<SmolStr>,
463 },
464}
465
466#[derive(Educe, Debug, Clone, Serialize)]
475#[educe(PartialEq, Eq)]
476#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
477pub struct EntityType<N> {
478 #[serde(flatten)]
480 pub kind: EntityTypeKind<N>,
481 #[serde(default)]
483 #[serde(skip_serializing_if = "Annotations::is_empty")]
484 pub annotations: Annotations,
485 #[serde(skip)]
491 #[educe(PartialEq(ignore))]
492 pub loc: Option<Loc>,
493}
494
495impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for EntityType<N> {
496 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
497 where
498 D: serde::Deserializer<'de>,
499 {
500 enum RealOption<T> {
502 Some(T),
503 None,
504 }
505 impl<'de, T: Deserialize<'de>> Deserialize<'de> for RealOption<T> {
506 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
507 where
508 D: Deserializer<'de>,
509 {
510 T::deserialize(deserializer).map(Self::Some)
511 }
512 }
513 impl<T> Default for RealOption<T> {
514 fn default() -> Self {
515 Self::None
516 }
517 }
518
519 impl<T> From<RealOption<T>> for Option<T> {
520 fn from(value: RealOption<T>) -> Self {
521 match value {
522 RealOption::Some(v) => Self::Some(v),
523 RealOption::None => None,
524 }
525 }
526 }
527
528 #[derive(Deserialize)]
532 #[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
533 #[serde(deny_unknown_fields)]
534 #[serde(rename_all = "camelCase")]
535 struct Everything<N> {
536 #[serde(default)]
537 member_of_types: RealOption<Vec<N>>,
538 #[serde(default)]
539 shape: RealOption<AttributesOrContext<N>>,
540 #[serde(default)]
541 tags: RealOption<Type<N>>,
542 #[serde(default)]
543 #[serde(rename = "enum")]
544 choices: RealOption<NonEmpty<SmolStr>>,
545 #[serde(default)]
546 annotations: Annotations,
547 }
548
549 let value: Everything<N> = Everything::deserialize(deserializer)?;
550 if let Some(choices) = value.choices.into() {
554 let mut unexpected_fields: Vec<&str> = vec![];
555 if Option::<Vec<N>>::from(value.member_of_types).is_some() {
556 unexpected_fields.push("memberOfTypes");
557 }
558 if Option::<AttributesOrContext<N>>::from(value.shape).is_some() {
559 unexpected_fields.push("shape");
560 }
561 if Option::<Type<N>>::from(value.tags).is_some() {
562 unexpected_fields.push("tags");
563 }
564 if !unexpected_fields.is_empty() {
565 return Err(serde::de::Error::custom(format!(
566 "unexpected field: {}",
567 unexpected_fields.into_iter().join(", ")
568 )));
569 }
570 Ok(EntityType {
571 kind: EntityTypeKind::Enum { choices },
572 annotations: value.annotations,
573 loc: None,
574 })
575 } else {
576 Ok(EntityType {
577 kind: EntityTypeKind::Standard(StandardEntityType {
578 member_of_types: Option::from(value.member_of_types).unwrap_or_default(),
579 shape: Option::from(value.shape).unwrap_or_default(),
580 tags: Option::from(value.tags),
581 }),
582 annotations: value.annotations,
583 loc: None,
584 })
585 }
586 }
587}
588
589#[derive(Debug, Clone, Serialize, Deserialize, Educe)]
592#[educe(PartialEq, Eq)]
593#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
594#[serde(rename_all = "camelCase")]
595#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
596#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
597pub struct StandardEntityType<N> {
598 #[serde(skip_serializing_if = "Vec::is_empty")]
601 #[serde(default)]
602 pub member_of_types: Vec<N>,
603 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
605 #[serde(default)]
606 pub shape: AttributesOrContext<N>,
607 #[serde(skip_serializing_if = "Option::is_none")]
609 #[serde(default)]
610 pub tags: Option<Type<N>>,
611}
612
613#[cfg(test)]
614impl<N> From<StandardEntityType<N>> for EntityType<N> {
615 fn from(value: StandardEntityType<N>) -> Self {
616 Self {
617 kind: EntityTypeKind::Standard(value),
618 annotations: Annotations::new(),
619 loc: None,
620 }
621 }
622}
623
624impl EntityType<RawName> {
625 pub fn conditionally_qualify_type_references(
627 self,
628 ns: Option<&InternalName>,
629 ) -> EntityType<ConditionalName> {
630 let Self {
631 kind,
632 annotations,
633 loc,
634 } = self;
635 match kind {
636 EntityTypeKind::Enum { choices } => EntityType {
637 kind: EntityTypeKind::Enum { choices },
638 annotations,
639 loc,
640 },
641 EntityTypeKind::Standard(ty) => EntityType {
642 kind: EntityTypeKind::Standard(StandardEntityType {
643 member_of_types: ty
644 .member_of_types
645 .into_iter()
646 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
648 shape: ty.shape.conditionally_qualify_type_references(ns),
649 tags: ty
650 .tags
651 .map(|ty| ty.conditionally_qualify_type_references(ns)),
652 }),
653 annotations,
654 loc,
655 },
656 }
657 }
658}
659
660impl EntityType<ConditionalName> {
661 pub fn fully_qualify_type_references(
668 self,
669 all_defs: &AllDefs,
670 ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
671 let Self {
672 kind,
673 annotations,
674 loc,
675 } = self;
676 Ok(match kind {
677 EntityTypeKind::Enum { choices } => EntityType {
678 kind: EntityTypeKind::Enum { choices },
679 annotations,
680 loc,
681 },
682 EntityTypeKind::Standard(ty) => EntityType {
683 kind: EntityTypeKind::Standard(StandardEntityType {
684 member_of_types: ty
685 .member_of_types
686 .into_iter()
687 .map(|cname| cname.resolve(all_defs))
688 .collect::<std::result::Result<_, _>>()?,
689 shape: ty.shape.fully_qualify_type_references(all_defs)?,
690 tags: ty
691 .tags
692 .map(|ty| ty.fully_qualify_type_references(all_defs))
693 .transpose()?,
694 }),
695 annotations,
696 loc,
697 },
698 })
699 }
700}
701
702#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
709#[educe(PartialEq, Eq)]
710#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
711#[serde(transparent)]
712#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
713#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
714pub struct AttributesOrContext<N>(
715 pub Type<N>,
718);
719
720impl<N> AttributesOrContext<N> {
721 pub fn into_inner(self) -> Type<N> {
723 self.0
724 }
725
726 pub fn is_empty_record(&self) -> bool {
728 self.0.is_empty_record()
729 }
730
731 pub fn loc(&self) -> Option<&Loc> {
733 self.0.loc()
734 }
735}
736
737impl<N> Default for AttributesOrContext<N> {
738 fn default() -> Self {
739 Self::from(RecordType::default())
740 }
741}
742
743impl<N: Display> Display for AttributesOrContext<N> {
744 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
745 self.0.fmt(f)
746 }
747}
748
749impl<N> From<RecordType<N>> for AttributesOrContext<N> {
750 fn from(rty: RecordType<N>) -> AttributesOrContext<N> {
751 Self(Type::Type {
752 ty: TypeVariant::Record(rty),
753 loc: None,
754 })
755 }
756}
757
758impl AttributesOrContext<RawName> {
759 pub fn conditionally_qualify_type_references(
761 self,
762 ns: Option<&InternalName>,
763 ) -> AttributesOrContext<ConditionalName> {
764 AttributesOrContext(self.0.conditionally_qualify_type_references(ns))
765 }
766}
767
768impl AttributesOrContext<ConditionalName> {
769 pub fn fully_qualify_type_references(
776 self,
777 all_defs: &AllDefs,
778 ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
779 Ok(AttributesOrContext(
780 self.0.fully_qualify_type_references(all_defs)?,
781 ))
782 }
783}
784
785#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
793#[educe(PartialEq, Eq)]
794#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
795#[serde(deny_unknown_fields)]
796#[serde(rename_all = "camelCase")]
797#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
798#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
799pub struct ActionType<N> {
800 #[serde(default)]
804 #[serde(skip_serializing_if = "Option::is_none")]
805 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
806 #[serde(default)]
808 #[serde(skip_serializing_if = "Option::is_none")]
809 pub applies_to: Option<ApplySpec<N>>,
810 #[serde(default)]
812 #[serde(skip_serializing_if = "Option::is_none")]
813 pub member_of: Option<Vec<ActionEntityUID<N>>>,
814 #[serde(default)]
816 #[serde(skip_serializing_if = "Annotations::is_empty")]
817 pub annotations: Annotations,
818 #[serde(skip)]
824 #[educe(PartialEq(ignore))]
825 pub loc: Option<Loc>,
826
827 #[cfg(feature = "extended-schema")]
829 #[serde(skip)]
830 #[educe(PartialEq(ignore))]
831 pub(crate) defn_loc: Option<Loc>,
832}
833
834impl ActionType<RawName> {
835 pub fn conditionally_qualify_type_references(
837 self,
838 ns: Option<&InternalName>,
839 ) -> ActionType<ConditionalName> {
840 ActionType {
841 attributes: self.attributes,
842 applies_to: self
843 .applies_to
844 .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
845 member_of: self.member_of.map(|v| {
846 v.into_iter()
847 .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
848 .collect()
849 }),
850 annotations: self.annotations,
851 loc: self.loc,
852 #[cfg(feature = "extended-schema")]
853 defn_loc: self.defn_loc,
854 }
855 }
856}
857
858impl ActionType<ConditionalName> {
859 pub fn fully_qualify_type_references(
866 self,
867 all_defs: &AllDefs,
868 ) -> Result<ActionType<InternalName>> {
869 Ok(ActionType {
870 attributes: self.attributes,
871 applies_to: self
872 .applies_to
873 .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
874 .transpose()?,
875 member_of: self
876 .member_of
877 .map(|v| {
878 v.into_iter()
879 .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
880 .collect::<std::result::Result<_, ActionNotDefinedError>>()
881 })
882 .transpose()?,
883 annotations: self.annotations,
884 loc: self.loc,
885 #[cfg(feature = "extended-schema")]
886 defn_loc: self.defn_loc,
887 })
888 }
889}
890
891#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
901#[educe(PartialEq, Eq)]
902#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
903#[serde(deny_unknown_fields)]
904#[serde(rename_all = "camelCase")]
905#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
906#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
907pub struct ApplySpec<N> {
908 pub resource_types: Vec<N>,
910 pub principal_types: Vec<N>,
912 #[serde(default)]
914 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
915 pub context: AttributesOrContext<N>,
916}
917
918impl ApplySpec<RawName> {
919 pub fn conditionally_qualify_type_references(
921 self,
922 ns: Option<&InternalName>,
923 ) -> ApplySpec<ConditionalName> {
924 ApplySpec {
925 resource_types: self
926 .resource_types
927 .into_iter()
928 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
930 principal_types: self
931 .principal_types
932 .into_iter()
933 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
935 context: self.context.conditionally_qualify_type_references(ns),
936 }
937 }
938}
939
940impl ApplySpec<ConditionalName> {
941 pub fn fully_qualify_type_references(
948 self,
949 all_defs: &AllDefs,
950 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
951 Ok(ApplySpec {
952 resource_types: self
953 .resource_types
954 .into_iter()
955 .map(|cname| cname.resolve(all_defs))
956 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
957 principal_types: self
958 .principal_types
959 .into_iter()
960 .map(|cname| cname.resolve(all_defs))
961 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
962 context: self.context.fully_qualify_type_references(all_defs)?,
963 })
964 }
965}
966
967#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
969#[educe(PartialEq, Eq, Hash)]
970#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
971#[serde(deny_unknown_fields)]
972#[serde(rename_all = "camelCase")]
973#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
974#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
975pub struct ActionEntityUID<N> {
976 pub id: SmolStr,
978
979 #[serde(rename = "type")]
990 #[serde(default)]
991 #[serde(skip_serializing_if = "Option::is_none")]
992 pub ty: Option<N>,
993 #[cfg(feature = "extended-schema")]
994 #[serde(skip)]
995 pub loc: Option<Loc>,
997}
998
999impl ActionEntityUID<RawName> {
1000 pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
1003 Self {
1004 id,
1005 ty,
1006 #[cfg(feature = "extended-schema")]
1007 loc: None,
1008 }
1009 }
1010
1011 pub fn default_type(id: SmolStr) -> Self {
1016 Self {
1017 id,
1018 ty: None,
1019 #[cfg(feature = "extended-schema")]
1020 loc: None,
1021 }
1022 }
1023
1024 #[cfg(feature = "extended-schema")]
1029 pub fn default_type_with_loc(id: SmolStr, loc: Option<Loc>) -> Self {
1030 Self { id, ty: None, loc }
1031 }
1032}
1033
1034impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
1035 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1036 if let Some(ty) = &self.ty {
1037 write!(f, "{}::", ty)?
1038 } else {
1039 write!(f, "Action::")?
1040 }
1041 write!(f, "\"{}\"", self.id.escape_debug())
1042 }
1043}
1044
1045impl ActionEntityUID<RawName> {
1046 pub fn conditionally_qualify_type_references(
1048 self,
1049 ns: Option<&InternalName>,
1050 ) -> ActionEntityUID<ConditionalName> {
1051 ActionEntityUID {
1054 id: self.id,
1055 ty: {
1056 #[allow(clippy::expect_used)]
1058 let raw_name = self
1059 .ty
1060 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1061 Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
1062 },
1063 #[cfg(feature = "extended-schema")]
1064 loc: None,
1065 }
1066 }
1067
1068 pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
1070 ActionEntityUID {
1073 id: self.id,
1074 ty: {
1075 #[allow(clippy::expect_used)]
1077 let raw_name = self
1078 .ty
1079 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1080 Some(raw_name.qualify_with(ns))
1081 },
1082 #[cfg(feature = "extended-schema")]
1083 loc: self.loc,
1084 }
1085 }
1086}
1087
1088impl ActionEntityUID<ConditionalName> {
1089 pub fn ty(&self) -> &ConditionalName {
1091 #[allow(clippy::expect_used)]
1093 self.ty.as_ref().expect("by INVARIANT on self.ty")
1094 }
1095
1096 pub fn fully_qualify_type_references(
1104 self,
1105 all_defs: &AllDefs,
1106 ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
1107 for possibility in self.possibilities() {
1108 if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
1112 if all_defs.is_defined_as_action(&euid) {
1113 return Ok(possibility);
1114 }
1115 }
1116 }
1117 Err(ActionNotDefinedError(nonempty!(self)))
1118 }
1119
1120 pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
1124 self.ty()
1127 .possibilities()
1128 .map(|possibility| ActionEntityUID {
1129 id: self.id.clone(),
1130 ty: Some(possibility.clone()),
1131 #[cfg(feature = "extended-schema")]
1132 loc: None,
1133 })
1134 }
1135
1136 pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
1140 ActionEntityUID {
1141 id: self.id.clone(),
1142 ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
1143 #[cfg(feature = "extended-schema")]
1144 loc: None,
1145 }
1146 }
1147}
1148
1149impl ActionEntityUID<Name> {
1150 pub fn ty(&self) -> &Name {
1152 #[allow(clippy::expect_used)]
1154 self.ty.as_ref().expect("by INVARIANT on self.ty")
1155 }
1156}
1157
1158impl ActionEntityUID<InternalName> {
1159 pub fn ty(&self) -> &InternalName {
1161 #[allow(clippy::expect_used)]
1163 self.ty.as_ref().expect("by INVARIANT on self.ty")
1164 }
1165}
1166
1167impl From<ActionEntityUID<Name>> for EntityUID {
1168 fn from(aeuid: ActionEntityUID<Name>) -> Self {
1169 EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
1170 }
1171}
1172
1173impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
1174 type Error = <InternalName as TryInto<Name>>::Error;
1175 fn try_from(
1176 aeuid: ActionEntityUID<InternalName>,
1177 ) -> std::result::Result<Self, <InternalName as TryInto<Name>>::Error> {
1178 let ty = Name::try_from(aeuid.ty().clone())?;
1179 #[cfg(feature = "extended-schema")]
1180 let loc = aeuid.loc;
1181 #[cfg(not(feature = "extended-schema"))]
1182 let loc = None;
1183 Ok(EntityUID::from_components(
1184 ty.into(),
1185 Eid::new(aeuid.id),
1186 loc,
1187 ))
1188 }
1189}
1190
1191impl From<EntityUID> for ActionEntityUID<Name> {
1192 fn from(euid: EntityUID) -> Self {
1193 let (ty, id) = euid.components();
1194 ActionEntityUID {
1195 ty: Some(ty.into()),
1196 id: <Eid as AsRef<SmolStr>>::as_ref(&id).clone(),
1197 #[cfg(feature = "extended-schema")]
1198 loc: None,
1199 }
1200 }
1201}
1202
1203#[derive(Educe, Debug, Clone, Serialize)]
1210#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1211#[serde(untagged)]
1216#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1217#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1218pub enum Type<N> {
1219 Type {
1223 #[serde(flatten)]
1225 ty: TypeVariant<N>,
1226 #[serde(skip)]
1232 #[educe(PartialEq(ignore))]
1233 #[educe(PartialOrd(ignore))]
1234 loc: Option<Loc>,
1235 },
1236 CommonTypeRef {
1242 #[serde(rename = "type")]
1247 type_name: N,
1248 #[serde(skip)]
1254 #[educe(PartialEq(ignore))]
1255 #[educe(PartialOrd(ignore))]
1256 loc: Option<Loc>,
1257 },
1258}
1259
1260impl<N> Type<N> {
1261 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
1264 match self {
1265 Type::Type {
1266 ty: TypeVariant::Record(RecordType { attributes, .. }),
1267 ..
1268 } => attributes
1269 .iter()
1270 .map(|(_, ty)| ty.ty.common_type_references())
1271 .fold(Box::new(std::iter::empty()), |it, tys| {
1272 Box::new(it.chain(tys))
1273 }),
1274 Type::Type {
1275 ty: TypeVariant::Set { element },
1276 ..
1277 } => element.common_type_references(),
1278 Type::Type {
1279 ty: TypeVariant::EntityOrCommon { type_name },
1280 ..
1281 } => Box::new(std::iter::once(type_name)),
1282 Type::CommonTypeRef { type_name, .. } => Box::new(std::iter::once(type_name)),
1283 _ => Box::new(std::iter::empty()),
1284 }
1285 }
1286
1287 pub fn is_extension(&self) -> Option<bool> {
1293 match self {
1294 Self::Type {
1295 ty: TypeVariant::Extension { .. },
1296 ..
1297 } => Some(true),
1298 Self::Type {
1299 ty: TypeVariant::Set { element },
1300 ..
1301 } => element.is_extension(),
1302 Self::Type {
1303 ty: TypeVariant::Record(RecordType { attributes, .. }),
1304 ..
1305 } => attributes
1306 .values()
1307 .try_fold(false, |a, e| match e.ty.is_extension() {
1308 Some(true) => Some(true),
1309 Some(false) => Some(a),
1310 None => None,
1311 }),
1312 Self::Type { .. } => Some(false),
1313 Self::CommonTypeRef { .. } => None,
1314 }
1315 }
1316
1317 pub fn is_empty_record(&self) -> bool {
1320 match self {
1321 Self::Type {
1322 ty: TypeVariant::Record(rty),
1323 ..
1324 } => rty.is_empty_record(),
1325 _ => false,
1326 }
1327 }
1328
1329 pub fn loc(&self) -> Option<&Loc> {
1331 match self {
1332 Self::Type { loc, .. } => loc.as_ref(),
1333 Self::CommonTypeRef { loc, .. } => loc.as_ref(),
1334 }
1335 }
1336
1337 pub fn with_loc(self, new_loc: Option<Loc>) -> Self {
1339 match self {
1340 Self::Type { ty, loc: _loc } => Self::Type { ty, loc: new_loc },
1341 Self::CommonTypeRef {
1342 type_name,
1343 loc: _loc,
1344 } => Self::CommonTypeRef {
1345 type_name,
1346 loc: new_loc,
1347 },
1348 }
1349 }
1350}
1351
1352impl Type<RawName> {
1353 pub fn conditionally_qualify_type_references(
1355 self,
1356 ns: Option<&InternalName>,
1357 ) -> Type<ConditionalName> {
1358 match self {
1359 Self::Type { ty, loc } => Type::Type {
1360 ty: ty.conditionally_qualify_type_references(ns),
1361 loc,
1362 },
1363 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1364 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
1365 loc,
1366 },
1367 }
1368 }
1369
1370 fn into_n<N: From<RawName>>(self) -> Type<N> {
1371 match self {
1372 Self::Type { ty, loc } => Type::Type {
1373 ty: ty.into_n(),
1374 loc,
1375 },
1376 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1377 type_name: type_name.into(),
1378 loc,
1379 },
1380 }
1381 }
1382}
1383
1384impl Type<ConditionalName> {
1385 pub fn fully_qualify_type_references(
1391 self,
1392 all_defs: &AllDefs,
1393 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1394 match self {
1395 Self::Type { ty, loc } => Ok(Type::Type {
1396 ty: ty.fully_qualify_type_references(all_defs)?,
1397 loc,
1398 }),
1399 Self::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef {
1400 type_name: type_name.resolve(all_defs)?,
1401 loc,
1402 }),
1403 }
1404 }
1405}
1406
1407impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1408 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1409 where
1410 D: serde::Deserializer<'de>,
1411 {
1412 deserializer.deserialize_any(TypeVisitor {
1413 _phantom: PhantomData,
1414 })
1415 }
1416}
1417
1418#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1420#[serde(field_identifier, rename_all = "camelCase")]
1421enum TypeFields {
1422 Type,
1423 Element,
1424 Attributes,
1425 AdditionalAttributes,
1426 Name,
1427}
1428
1429macro_rules! type_field_name {
1433 (Type) => {
1434 "type"
1435 };
1436 (Element) => {
1437 "element"
1438 };
1439 (Attributes) => {
1440 "attributes"
1441 };
1442 (AdditionalAttributes) => {
1443 "additionalAttributes"
1444 };
1445 (Name) => {
1446 "name"
1447 };
1448}
1449
1450impl TypeFields {
1451 fn as_str(&self) -> &'static str {
1452 match self {
1453 TypeFields::Type => type_field_name!(Type),
1454 TypeFields::Element => type_field_name!(Element),
1455 TypeFields::Attributes => type_field_name!(Attributes),
1456 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1457 TypeFields::Name => type_field_name!(Name),
1458 }
1459 }
1460}
1461
1462#[derive(Debug, Deserialize)]
1467struct AttributesTypeMap(
1468 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1469 BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1470);
1471
1472struct TypeVisitor<N> {
1473 _phantom: PhantomData<N>,
1474}
1475
1476impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1477 type Value = Type<N>;
1478
1479 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1480 formatter.write_str("builtin type or reference to type defined in commonTypes")
1481 }
1482
1483 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1484 where
1485 M: MapAccess<'de>,
1486 {
1487 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1488
1489 let mut type_name: Option<SmolStr> = None;
1490 let mut element: Option<Type<N>> = None;
1491 let mut attributes: Option<AttributesTypeMap> = None;
1492 let mut additional_attributes: Option<bool> = None;
1493 let mut name: Option<SmolStr> = None;
1494
1495 while let Some(key) = map.next_key()? {
1499 match key {
1500 TypeField => {
1501 if type_name.is_some() {
1502 return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1503 }
1504 type_name = Some(map.next_value()?);
1505 }
1506 Element => {
1507 if element.is_some() {
1508 return Err(serde::de::Error::duplicate_field(Element.as_str()));
1509 }
1510 element = Some(map.next_value()?);
1511 }
1512 Attributes => {
1513 if attributes.is_some() {
1514 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1515 }
1516 attributes = Some(map.next_value()?);
1517 }
1518 AdditionalAttributes => {
1519 if additional_attributes.is_some() {
1520 return Err(serde::de::Error::duplicate_field(
1521 AdditionalAttributes.as_str(),
1522 ));
1523 }
1524 additional_attributes = Some(map.next_value()?);
1525 }
1526 Name => {
1527 if name.is_some() {
1528 return Err(serde::de::Error::duplicate_field(Name.as_str()));
1529 }
1530 name = Some(map.next_value()?);
1531 }
1532 }
1533 }
1534
1535 Self::build_schema_type::<M>(
1536 type_name.as_ref(),
1537 element,
1538 attributes,
1539 additional_attributes,
1540 name,
1541 )
1542 }
1543}
1544
1545impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1546 fn build_schema_type<M>(
1551 type_name: Option<&SmolStr>,
1552 element: Option<Type<N>>,
1553 attributes: Option<AttributesTypeMap>,
1554 additional_attributes: Option<bool>,
1555 name: Option<SmolStr>,
1556 ) -> std::result::Result<Type<N>, M::Error>
1557 where
1558 M: MapAccess<'de>,
1559 {
1560 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1561 let mut remaining_fields = [
1563 (TypeField, type_name.is_some()),
1564 (Element, element.is_some()),
1565 (Attributes, attributes.is_some()),
1566 (AdditionalAttributes, additional_attributes.is_some()),
1567 (Name, name.is_some()),
1568 ]
1569 .into_iter()
1570 .filter(|(_, present)| *present)
1571 .map(|(field, _)| field)
1572 .collect::<HashSet<_>>();
1573
1574 match type_name {
1575 Some(s) => {
1576 remaining_fields.remove(&TypeField);
1578 let error_if_fields = |fs: &[TypeFields],
1581 expected: &'static [&'static str]|
1582 -> std::result::Result<(), M::Error> {
1583 for f in fs {
1584 if remaining_fields.contains(f) {
1585 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1586 }
1587 }
1588 Ok(())
1589 };
1590 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1591 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1592 };
1593 match s.as_str() {
1594 "String" => {
1595 error_if_any_fields()?;
1596 Ok(Type::Type {
1597 ty: TypeVariant::String,
1598 loc: None,
1599 })
1600 }
1601 "Long" => {
1602 error_if_any_fields()?;
1603 Ok(Type::Type {
1604 ty: TypeVariant::Long,
1605 loc: None,
1606 })
1607 }
1608 "Boolean" => {
1609 error_if_any_fields()?;
1610 Ok(Type::Type {
1611 ty: TypeVariant::Boolean,
1612 loc: None,
1613 })
1614 }
1615 "Set" => {
1616 error_if_fields(
1617 &[Attributes, AdditionalAttributes, Name],
1618 &[type_field_name!(Element)],
1619 )?;
1620
1621 match element {
1622 Some(element) => Ok(Type::Type {
1623 ty: TypeVariant::Set {
1624 element: Box::new(element),
1625 },
1626 loc: None,
1627 }),
1628 None => Err(serde::de::Error::missing_field(Element.as_str())),
1629 }
1630 }
1631 "Record" => {
1632 error_if_fields(
1633 &[Element, Name],
1634 &[
1635 type_field_name!(Attributes),
1636 type_field_name!(AdditionalAttributes),
1637 ],
1638 )?;
1639
1640 if let Some(attributes) = attributes {
1641 let additional_attributes =
1642 additional_attributes.unwrap_or_else(partial_schema_default);
1643 Ok(Type::Type {
1644 ty: TypeVariant::Record(RecordType {
1645 attributes: attributes
1646 .0
1647 .into_iter()
1648 .map(
1649 |(
1650 k,
1651 TypeOfAttribute {
1652 ty,
1653 required,
1654 annotations,
1655 #[cfg(feature = "extended-schema")]
1656 loc,
1657 },
1658 )| {
1659 (
1660 k,
1661 TypeOfAttribute {
1662 ty: ty.into_n(),
1663 required,
1664 annotations,
1665 #[cfg(feature = "extended-schema")]
1666 loc,
1667 },
1668 )
1669 },
1670 )
1671 .collect(),
1672 additional_attributes,
1673 }),
1674 loc: None,
1675 })
1676 } else {
1677 Err(serde::de::Error::missing_field(Attributes.as_str()))
1678 }
1679 }
1680 "Entity" => {
1681 error_if_fields(
1682 &[Element, Attributes, AdditionalAttributes],
1683 &[type_field_name!(Name)],
1684 )?;
1685 match name {
1686 Some(name) => Ok(Type::Type {
1687 ty: TypeVariant::Entity {
1688 name: RawName::from_normalized_str(&name)
1689 .map_err(|err| {
1690 serde::de::Error::custom(format!(
1691 "invalid entity type `{name}`: {err}"
1692 ))
1693 })?
1694 .into(),
1695 },
1696 loc: None,
1697 }),
1698 None => Err(serde::de::Error::missing_field(Name.as_str())),
1699 }
1700 }
1701 "EntityOrCommon" => {
1702 error_if_fields(
1703 &[Element, Attributes, AdditionalAttributes],
1704 &[type_field_name!(Name)],
1705 )?;
1706 match name {
1707 Some(name) => Ok(Type::Type {
1708 ty: TypeVariant::EntityOrCommon {
1709 type_name: RawName::from_normalized_str(&name)
1710 .map_err(|err| {
1711 serde::de::Error::custom(format!(
1712 "invalid entity or common type `{name}`: {err}"
1713 ))
1714 })?
1715 .into(),
1716 },
1717 loc: None,
1718 }),
1719 None => Err(serde::de::Error::missing_field(Name.as_str())),
1720 }
1721 }
1722 "Extension" => {
1723 error_if_fields(
1724 &[Element, Attributes, AdditionalAttributes],
1725 &[type_field_name!(Name)],
1726 )?;
1727
1728 match name {
1729 Some(name) => Ok(Type::Type {
1730 ty: TypeVariant::Extension {
1731 name: UnreservedId::from_normalized_str(&name).map_err(
1732 |err| {
1733 serde::de::Error::custom(format!(
1734 "invalid extension type `{name}`: {err}"
1735 ))
1736 },
1737 )?,
1738 },
1739 loc: None,
1740 }),
1741 None => Err(serde::de::Error::missing_field(Name.as_str())),
1742 }
1743 }
1744 type_name => {
1745 error_if_any_fields()?;
1746 Ok(Type::CommonTypeRef {
1747 type_name: N::from(RawName::from_normalized_str(type_name).map_err(
1748 |err| {
1749 serde::de::Error::custom(format!(
1750 "invalid common type `{type_name}`: {err}"
1751 ))
1752 },
1753 )?),
1754 loc: None,
1755 })
1756 }
1757 }
1758 }
1759 None => Err(serde::de::Error::missing_field(TypeField.as_str())),
1760 }
1761 }
1762}
1763
1764impl<N> From<TypeVariant<N>> for Type<N> {
1765 fn from(ty: TypeVariant<N>) -> Self {
1766 Self::Type { ty, loc: None }
1767 }
1768}
1769
1770#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1776#[educe(PartialEq, Eq, PartialOrd, Ord)]
1777#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1778#[serde(rename_all = "camelCase")]
1779#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1780#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1781pub struct RecordType<N> {
1782 pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
1784 #[serde(default = "partial_schema_default")]
1786 #[serde(skip_serializing_if = "is_partial_schema_default")]
1787 pub additional_attributes: bool,
1788}
1789
1790impl<N> Default for RecordType<N> {
1791 fn default() -> Self {
1792 Self {
1793 attributes: BTreeMap::new(),
1794 additional_attributes: partial_schema_default(),
1795 }
1796 }
1797}
1798
1799impl<N> RecordType<N> {
1800 pub fn is_empty_record(&self) -> bool {
1802 self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
1803 }
1804}
1805
1806impl RecordType<RawName> {
1807 pub fn conditionally_qualify_type_references(
1809 self,
1810 ns: Option<&InternalName>,
1811 ) -> RecordType<ConditionalName> {
1812 RecordType {
1813 attributes: self
1814 .attributes
1815 .into_iter()
1816 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
1817 .collect(),
1818 additional_attributes: self.additional_attributes,
1819 }
1820 }
1821}
1822
1823impl RecordType<ConditionalName> {
1824 pub fn fully_qualify_type_references(
1831 self,
1832 all_defs: &AllDefs,
1833 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1834 Ok(RecordType {
1835 attributes: self
1836 .attributes
1837 .into_iter()
1838 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
1839 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1840 additional_attributes: self.additional_attributes,
1841 })
1842 }
1843}
1844
1845#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1853#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1854#[serde(tag = "type")]
1855#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1856#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1857#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1858pub enum TypeVariant<N> {
1859 String,
1861 Long,
1863 Boolean,
1865 Set {
1867 element: Box<Type<N>>,
1869 },
1870 Record(RecordType<N>),
1872 Entity {
1874 name: N,
1879 },
1880 EntityOrCommon {
1882 #[serde(rename = "name")]
1903 type_name: N,
1904 },
1905 Extension {
1907 name: UnreservedId,
1909 },
1910}
1911
1912impl TypeVariant<RawName> {
1913 pub fn conditionally_qualify_type_references(
1915 self,
1916 ns: Option<&InternalName>,
1917 ) -> TypeVariant<ConditionalName> {
1918 match self {
1919 Self::Boolean => TypeVariant::Boolean,
1920 Self::Long => TypeVariant::Long,
1921 Self::String => TypeVariant::String,
1922 Self::Extension { name } => TypeVariant::Extension { name },
1923 Self::Entity { name } => TypeVariant::Entity {
1924 name: name.conditionally_qualify_with(ns, ReferenceType::Entity), },
1926 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1927 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
1928 },
1929 Self::Set { element } => TypeVariant::Set {
1930 element: Box::new(element.conditionally_qualify_type_references(ns)),
1931 },
1932 Self::Record(RecordType {
1933 attributes,
1934 additional_attributes,
1935 }) => TypeVariant::Record(RecordType {
1936 attributes: BTreeMap::from_iter(attributes.into_iter().map(
1937 |(
1938 attr,
1939 TypeOfAttribute {
1940 ty,
1941 required,
1942 annotations,
1943 #[cfg(feature = "extended-schema")]
1944 loc,
1945 },
1946 )| {
1947 (
1948 attr,
1949 TypeOfAttribute {
1950 ty: ty.conditionally_qualify_type_references(ns),
1951 required,
1952 annotations,
1953 #[cfg(feature = "extended-schema")]
1954 loc,
1955 },
1956 )
1957 },
1958 )),
1959 additional_attributes,
1960 }),
1961 }
1962 }
1963
1964 fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
1965 match self {
1966 Self::Boolean => TypeVariant::Boolean,
1967 Self::Long => TypeVariant::Long,
1968 Self::String => TypeVariant::String,
1969 Self::Entity { name } => TypeVariant::Entity { name: name.into() },
1970 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1971 type_name: type_name.into(),
1972 },
1973 Self::Record(RecordType {
1974 attributes,
1975 additional_attributes,
1976 }) => TypeVariant::Record(RecordType {
1977 attributes: attributes
1978 .into_iter()
1979 .map(|(k, v)| (k, v.into_n()))
1980 .collect(),
1981 additional_attributes,
1982 }),
1983 Self::Set { element } => TypeVariant::Set {
1984 element: Box::new(element.into_n()),
1985 },
1986 Self::Extension { name } => TypeVariant::Extension { name },
1987 }
1988 }
1989}
1990
1991impl TypeVariant<ConditionalName> {
1992 pub fn fully_qualify_type_references(
1999 self,
2000 all_defs: &AllDefs,
2001 ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
2002 match self {
2003 Self::Boolean => Ok(TypeVariant::Boolean),
2004 Self::Long => Ok(TypeVariant::Long),
2005 Self::String => Ok(TypeVariant::String),
2006 Self::Extension { name } => Ok(TypeVariant::Extension { name }),
2007 Self::Entity { name } => Ok(TypeVariant::Entity {
2008 name: name.resolve(all_defs)?,
2009 }),
2010 Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
2011 type_name: type_name.resolve(all_defs)?,
2012 }),
2013 Self::Set { element } => Ok(TypeVariant::Set {
2014 element: Box::new(element.fully_qualify_type_references(all_defs)?),
2015 }),
2016 Self::Record(RecordType {
2017 attributes,
2018 additional_attributes,
2019 }) => Ok(TypeVariant::Record(RecordType {
2020 attributes: attributes
2021 .into_iter()
2022 .map(
2023 |(
2024 attr,
2025 TypeOfAttribute {
2026 ty,
2027 required,
2028 annotations,
2029 #[cfg(feature = "extended-schema")]
2030 loc,
2031 },
2032 )| {
2033 Ok((
2034 attr,
2035 TypeOfAttribute {
2036 ty: ty.fully_qualify_type_references(all_defs)?,
2037 required,
2038 annotations,
2039 #[cfg(feature = "extended-schema")]
2040 loc,
2041 },
2042 ))
2043 },
2044 )
2045 .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
2046 additional_attributes,
2047 })),
2048 }
2049 }
2050}
2051
2052#[allow(
2054 clippy::trivially_copy_pass_by_ref,
2055 reason = "Reference required to work with derived serde serialize implementation"
2056)]
2057fn is_partial_schema_default(b: &bool) -> bool {
2058 *b == partial_schema_default()
2059}
2060
2061#[cfg(feature = "arbitrary")]
2062#[allow(clippy::panic)]
2064impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
2065 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
2066 use std::collections::BTreeSet;
2067
2068 Ok(Type::Type {
2069 ty: match u.int_in_range::<u8>(1..=8)? {
2070 1 => TypeVariant::String,
2071 2 => TypeVariant::Long,
2072 3 => TypeVariant::Boolean,
2073 4 => TypeVariant::Set {
2074 element: Box::new(u.arbitrary()?),
2075 },
2076 5 => {
2077 let attributes = {
2078 let attr_names: BTreeSet<String> = u.arbitrary()?;
2079 attr_names
2080 .into_iter()
2081 .map(|attr_name| {
2082 Ok((attr_name.into(), u.arbitrary::<TypeOfAttribute<RawName>>()?))
2083 })
2084 .collect::<arbitrary::Result<_>>()?
2085 };
2086 TypeVariant::Record(RecordType {
2087 attributes,
2088 additional_attributes: u.arbitrary()?,
2089 })
2090 }
2091 6 => TypeVariant::Entity {
2092 name: u.arbitrary()?,
2093 },
2094 7 => TypeVariant::Extension {
2095 #[allow(clippy::unwrap_used)]
2097 name: "ipaddr".parse().unwrap(),
2098 },
2099 8 => TypeVariant::Extension {
2100 #[allow(clippy::unwrap_used)]
2102 name: "decimal".parse().unwrap(),
2103 },
2104 n => panic!("bad index: {n}"),
2105 },
2106 loc: None,
2107 })
2108 }
2109 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
2110 (1, None) }
2112}
2113
2114#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2133#[educe(PartialEq, Eq, PartialOrd, Ord)]
2134#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2135pub struct TypeOfAttribute<N> {
2136 #[serde(flatten)]
2138 pub ty: Type<N>,
2139 #[serde(default)]
2141 #[serde(skip_serializing_if = "Annotations::is_empty")]
2142 pub annotations: Annotations,
2143 #[serde(default = "record_attribute_required_default")]
2145 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
2146 pub required: bool,
2147
2148 #[cfg(feature = "extended-schema")]
2150 #[educe(Eq(ignore))]
2151 #[serde(skip)]
2152 pub loc: Option<Loc>,
2153}
2154
2155impl TypeOfAttribute<RawName> {
2156 fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
2157 TypeOfAttribute {
2158 ty: self.ty.into_n(),
2159
2160 required: self.required,
2161 annotations: self.annotations,
2162 #[cfg(feature = "extended-schema")]
2163 loc: self.loc,
2164 }
2165 }
2166
2167 pub fn conditionally_qualify_type_references(
2169 self,
2170 ns: Option<&InternalName>,
2171 ) -> TypeOfAttribute<ConditionalName> {
2172 TypeOfAttribute {
2173 ty: self.ty.conditionally_qualify_type_references(ns),
2174 required: self.required,
2175 annotations: self.annotations,
2176 #[cfg(feature = "extended-schema")]
2177 loc: self.loc,
2178 }
2179 }
2180}
2181
2182impl TypeOfAttribute<ConditionalName> {
2183 pub fn fully_qualify_type_references(
2190 self,
2191 all_defs: &AllDefs,
2192 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
2193 Ok(TypeOfAttribute {
2194 ty: self.ty.fully_qualify_type_references(all_defs)?,
2195 required: self.required,
2196 annotations: self.annotations,
2197 #[cfg(feature = "extended-schema")]
2198 loc: self.loc,
2199 })
2200 }
2201}
2202
2203#[cfg(feature = "arbitrary")]
2204impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
2205 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2206 Ok(Self {
2207 ty: u.arbitrary::<Type<RawName>>()?,
2208 required: u.arbitrary()?,
2209 annotations: u.arbitrary()?,
2210 #[cfg(feature = "extended-schema")]
2211 loc: None,
2212 })
2213 }
2214
2215 fn size_hint(depth: usize) -> (usize, Option<usize>) {
2216 arbitrary::size_hint::and_all(&[
2217 <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
2218 <bool as arbitrary::Arbitrary>::size_hint(depth),
2219 <crate::est::Annotations as arbitrary::Arbitrary>::size_hint(depth),
2220 ])
2221 }
2222}
2223
2224#[allow(
2226 clippy::trivially_copy_pass_by_ref,
2227 reason = "Reference required to work with derived serde serialize implementation"
2228)]
2229fn is_record_attribute_required_default(b: &bool) -> bool {
2230 *b == record_attribute_required_default()
2231}
2232
2233fn partial_schema_default() -> bool {
2236 false
2237}
2238
2239fn record_attribute_required_default() -> bool {
2241 true
2242}
2243
2244#[cfg(test)]
2245mod test {
2246 use crate::{
2247 extensions::Extensions,
2248 test_utils::{expect_err, ExpectedErrorMessageBuilder},
2249 };
2250 use cool_asserts::assert_matches;
2251
2252 use crate::validator::ValidatorSchema;
2253
2254 use super::*;
2255
2256 #[test]
2257 fn test_entity_type_parser1() {
2258 let user = r#"
2259 {
2260 "memberOfTypes" : ["UserGroup"]
2261 }
2262 "#;
2263 assert_matches!(serde_json::from_str::<EntityType<RawName>>(user), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2264 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
2265 assert_eq!(
2266 et.shape,
2267 AttributesOrContext(Type::Type {
2268 ty: TypeVariant::Record(RecordType {
2269 attributes: BTreeMap::new(),
2270 additional_attributes: false
2271 }),
2272 loc: None
2273 }),
2274 );});
2275 }
2276
2277 #[test]
2278 fn test_entity_type_parser2() {
2279 let src = r#"
2280 { }
2281 "#;
2282 assert_matches!(serde_json::from_str::<EntityType<RawName>>(src), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2283 assert_eq!(et.member_of_types.len(), 0);
2284 assert_eq!(
2285 et.shape,
2286 AttributesOrContext(Type::Type {
2287 ty: TypeVariant::Record(RecordType {
2288 attributes: BTreeMap::new(),
2289 additional_attributes: false
2290 }),
2291 loc: None
2292 }),
2293 );});
2294 }
2295
2296 #[test]
2297 fn test_action_type_parser1() {
2298 let src = r#"
2299 {
2300 "appliesTo" : {
2301 "resourceTypes": ["Album"],
2302 "principalTypes": ["User"]
2303 },
2304 "memberOf": [{"id": "readWrite"}]
2305 }
2306 "#;
2307 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2308 let spec = ApplySpec {
2309 resource_types: vec!["Album".parse().unwrap()],
2310 principal_types: vec!["User".parse().unwrap()],
2311 context: AttributesOrContext::default(),
2312 };
2313 assert_eq!(at.applies_to, Some(spec));
2314 assert_eq!(
2315 at.member_of,
2316 Some(vec![ActionEntityUID {
2317 ty: None,
2318 id: "readWrite".into(),
2319 #[cfg(feature = "extended-schema")]
2320 loc: None
2321 }])
2322 );
2323 }
2324
2325 #[test]
2326 fn test_action_type_parser2() {
2327 let src = r#"
2328 { }
2329 "#;
2330 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2331 assert_eq!(at.applies_to, None);
2332 assert!(at.member_of.is_none());
2333 }
2334
2335 #[test]
2336 fn test_schema_file_parser() {
2337 let src = serde_json::json!(
2338 {
2339 "entityTypes": {
2340
2341 "User": {
2342 "memberOfTypes": ["UserGroup"]
2343 },
2344 "Photo": {
2345 "memberOfTypes": ["Album", "Account"]
2346 },
2347
2348 "Album": {
2349 "memberOfTypes": ["Album", "Account"]
2350 },
2351 "Account": { },
2352 "UserGroup": { }
2353 },
2354
2355 "actions": {
2356 "readOnly": { },
2357 "readWrite": { },
2358 "createAlbum": {
2359 "appliesTo" : {
2360 "resourceTypes": ["Account", "Album"],
2361 "principalTypes": ["User"]
2362 },
2363 "memberOf": [{"id": "readWrite"}]
2364 },
2365 "addPhotoToAlbum": {
2366 "appliesTo" : {
2367 "resourceTypes": ["Album"],
2368 "principalTypes": ["User"]
2369 },
2370 "memberOf": [{"id": "readWrite"}]
2371 },
2372 "viewPhoto": {
2373 "appliesTo" : {
2374 "resourceTypes": ["Photo"],
2375 "principalTypes": ["User"]
2376 },
2377 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2378 },
2379 "viewComments": {
2380 "appliesTo" : {
2381 "resourceTypes": ["Photo"],
2382 "principalTypes": ["User"]
2383 },
2384 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2385 }
2386 }
2387 });
2388 let schema_file: NamespaceDefinition<RawName> =
2389 serde_json::from_value(src).expect("Parse Error");
2390
2391 assert_eq!(schema_file.entity_types.len(), 5);
2392 assert_eq!(schema_file.actions.len(), 6);
2393 }
2394
2395 #[test]
2396 fn test_parse_namespaces() {
2397 let src = r#"
2398 {
2399 "foo::foo::bar::baz": {
2400 "entityTypes": {},
2401 "actions": {}
2402 }
2403 }"#;
2404 let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
2405 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
2406 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
2407 }
2408
2409 #[test]
2410 #[should_panic(expected = "unknown field `requiredddddd`")]
2411 fn test_schema_file_with_misspelled_required() {
2412 let src = serde_json::json!(
2413 {
2414 "entityTypes": {
2415 "User": {
2416 "shape": {
2417 "type": "Record",
2418 "attributes": {
2419 "favorite": {
2420 "type": "Entity",
2421 "name": "Photo",
2422 "requiredddddd": false
2423 }
2424 }
2425 }
2426 }
2427 },
2428 "actions": {}
2429 });
2430 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2431 println!("{:#?}", schema);
2432 }
2433
2434 #[test]
2435 #[should_panic(expected = "unknown field `nameeeeee`")]
2436 fn test_schema_file_with_misspelled_field() {
2437 let src = serde_json::json!(
2438 {
2439 "entityTypes": {
2440 "User": {
2441 "shape": {
2442 "type": "Record",
2443 "attributes": {
2444 "favorite": {
2445 "type": "Entity",
2446 "nameeeeee": "Photo",
2447 }
2448 }
2449 }
2450 }
2451 },
2452 "actions": {}
2453 });
2454 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2455 println!("{:#?}", schema);
2456 }
2457
2458 #[test]
2459 #[should_panic(expected = "unknown field `extra`")]
2460 fn test_schema_file_with_extra_field() {
2461 let src = serde_json::json!(
2462 {
2463 "entityTypes": {
2464 "User": {
2465 "shape": {
2466 "type": "Record",
2467 "attributes": {
2468 "favorite": {
2469 "type": "Entity",
2470 "name": "Photo",
2471 "extra": "Should not exist"
2472 }
2473 }
2474 }
2475 }
2476 },
2477 "actions": {}
2478 });
2479 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2480 println!("{:#?}", schema);
2481 }
2482
2483 #[test]
2484 #[should_panic(expected = "unknown field `memberOfTypes`")]
2485 fn test_schema_file_with_misplaced_field() {
2486 let src = serde_json::json!(
2487 {
2488 "entityTypes": {
2489 "User": {
2490 "shape": {
2491 "memberOfTypes": [],
2492 "type": "Record",
2493 "attributes": {
2494 "favorite": {
2495 "type": "Entity",
2496 "name": "Photo",
2497 }
2498 }
2499 }
2500 }
2501 },
2502 "actions": {}
2503 });
2504 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2505 println!("{:#?}", schema);
2506 }
2507
2508 #[test]
2509 fn schema_file_with_missing_field() {
2510 let src = serde_json::json!(
2511 {
2512 "": {
2513 "entityTypes": {
2514 "User": {
2515 "shape": {
2516 "type": "Record",
2517 "attributes": {
2518 "favorite": {
2519 "type": "Entity",
2520 }
2521 }
2522 }
2523 }
2524 },
2525 "actions": {}
2526 }
2527 });
2528 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2529 assert_matches!(schema, Err(e) => {
2530 expect_err(
2531 &src,
2532 &miette::Report::new(e),
2533 &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2534 .build());
2535 });
2536 }
2537
2538 #[test]
2539 #[should_panic(expected = "missing field `type`")]
2540 fn schema_file_with_missing_type() {
2541 let src = serde_json::json!(
2542 {
2543 "entityTypes": {
2544 "User": {
2545 "shape": { }
2546 }
2547 },
2548 "actions": {}
2549 });
2550 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2551 println!("{:#?}", schema);
2552 }
2553
2554 #[test]
2555 fn schema_file_unexpected_malformed_attribute() {
2556 let src = serde_json::json!(
2557 { "": {
2558 "entityTypes": {
2559 "User": {
2560 "shape": {
2561 "type": "Record",
2562 "attributes": {
2563 "a": {
2564 "type": "Long",
2565 "attributes": {
2566 "b": {"foo": "bar"}
2567 }
2568 }
2569 }
2570 }
2571 }
2572 },
2573 "actions": {}
2574 }});
2575 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2576 assert_matches!(schema, Err(e) => {
2577 expect_err(
2578 "",
2579 &miette::Report::new(e),
2580 &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2581 );
2582 });
2583 }
2584
2585 #[test]
2586 fn error_in_nested_attribute_fails_fast_top_level_attr() {
2587 let src = serde_json::json!(
2588 {
2589 "": {
2590 "entityTypes": {
2591 "User": {
2592 "shape": {
2593 "type": "Record",
2594 "attributes": {
2595 "foo": {
2596 "type": "Record",
2597 "element": { "type": "Long" }
2599 },
2600 "bar": { "type": "Long" }
2601 }
2602 }
2603 }
2604 },
2605 "actions": {}
2606 }
2607 }
2608 );
2609
2610 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2611 assert_matches!(schema, Err(e) => {
2612 expect_err(
2613 "",
2614 &miette::Report::new(e),
2615 &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2616 );
2617 });
2618 }
2619
2620 #[test]
2621 fn error_in_nested_attribute_fails_fast_nested_attr() {
2622 let src = serde_json::json!(
2623 { "": {
2624 "entityTypes": {
2625 "a": {
2626 "shape": {
2627 "type": "Record",
2628 "attributes": {
2629 "foo": { "type": "Entity", "name": "b" },
2630 "baz": { "type": "Record",
2631 "attributes": {
2632 "z": "Boolean"
2634 }
2635 }
2636 }
2637 }
2638 },
2639 "b": {}
2640 }
2641 } }
2642 );
2643
2644 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2645 assert_matches!(schema, Err(e) => {
2646 expect_err(
2647 "",
2648 &miette::Report::new(e),
2649 &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2650 );
2651 });
2652 }
2653
2654 #[test]
2655 fn missing_namespace() {
2656 let src = r#"
2657 {
2658 "entityTypes": { "User": { } },
2659 "actions": {}
2660 }"#;
2661 let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2662 assert_matches!(schema, Err(e) => {
2663 expect_err(
2664 src,
2665 &miette::Report::new(e),
2666 &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations` at line 3 column 35"#)
2667 .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2668 .build());
2669 });
2670 }
2671}
2672
2673#[cfg(test)]
2675mod strengthened_types {
2676 use cool_asserts::assert_matches;
2677
2678 use super::{
2679 ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
2680 };
2681
2682 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
2685 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
2686 }
2687
2688 #[test]
2689 fn invalid_namespace() {
2690 let src = serde_json::json!(
2691 {
2692 "\n" : {
2693 "entityTypes": {},
2694 "actions": {}
2695 }
2696 });
2697 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2698 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
2699
2700 let src = serde_json::json!(
2701 {
2702 "1" : {
2703 "entityTypes": {},
2704 "actions": {}
2705 }
2706 });
2707 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2708 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
2709
2710 let src = serde_json::json!(
2711 {
2712 "*1" : {
2713 "entityTypes": {},
2714 "actions": {}
2715 }
2716 });
2717 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2718 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
2719
2720 let src = serde_json::json!(
2721 {
2722 "::" : {
2723 "entityTypes": {},
2724 "actions": {}
2725 }
2726 });
2727 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2728 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
2729
2730 let src = serde_json::json!(
2731 {
2732 "A::" : {
2733 "entityTypes": {},
2734 "actions": {}
2735 }
2736 });
2737 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2738 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
2739 }
2740
2741 #[test]
2742 fn invalid_common_type() {
2743 let src = serde_json::json!(
2744 {
2745 "entityTypes": {},
2746 "actions": {},
2747 "commonTypes": {
2748 "" : {
2749 "type": "String"
2750 }
2751 }
2752 });
2753 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2754 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2755
2756 let src = serde_json::json!(
2757 {
2758 "entityTypes": {},
2759 "actions": {},
2760 "commonTypes": {
2761 "~" : {
2762 "type": "String"
2763 }
2764 }
2765 });
2766 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2767 assert_error_matches(schema, "invalid id `~`: invalid token");
2768
2769 let src = serde_json::json!(
2770 {
2771 "entityTypes": {},
2772 "actions": {},
2773 "commonTypes": {
2774 "A::B" : {
2775 "type": "String"
2776 }
2777 }
2778 });
2779 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2780 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2781 }
2782
2783 #[test]
2784 fn invalid_entity_type() {
2785 let src = serde_json::json!(
2786 {
2787 "entityTypes": {
2788 "": {}
2789 },
2790 "actions": {}
2791 });
2792 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2793 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2794
2795 let src = serde_json::json!(
2796 {
2797 "entityTypes": {
2798 "*": {}
2799 },
2800 "actions": {}
2801 });
2802 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2803 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
2804
2805 let src = serde_json::json!(
2806 {
2807 "entityTypes": {
2808 "A::B": {}
2809 },
2810 "actions": {}
2811 });
2812 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2813 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2814 }
2815
2816 #[test]
2817 fn invalid_member_of_types() {
2818 let src = serde_json::json!(
2819 {
2820 "memberOfTypes": [""]
2821 });
2822 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2823 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2824
2825 let src = serde_json::json!(
2826 {
2827 "memberOfTypes": ["*"]
2828 });
2829 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2830 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2831
2832 let src = serde_json::json!(
2833 {
2834 "memberOfTypes": ["A::"]
2835 });
2836 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2837 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2838
2839 let src = serde_json::json!(
2840 {
2841 "memberOfTypes": ["::A"]
2842 });
2843 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2844 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2845 }
2846
2847 #[test]
2848 fn invalid_apply_spec() {
2849 let src = serde_json::json!(
2850 {
2851 "resourceTypes": [""]
2852 });
2853 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2854 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2855
2856 let src = serde_json::json!(
2857 {
2858 "resourceTypes": ["*"]
2859 });
2860 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2861 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2862
2863 let src = serde_json::json!(
2864 {
2865 "resourceTypes": ["A::"]
2866 });
2867 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2868 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2869
2870 let src = serde_json::json!(
2871 {
2872 "resourceTypes": ["::A"]
2873 });
2874 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2875 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2876 }
2877
2878 #[test]
2879 fn invalid_schema_entity_types() {
2880 let src = serde_json::json!(
2881 {
2882 "type": "Entity",
2883 "name": ""
2884 });
2885 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2886 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
2887
2888 let src = serde_json::json!(
2889 {
2890 "type": "Entity",
2891 "name": "*"
2892 });
2893 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2894 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
2895
2896 let src = serde_json::json!(
2897 {
2898 "type": "Entity",
2899 "name": "::A"
2900 });
2901 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2902 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
2903
2904 let src = serde_json::json!(
2905 {
2906 "type": "Entity",
2907 "name": "A::"
2908 });
2909 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2910 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
2911 }
2912
2913 #[test]
2914 fn invalid_action_euid() {
2915 let src = serde_json::json!(
2916 {
2917 "id": "action",
2918 "type": ""
2919 });
2920 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2921 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2922
2923 let src = serde_json::json!(
2924 {
2925 "id": "action",
2926 "type": "*"
2927 });
2928 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2929 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2930
2931 let src = serde_json::json!(
2932 {
2933 "id": "action",
2934 "type": "Action::"
2935 });
2936 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2937 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
2938
2939 let src = serde_json::json!(
2940 {
2941 "id": "action",
2942 "type": "::Action"
2943 });
2944 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2945 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
2946 }
2947
2948 #[test]
2949 fn invalid_schema_common_types() {
2950 let src = serde_json::json!(
2951 {
2952 "type": ""
2953 });
2954 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2955 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
2956
2957 let src = serde_json::json!(
2958 {
2959 "type": "*"
2960 });
2961 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2962 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
2963
2964 let src = serde_json::json!(
2965 {
2966 "type": "::A"
2967 });
2968 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2969 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
2970
2971 let src = serde_json::json!(
2972 {
2973 "type": "A::"
2974 });
2975 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2976 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
2977 }
2978
2979 #[test]
2980 fn invalid_schema_extension_types() {
2981 let src = serde_json::json!(
2982 {
2983 "type": "Extension",
2984 "name": ""
2985 });
2986 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2987 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
2988
2989 let src = serde_json::json!(
2990 {
2991 "type": "Extension",
2992 "name": "*"
2993 });
2994 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2995 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
2996
2997 let src = serde_json::json!(
2998 {
2999 "type": "Extension",
3000 "name": "__cedar::decimal"
3001 });
3002 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3003 assert_error_matches(
3004 schema,
3005 "invalid extension type `__cedar::decimal`: unexpected token `::`",
3006 );
3007
3008 let src = serde_json::json!(
3009 {
3010 "type": "Extension",
3011 "name": "__cedar::"
3012 });
3013 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3014 assert_error_matches(
3015 schema,
3016 "invalid extension type `__cedar::`: unexpected token `::`",
3017 );
3018
3019 let src = serde_json::json!(
3020 {
3021 "type": "Extension",
3022 "name": "::__cedar"
3023 });
3024 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3025 assert_error_matches(
3026 schema,
3027 "invalid extension type `::__cedar`: unexpected token `::`",
3028 );
3029 }
3030}
3031
3032#[cfg(test)]
3034mod entity_tags {
3035 use super::*;
3036 use crate::test_utils::{expect_err, ExpectedErrorMessageBuilder};
3037 use cool_asserts::assert_matches;
3038 use serde_json::json;
3039
3040 #[track_caller]
3042 fn example_json_schema() -> serde_json::Value {
3043 json!({"": {
3044 "entityTypes": {
3045 "User" : {
3046 "shape" : {
3047 "type" : "Record",
3048 "attributes" : {
3049 "jobLevel" : {
3050 "type" : "Long"
3051 },
3052 }
3053 },
3054 "tags" : {
3055 "type" : "Set",
3056 "element": { "type": "String" }
3057 }
3058 },
3059 "Document" : {
3060 "shape" : {
3061 "type" : "Record",
3062 "attributes" : {
3063 "owner" : {
3064 "type" : "Entity",
3065 "name" : "User"
3066 },
3067 }
3068 },
3069 "tags" : {
3070 "type" : "Set",
3071 "element": { "type": "String" }
3072 }
3073 }
3074 },
3075 "actions": {}
3076 }})
3077 }
3078
3079 #[test]
3080 fn roundtrip() {
3081 let json = example_json_schema();
3082 let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
3083 let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
3084 assert_eq!(json, serialized_json_schema);
3085 }
3086
3087 #[test]
3088 fn basic() {
3089 let json = example_json_schema();
3090 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3091 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3092 assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3093 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });});
3095 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(doc), ..} => {
3096 assert_matches!(&doc.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3097 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });
3099 })})
3100 }
3101
3102 #[test]
3104 fn tag_type_is_common_type() {
3105 let json = json!({"": {
3106 "commonTypes": {
3107 "T": { "type": "String" },
3108 },
3109 "entityTypes": {
3110 "User" : {
3111 "shape" : {
3112 "type" : "Record",
3113 "attributes" : {
3114 "jobLevel" : {
3115 "type" : "Long"
3116 },
3117 }
3118 },
3119 "tags" : { "type" : "T" },
3120 },
3121 },
3122 "actions": {}
3123 }});
3124 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3125 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType {kind: EntityTypeKind::Standard(user), ..} => {
3126 assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name, .. }) => {
3127 assert_eq!(&format!("{type_name}"), "T");
3128 });
3129 })});
3130 }
3131
3132 #[test]
3134 fn tag_type_is_entity_type() {
3135 let json = json!({"": {
3136 "entityTypes": {
3137 "User" : {
3138 "shape" : {
3139 "type" : "Record",
3140 "attributes" : {
3141 "jobLevel" : {
3142 "type" : "Long"
3143 },
3144 }
3145 },
3146 "tags" : { "type" : "Entity", "name": "User" },
3147 },
3148 },
3149 "actions": {}
3150 }});
3151 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3152 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3153 assert_matches!(&user.tags, Some(Type::Type{ ty: TypeVariant::Entity{ name }, ..}) => {
3154 assert_eq!(&format!("{name}"), "User");
3155 });
3156 })});
3157 }
3158
3159 #[test]
3161 fn bad_tags() {
3162 let json = json!({"": {
3163 "entityTypes": {
3164 "User": {
3165 "shape": {
3166 "type": "Record",
3167 "attributes": {
3168 "jobLevel": {
3169 "type": "Long"
3170 },
3171 },
3172 "tags": { "type": "String" },
3173 }
3174 },
3175 },
3176 "actions": {}
3177 }});
3178 assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
3179 expect_err(
3180 &json,
3181 &miette::Report::new(e),
3182 &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
3183 .build(),
3184 );
3185 });
3186 }
3187}
3188
3189#[cfg(test)]
3191mod test_json_roundtrip {
3192 use super::*;
3193
3194 #[track_caller] fn roundtrip(schema: &Fragment<RawName>) {
3196 let json = serde_json::to_value(schema.clone()).unwrap();
3197 let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
3198 assert_eq!(schema, &new_schema);
3199 }
3200
3201 #[test]
3202 fn empty_namespace() {
3203 let fragment = Fragment(BTreeMap::from([(None, NamespaceDefinition::new([], []))]));
3204 roundtrip(&fragment);
3205 }
3206
3207 #[test]
3208 fn nonempty_namespace() {
3209 let fragment = Fragment(BTreeMap::from([(
3210 Some("a".parse().unwrap()),
3211 NamespaceDefinition::new([], []),
3212 )]));
3213 roundtrip(&fragment);
3214 }
3215
3216 #[test]
3217 fn nonempty_entity_types() {
3218 let fragment = Fragment(BTreeMap::from([(
3219 None,
3220 NamespaceDefinition::new(
3221 [(
3222 "a".parse().unwrap(),
3223 EntityType {
3224 kind: EntityTypeKind::Standard(StandardEntityType {
3225 member_of_types: vec!["a".parse().unwrap()],
3226 shape: AttributesOrContext(Type::Type {
3227 ty: TypeVariant::Record(RecordType {
3228 attributes: BTreeMap::new(),
3229 additional_attributes: false,
3230 }),
3231 loc: None,
3232 }),
3233 tags: None,
3234 }),
3235 annotations: Annotations::new(),
3236 loc: None,
3237 },
3238 )],
3239 [(
3240 "action".into(),
3241 ActionType {
3242 attributes: None,
3243 applies_to: Some(ApplySpec {
3244 resource_types: vec!["a".parse().unwrap()],
3245 principal_types: vec!["a".parse().unwrap()],
3246 context: AttributesOrContext(Type::Type {
3247 ty: TypeVariant::Record(RecordType {
3248 attributes: BTreeMap::new(),
3249 additional_attributes: false,
3250 }),
3251 loc: None,
3252 }),
3253 }),
3254 member_of: None,
3255 annotations: Annotations::new(),
3256 loc: None,
3257 #[cfg(feature = "extended-schema")]
3258 defn_loc: None,
3259 },
3260 )],
3261 ),
3262 )]));
3263 roundtrip(&fragment);
3264 }
3265
3266 #[test]
3267 fn multiple_namespaces() {
3268 let fragment = Fragment(BTreeMap::from([
3269 (
3270 Some("foo".parse().unwrap()),
3271 NamespaceDefinition::new(
3272 [(
3273 "a".parse().unwrap(),
3274 EntityType {
3275 kind: EntityTypeKind::Standard(StandardEntityType {
3276 member_of_types: vec!["a".parse().unwrap()],
3277 shape: AttributesOrContext(Type::Type {
3278 ty: TypeVariant::Record(RecordType {
3279 attributes: BTreeMap::new(),
3280 additional_attributes: false,
3281 }),
3282 loc: None,
3283 }),
3284 tags: None,
3285 }),
3286 annotations: Annotations::new(),
3287 loc: None,
3288 },
3289 )],
3290 [],
3291 ),
3292 ),
3293 (
3294 None,
3295 NamespaceDefinition::new(
3296 [],
3297 [(
3298 "action".into(),
3299 ActionType {
3300 attributes: None,
3301 applies_to: Some(ApplySpec {
3302 resource_types: vec!["foo::a".parse().unwrap()],
3303 principal_types: vec!["foo::a".parse().unwrap()],
3304 context: AttributesOrContext(Type::Type {
3305 ty: TypeVariant::Record(RecordType {
3306 attributes: BTreeMap::new(),
3307 additional_attributes: false,
3308 }),
3309 loc: None,
3310 }),
3311 }),
3312 member_of: None,
3313 annotations: Annotations::new(),
3314 loc: None,
3315 #[cfg(feature = "extended-schema")]
3316 defn_loc: None,
3317 },
3318 )],
3319 ),
3320 ),
3321 ]));
3322 roundtrip(&fragment);
3323 }
3324}
3325
3326#[cfg(test)]
3331mod test_duplicates_error {
3332 use super::*;
3333
3334 #[test]
3335 #[should_panic(expected = "invalid entry: found duplicate key")]
3336 fn namespace() {
3337 let src = r#"{
3338 "Foo": {
3339 "entityTypes" : {},
3340 "actions": {}
3341 },
3342 "Foo": {
3343 "entityTypes" : {},
3344 "actions": {}
3345 }
3346 }"#;
3347 Fragment::from_json_str(src).unwrap();
3348 }
3349
3350 #[test]
3351 #[should_panic(expected = "invalid entry: found duplicate key")]
3352 fn entity_type() {
3353 let src = r#"{
3354 "Foo": {
3355 "entityTypes" : {
3356 "Bar": {},
3357 "Bar": {}
3358 },
3359 "actions": {}
3360 }
3361 }"#;
3362 Fragment::from_json_str(src).unwrap();
3363 }
3364
3365 #[test]
3366 #[should_panic(expected = "invalid entry: found duplicate key")]
3367 fn action() {
3368 let src = r#"{
3369 "Foo": {
3370 "entityTypes" : {},
3371 "actions": {
3372 "Bar": {},
3373 "Bar": {}
3374 }
3375 }
3376 }"#;
3377 Fragment::from_json_str(src).unwrap();
3378 }
3379
3380 #[test]
3381 #[should_panic(expected = "invalid entry: found duplicate key")]
3382 fn common_types() {
3383 let src = r#"{
3384 "Foo": {
3385 "entityTypes" : {},
3386 "actions": { },
3387 "commonTypes": {
3388 "Bar": {"type": "Long"},
3389 "Bar": {"type": "String"}
3390 }
3391 }
3392 }"#;
3393 Fragment::from_json_str(src).unwrap();
3394 }
3395
3396 #[test]
3397 #[should_panic(expected = "invalid entry: found duplicate key")]
3398 fn record_type() {
3399 let src = r#"{
3400 "Foo": {
3401 "entityTypes" : {
3402 "Bar": {
3403 "shape": {
3404 "type": "Record",
3405 "attributes": {
3406 "Baz": {"type": "Long"},
3407 "Baz": {"type": "String"}
3408 }
3409 }
3410 }
3411 },
3412 "actions": { }
3413 }
3414 }"#;
3415 Fragment::from_json_str(src).unwrap();
3416 }
3417
3418 #[test]
3419 #[should_panic(expected = "missing field `resourceTypes`")]
3420 fn missing_resource() {
3421 let src = r#"{
3422 "Foo": {
3423 "entityTypes" : {},
3424 "actions": {
3425 "foo" : {
3426 "appliesTo" : {
3427 "principalTypes" : ["a"]
3428 }
3429 }
3430 }
3431 }
3432 }"#;
3433 Fragment::from_json_str(src).unwrap();
3434 }
3435
3436 #[test]
3437 #[should_panic(expected = "missing field `principalTypes`")]
3438 fn missing_principal() {
3439 let src = r#"{
3440 "Foo": {
3441 "entityTypes" : {},
3442 "actions": {
3443 "foo" : {
3444 "appliesTo" : {
3445 "resourceTypes" : ["a"]
3446 }
3447 }
3448 }
3449 }
3450 }"#;
3451 Fragment::from_json_str(src).unwrap();
3452 }
3453
3454 #[test]
3455 #[should_panic(expected = "missing field `resourceTypes`")]
3456 fn missing_both() {
3457 let src = r#"{
3458 "Foo": {
3459 "entityTypes" : {},
3460 "actions": {
3461 "foo" : {
3462 "appliesTo" : {
3463 }
3464 }
3465 }
3466 }
3467 }"#;
3468 Fragment::from_json_str(src).unwrap();
3469 }
3470}
3471
3472#[cfg(test)]
3473mod annotations {
3474 use crate::validator::RawName;
3475 use cool_asserts::assert_matches;
3476
3477 use super::Fragment;
3478
3479 #[test]
3480 fn empty_namespace() {
3481 let src = serde_json::json!(
3482 {
3483 "" : {
3484 "entityTypes": {},
3485 "actions": {},
3486 "annotations": {
3487 "doc": "this is a doc"
3488 }
3489 }
3490 });
3491 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3492 assert_matches!(schema, Err(err) => {
3493 assert_eq!(&err.to_string(), "annotations are not allowed on the empty namespace");
3494 });
3495 }
3496
3497 #[test]
3498 fn basic() {
3499 let src = serde_json::json!(
3500 {
3501 "N" : {
3502 "entityTypes": {},
3503 "actions": {},
3504 "annotations": {
3505 "doc": "this is a doc"
3506 }
3507 }
3508 });
3509 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3510 assert_matches!(schema, Ok(_));
3511
3512 let src = serde_json::json!(
3513 {
3514 "N" : {
3515 "entityTypes": {
3516 "a": {
3517 "annotations": {
3518 "a": "",
3519 "d": null,
3521 "b": "c",
3522 },
3523 "shape": {
3524 "type": "Long",
3525 }
3526 }
3527 },
3528 "actions": {},
3529 "annotations": {
3530 "doc": "this is a doc"
3531 }
3532 }
3533 });
3534 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3535 assert_matches!(schema, Ok(_));
3536
3537 let src = serde_json::json!(
3538 {
3539 "N" : {
3540 "entityTypes": {
3541 "a": {
3542 "annotations": {
3543 "a": "",
3544 "b": "c",
3545 },
3546 "shape": {
3547 "type": "Long",
3548 }
3549 }
3550 },
3551 "actions": {
3552 "a": {
3553 "annotations": {
3554 "doc": "this is a doc"
3555 },
3556 "appliesTo": {
3557 "principalTypes": ["A"],
3558 "resourceTypes": ["B"],
3559 }
3560 },
3561 },
3562 "annotations": {
3563 "doc": "this is a doc"
3564 }
3565 }
3566 });
3567 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3568 assert_matches!(schema, Ok(_));
3569
3570 let src = serde_json::json!({
3571 "N": {
3572 "entityTypes": {},
3573 "actions": {},
3574 "commonTypes": {
3575 "Task": {
3576 "annotations": {
3577 "doc": "a common type representing a task"
3578 },
3579 "type": "Record",
3580 "attributes": {
3581 "id": {
3582 "type": "Long",
3583 "annotations": {
3584 "doc": "task id"
3585 }
3586 },
3587 "name": {
3588 "type": "String"
3589 },
3590 "state": {
3591 "type": "String"
3592 }
3593 }
3594 }}}});
3595 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3596 assert_matches!(schema, Ok(_));
3597
3598 let src = serde_json::json!({
3599 "N": {
3600 "entityTypes": {
3601 "User" : {
3602 "shape" : {
3603 "type" : "Record",
3604 "attributes" : {
3605 "name" : {
3606 "annotations": {
3607 "a": null,
3608 },
3609 "type" : "String"
3610 },
3611 "age" : {
3612 "type" : "Long"
3613 }
3614 }
3615 }
3616 }
3617 },
3618 "actions": {},
3619 "commonTypes": {}
3620 }});
3621 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3622 assert_matches!(schema, Ok(_));
3623
3624 let src = serde_json::json!({
3626 "N": {
3627 "entityTypes": {
3628 "User" : {
3629 "shape" : {
3630 "type" : "Record",
3631 "attributes" : {
3632 "name" : {
3633 "annotations": {
3634 "first_layer": "b"
3635 },
3636 "type" : "Record",
3637 "attributes": {
3638 "a": {
3639 "type": "Record",
3640 "annotations": {
3641 "second_layer": "d"
3642 },
3643 "attributes": {
3644 "...": {
3645 "annotations": {
3646 "last_layer": null,
3647 },
3648 "type": "Long"
3649 }
3650 }
3651 }
3652 }
3653 },
3654 "age" : {
3655 "type" : "Long"
3656 }
3657 }
3658 }
3659 }
3660 },
3661 "actions": {},
3662 "commonTypes": {}
3663 }});
3664 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3665 assert_matches!(schema, Ok(_));
3666 }
3667
3668 #[track_caller]
3669 fn test_unknown_fields(src: serde_json::Value, field: &str, expected: &str) {
3670 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3671 assert_matches!(schema, Err(errs) => {
3672 assert_eq!(errs.to_string(), format!("unknown field {field}, expected one of {expected}"));
3673 });
3674 }
3675
3676 const ENTITY_TYPE_EXPECTED_ATTRIBUTES: &str =
3677 "`memberOfTypes`, `shape`, `tags`, `enum`, `annotations`";
3678 const NAMESPACE_EXPECTED_ATTRIBUTES: &str =
3679 "`commonTypes`, `entityTypes`, `actions`, `annotations`";
3680 const ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES: &str =
3681 "`type`, `element`, `attributes`, `additionalAttributes`, `name`";
3682 const APPLIES_TO_EXPECTED_ATTRIBUTES: &str = "`resourceTypes`, `principalTypes`, `context`";
3683
3684 #[test]
3685 fn unknown_fields() {
3686 let src = serde_json::json!(
3687 {
3688 "N": {
3689 "entityTypes": {
3690 "UserGroup": {
3691 "shape44": {
3692 "type": "Record",
3693 "attributes": {}
3694 },
3695 "memberOfTypes": [
3696 "UserGroup"
3697 ]
3698 }},
3699 "actions": {},
3700 }});
3701 test_unknown_fields(src, "`shape44`", ENTITY_TYPE_EXPECTED_ATTRIBUTES);
3702
3703 let src = serde_json::json!(
3704 {
3705 "N": {
3706 "entityTypes": {},
3707 "actions": {},
3708 "commonTypes": {
3709 "C": {
3710 "type": "Set",
3711 "element": {
3712 "annotations": {
3713 "doc": "this is a doc"
3714 },
3715 "type": "Long"
3716 }
3717 }
3718 }}});
3719 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3720
3721 let src = serde_json::json!(
3722 {
3723 "N": {
3724 "entityTypes": {},
3725 "actions": {},
3726 "commonTypes": {
3727 "C": {
3728 "type": "Long",
3729 "foo": 1,
3730 "annotations": {
3731 "doc": "this is a doc"
3732 },
3733 }}}});
3734 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3735
3736 let src = serde_json::json!(
3737 {
3738 "N": {
3739 "entityTypes": {},
3740 "actions": {},
3741 "commonTypes": {
3742 "C": {
3743 "type": "Record",
3744 "attributes": {
3745 "a": {
3746 "annotations": {
3747 "doc": "this is a doc"
3748 },
3749 "type": "Long",
3750 "foo": 2,
3751 "required": true,
3752 }
3753 },
3754 }}}});
3755 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3756
3757 let src = serde_json::json!(
3758 {
3759 "N": {
3760 "entityTypes": {},
3761 "actions": {},
3762 "commonTypes": {
3763 "C": {
3764 "type": "Record",
3765 "attributes": {
3766 "a": {
3767 "annotations": {
3768 "doc": "this is a doc"
3769 },
3770 "type": "Record",
3771 "attributes": {
3772 "b": {
3773 "annotations": {
3774 "doc": "this is a doc"
3775 },
3776 "type": "Long",
3777 "bar": 3,
3778 },
3779 },
3780 "required": true,
3781 }
3782 },
3783 }}}});
3784 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3785
3786 let src = serde_json::json!(
3787 {
3788 "N": {
3789 "entityTypes": {
3790 "UserGroup": {
3791 "shape": {
3792 "annotations": {
3793 "doc": "this is a doc"
3794 },
3795 "type": "Record",
3796 "attributes": {}
3797 },
3798 "memberOfTypes": [
3799 "UserGroup"
3800 ]
3801 }},
3802 "actions": {},
3803 }});
3804 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3805
3806 let src = serde_json::json!(
3807 {
3808 "N": {
3809 "entityTypes": {},
3810 "actions": {
3811 "a": {
3812 "appliesTo": {
3813 "annotations": {
3814 "doc": "this is a doc"
3815 },
3816 "principalTypes": ["A"],
3817 "resourceTypes": ["B"],
3818 }
3819 },
3820 },
3821 }});
3822 test_unknown_fields(src, "`annotations`", APPLIES_TO_EXPECTED_ATTRIBUTES);
3823
3824 let src = serde_json::json!(
3825 {
3826 "N" : {
3827 "entityTypes": {},
3828 "actions": {},
3829 "foo": "",
3830 "annotations": {
3831 "doc": "this is a doc"
3832 }
3833 }
3834 });
3835 test_unknown_fields(src, "`foo`", NAMESPACE_EXPECTED_ATTRIBUTES);
3836
3837 let src = serde_json::json!(
3838 {
3839 "" : {
3840 "entityTypes": {},
3841 "actions": {},
3842 "commonTypes": {
3843 "a": {
3844 "type": "Long",
3845 "annotations": {
3846 "foo": ""
3847 },
3848 "bar": 1,
3849 }
3850 }
3851 }
3852 });
3853 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3854
3855 let src = serde_json::json!(
3856 {
3857 "N" : {
3858 "entityTypes": {},
3859 "actions": {},
3860 "commonTypes": {
3861 "a": {
3862 "type": "Record",
3863 "annotations": {
3864 "foo": ""
3865 },
3866 "attributes": {
3867 "a": {
3868 "bar": 1,
3869 "type": "Long"
3870 }
3871 }
3872 }
3873 }
3874 }
3875 });
3876 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3877 }
3878}
3879
3880#[cfg(test)]
3881mod ord {
3882 use super::{InternalName, RawName, Type, TypeVariant};
3883 use std::collections::BTreeSet;
3884
3885 #[test]
3887 #[allow(clippy::collection_is_never_read)]
3888 fn type_ord() {
3889 let mut set: BTreeSet<Type<RawName>> = BTreeSet::default();
3890 set.insert(Type::Type {
3891 ty: TypeVariant::String,
3892 loc: None,
3893 });
3894 let mut set: BTreeSet<Type<InternalName>> = BTreeSet::default();
3895 set.insert(Type::Type {
3896 ty: TypeVariant::String,
3897 loc: None,
3898 });
3899 }
3900}
3901
3902#[cfg(test)]
3903#[allow(clippy::indexing_slicing)]
3905mod enumerated_entity_types {
3906 use cool_asserts::assert_matches;
3907
3908 use crate::validator::{
3909 json_schema::{EntityType, EntityTypeKind, Fragment},
3910 RawName,
3911 };
3912
3913 #[test]
3914 fn basic() {
3915 let src = serde_json::json!({
3916 "": {
3917 "entityTypes": {
3918 "Foo": {
3919 "enum": ["foo", "bar"],
3920 "annotations": {
3921 "a": "b",
3922 }
3923 },
3924 },
3925 "actions": {},
3926 }
3927 });
3928 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3929 assert_matches!(schema, Ok(frag) => {
3930 assert_matches!(&frag.0[&None].entity_types[&"Foo".parse().unwrap()], EntityType {
3931 kind: EntityTypeKind::Enum {choices},
3932 ..
3933 } => {
3934 assert_eq!(Vec::from(choices.clone()), ["foo", "bar"]);
3935 });
3936 });
3937
3938 let src = serde_json::json!({
3939 "": {
3940 "entityTypes": {
3941 "Foo": {
3942 "enum": [],
3943 "annotations": {
3944 "a": "b",
3945 }
3946 },
3947 },
3948 "actions": {},
3949 }
3950 });
3951 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3952 assert_matches!(schema, Err(errs) => {
3953 assert_eq!(errs.to_string(), "the vector provided was empty, NonEmpty needs at least one element");
3955 });
3956
3957 let src = serde_json::json!({
3958 "": {
3959 "entityTypes": {
3960 "Foo": {
3961 "enum": null,
3962 },
3963 },
3964 "actions": {},
3965 }
3966 });
3967 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3968 assert_matches!(schema, Err(errs) => {
3969 assert_eq!(errs.to_string(), "invalid type: null, expected a sequence");
3970 });
3971
3972 let src = serde_json::json!({
3973 "": {
3974 "entityTypes": {
3975 "Foo": {
3976 "enum": ["foo"],
3977 "memberOfTypes": ["bar"],
3978 },
3979 },
3980 "actions": {},
3981 }
3982 });
3983 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3984 assert_matches!(schema, Err(errs) => {
3985 assert_eq!(errs.to_string(), "unexpected field: memberOfTypes");
3986 });
3987 }
3988}