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)]
803 #[serde(skip_serializing_if = "Option::is_none")]
804 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
805 #[serde(default)]
807 #[serde(skip_serializing_if = "Option::is_none")]
808 pub applies_to: Option<ApplySpec<N>>,
809 #[serde(default)]
811 #[serde(skip_serializing_if = "Option::is_none")]
812 pub member_of: Option<Vec<ActionEntityUID<N>>>,
813 #[serde(default)]
815 #[serde(skip_serializing_if = "Annotations::is_empty")]
816 pub annotations: Annotations,
817 #[serde(skip)]
823 #[educe(PartialEq(ignore))]
824 pub loc: Option<Loc>,
825
826 #[cfg(feature = "extended-schema")]
828 #[serde(skip)]
829 #[educe(PartialEq(ignore))]
830 pub(crate) defn_loc: Option<Loc>,
831}
832
833impl ActionType<RawName> {
834 pub fn conditionally_qualify_type_references(
836 self,
837 ns: Option<&InternalName>,
838 ) -> ActionType<ConditionalName> {
839 ActionType {
840 attributes: self.attributes,
841 applies_to: self
842 .applies_to
843 .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
844 member_of: self.member_of.map(|v| {
845 v.into_iter()
846 .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
847 .collect()
848 }),
849 annotations: self.annotations,
850 loc: self.loc,
851 #[cfg(feature = "extended-schema")]
852 defn_loc: self.defn_loc,
853 }
854 }
855}
856
857impl ActionType<ConditionalName> {
858 pub fn fully_qualify_type_references(
865 self,
866 all_defs: &AllDefs,
867 ) -> Result<ActionType<InternalName>> {
868 Ok(ActionType {
869 attributes: self.attributes,
870 applies_to: self
871 .applies_to
872 .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
873 .transpose()?,
874 member_of: self
875 .member_of
876 .map(|v| {
877 v.into_iter()
878 .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
879 .collect::<std::result::Result<_, ActionNotDefinedError>>()
880 })
881 .transpose()?,
882 annotations: self.annotations,
883 loc: self.loc,
884 #[cfg(feature = "extended-schema")]
885 defn_loc: self.defn_loc,
886 })
887 }
888}
889
890#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
900#[educe(PartialEq, Eq)]
901#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
902#[serde(deny_unknown_fields)]
903#[serde(rename_all = "camelCase")]
904#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
905#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
906pub struct ApplySpec<N> {
907 pub resource_types: Vec<N>,
909 pub principal_types: Vec<N>,
911 #[serde(default)]
913 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
914 pub context: AttributesOrContext<N>,
915}
916
917impl ApplySpec<RawName> {
918 pub fn conditionally_qualify_type_references(
920 self,
921 ns: Option<&InternalName>,
922 ) -> ApplySpec<ConditionalName> {
923 ApplySpec {
924 resource_types: self
925 .resource_types
926 .into_iter()
927 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
929 principal_types: self
930 .principal_types
931 .into_iter()
932 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
934 context: self.context.conditionally_qualify_type_references(ns),
935 }
936 }
937}
938
939impl ApplySpec<ConditionalName> {
940 pub fn fully_qualify_type_references(
947 self,
948 all_defs: &AllDefs,
949 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
950 Ok(ApplySpec {
951 resource_types: self
952 .resource_types
953 .into_iter()
954 .map(|cname| cname.resolve(all_defs))
955 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
956 principal_types: self
957 .principal_types
958 .into_iter()
959 .map(|cname| cname.resolve(all_defs))
960 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
961 context: self.context.fully_qualify_type_references(all_defs)?,
962 })
963 }
964}
965
966#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
968#[educe(PartialEq, Eq, Hash)]
969#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
970#[serde(deny_unknown_fields)]
971#[serde(rename_all = "camelCase")]
972#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
973#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
974pub struct ActionEntityUID<N> {
975 pub id: SmolStr,
977
978 #[serde(rename = "type")]
989 #[serde(default)]
990 #[serde(skip_serializing_if = "Option::is_none")]
991 pub ty: Option<N>,
992 #[cfg(feature = "extended-schema")]
993 #[serde(skip)]
994 pub loc: Option<Loc>,
996}
997
998impl ActionEntityUID<RawName> {
999 pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
1002 Self {
1003 id,
1004 ty,
1005 #[cfg(feature = "extended-schema")]
1006 loc: None,
1007 }
1008 }
1009
1010 pub fn default_type(id: SmolStr) -> Self {
1015 Self {
1016 id,
1017 ty: None,
1018 #[cfg(feature = "extended-schema")]
1019 loc: None,
1020 }
1021 }
1022
1023 #[cfg(feature = "extended-schema")]
1028 pub fn default_type_with_loc(id: SmolStr, loc: Option<Loc>) -> Self {
1029 Self { id, ty: None, loc }
1030 }
1031}
1032
1033impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
1034 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1035 if let Some(ty) = &self.ty {
1036 write!(f, "{ty}::")?
1037 } else {
1038 write!(f, "Action::")?
1039 }
1040 write!(f, "\"{}\"", self.id.escape_debug())
1041 }
1042}
1043
1044impl ActionEntityUID<RawName> {
1045 pub fn conditionally_qualify_type_references(
1047 self,
1048 ns: Option<&InternalName>,
1049 ) -> ActionEntityUID<ConditionalName> {
1050 ActionEntityUID {
1053 id: self.id,
1054 ty: {
1055 #[allow(clippy::expect_used)]
1057 let raw_name = self
1058 .ty
1059 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1060 Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
1061 },
1062 #[cfg(feature = "extended-schema")]
1063 loc: None,
1064 }
1065 }
1066
1067 pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
1069 ActionEntityUID {
1072 id: self.id,
1073 ty: {
1074 #[allow(clippy::expect_used)]
1076 let raw_name = self
1077 .ty
1078 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1079 Some(raw_name.qualify_with(ns))
1080 },
1081 #[cfg(feature = "extended-schema")]
1082 loc: self.loc,
1083 }
1084 }
1085}
1086
1087impl ActionEntityUID<ConditionalName> {
1088 pub fn ty(&self) -> &ConditionalName {
1090 #[allow(clippy::expect_used)]
1092 self.ty.as_ref().expect("by INVARIANT on self.ty")
1093 }
1094
1095 pub fn fully_qualify_type_references(
1103 self,
1104 all_defs: &AllDefs,
1105 ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
1106 for possibility in self.possibilities() {
1107 if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
1111 if all_defs.is_defined_as_action(&euid) {
1112 return Ok(possibility);
1113 }
1114 }
1115 }
1116 Err(ActionNotDefinedError(nonempty!(self)))
1117 }
1118
1119 pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
1123 self.ty()
1126 .possibilities()
1127 .map(|possibility| ActionEntityUID {
1128 id: self.id.clone(),
1129 ty: Some(possibility.clone()),
1130 #[cfg(feature = "extended-schema")]
1131 loc: None,
1132 })
1133 }
1134
1135 pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
1139 ActionEntityUID {
1140 id: self.id.clone(),
1141 ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
1142 #[cfg(feature = "extended-schema")]
1143 loc: None,
1144 }
1145 }
1146}
1147
1148impl ActionEntityUID<Name> {
1149 pub fn ty(&self) -> &Name {
1151 #[allow(clippy::expect_used)]
1153 self.ty.as_ref().expect("by INVARIANT on self.ty")
1154 }
1155}
1156
1157impl ActionEntityUID<InternalName> {
1158 pub fn ty(&self) -> &InternalName {
1160 #[allow(clippy::expect_used)]
1162 self.ty.as_ref().expect("by INVARIANT on self.ty")
1163 }
1164}
1165
1166impl From<ActionEntityUID<Name>> for EntityUID {
1167 fn from(aeuid: ActionEntityUID<Name>) -> Self {
1168 EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
1169 }
1170}
1171
1172impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
1173 type Error = <InternalName as TryInto<Name>>::Error;
1174 fn try_from(
1175 aeuid: ActionEntityUID<InternalName>,
1176 ) -> std::result::Result<Self, <InternalName as TryInto<Name>>::Error> {
1177 let ty = Name::try_from(aeuid.ty().clone())?;
1178 #[cfg(feature = "extended-schema")]
1179 let loc = aeuid.loc;
1180 #[cfg(not(feature = "extended-schema"))]
1181 let loc = None;
1182 Ok(EntityUID::from_components(
1183 ty.into(),
1184 Eid::new(aeuid.id),
1185 loc,
1186 ))
1187 }
1188}
1189
1190impl From<EntityUID> for ActionEntityUID<Name> {
1191 fn from(euid: EntityUID) -> Self {
1192 let (ty, id) = euid.components();
1193 ActionEntityUID {
1194 ty: Some(ty.into()),
1195 id: id.into_smolstr(),
1196 #[cfg(feature = "extended-schema")]
1197 loc: None,
1198 }
1199 }
1200}
1201
1202#[derive(Educe, Debug, Clone, Serialize)]
1209#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1210#[serde(untagged)]
1215#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1216#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1217pub enum Type<N> {
1218 Type {
1222 #[serde(flatten)]
1224 ty: TypeVariant<N>,
1225 #[serde(skip)]
1231 #[educe(PartialEq(ignore))]
1232 #[educe(PartialOrd(ignore))]
1233 loc: Option<Loc>,
1234 },
1235 CommonTypeRef {
1241 #[serde(rename = "type")]
1246 type_name: N,
1247 #[serde(skip)]
1253 #[educe(PartialEq(ignore))]
1254 #[educe(PartialOrd(ignore))]
1255 loc: Option<Loc>,
1256 },
1257}
1258
1259impl<N> Type<N> {
1260 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
1263 match self {
1264 Type::Type {
1265 ty: TypeVariant::Record(RecordType { attributes, .. }),
1266 ..
1267 } => attributes
1268 .values()
1269 .map(|ty| ty.ty.common_type_references())
1270 .fold(Box::new(std::iter::empty()), |it, tys| {
1271 Box::new(it.chain(tys))
1272 }),
1273 Type::Type {
1274 ty: TypeVariant::Set { element },
1275 ..
1276 } => element.common_type_references(),
1277 Type::Type {
1278 ty: TypeVariant::EntityOrCommon { type_name },
1279 ..
1280 } => Box::new(std::iter::once(type_name)),
1281 Type::CommonTypeRef { type_name, .. } => Box::new(std::iter::once(type_name)),
1282 _ => Box::new(std::iter::empty()),
1283 }
1284 }
1285
1286 pub fn is_extension(&self) -> Option<bool> {
1292 match self {
1293 Self::Type {
1294 ty: TypeVariant::Extension { .. },
1295 ..
1296 } => Some(true),
1297 Self::Type {
1298 ty: TypeVariant::Set { element },
1299 ..
1300 } => element.is_extension(),
1301 Self::Type {
1302 ty: TypeVariant::Record(RecordType { attributes, .. }),
1303 ..
1304 } => attributes
1305 .values()
1306 .try_fold(false, |a, e| match e.ty.is_extension() {
1307 Some(true) => Some(true),
1308 Some(false) => Some(a),
1309 None => None,
1310 }),
1311 Self::Type { .. } => Some(false),
1312 Self::CommonTypeRef { .. } => None,
1313 }
1314 }
1315
1316 pub fn is_empty_record(&self) -> bool {
1319 match self {
1320 Self::Type {
1321 ty: TypeVariant::Record(rty),
1322 ..
1323 } => rty.is_empty_record(),
1324 _ => false,
1325 }
1326 }
1327
1328 pub fn loc(&self) -> Option<&Loc> {
1330 match self {
1331 Self::Type { loc, .. } => loc.as_ref(),
1332 Self::CommonTypeRef { loc, .. } => loc.as_ref(),
1333 }
1334 }
1335
1336 pub fn with_loc(self, new_loc: Option<Loc>) -> Self {
1338 match self {
1339 Self::Type { ty, loc: _loc } => Self::Type { ty, loc: new_loc },
1340 Self::CommonTypeRef {
1341 type_name,
1342 loc: _loc,
1343 } => Self::CommonTypeRef {
1344 type_name,
1345 loc: new_loc,
1346 },
1347 }
1348 }
1349}
1350
1351impl Type<RawName> {
1352 pub fn conditionally_qualify_type_references(
1354 self,
1355 ns: Option<&InternalName>,
1356 ) -> Type<ConditionalName> {
1357 match self {
1358 Self::Type { ty, loc } => Type::Type {
1359 ty: ty.conditionally_qualify_type_references(ns),
1360 loc,
1361 },
1362 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1363 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
1364 loc,
1365 },
1366 }
1367 }
1368
1369 fn into_n<N: From<RawName>>(self) -> Type<N> {
1370 match self {
1371 Self::Type { ty, loc } => Type::Type {
1372 ty: ty.into_n(),
1373 loc,
1374 },
1375 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1376 type_name: type_name.into(),
1377 loc,
1378 },
1379 }
1380 }
1381}
1382
1383impl Type<ConditionalName> {
1384 pub fn fully_qualify_type_references(
1390 self,
1391 all_defs: &AllDefs,
1392 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1393 match self {
1394 Self::Type { ty, loc } => Ok(Type::Type {
1395 ty: ty.fully_qualify_type_references(all_defs)?,
1396 loc,
1397 }),
1398 Self::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef {
1399 type_name: type_name.resolve(all_defs)?,
1400 loc,
1401 }),
1402 }
1403 }
1404}
1405
1406impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1407 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1408 where
1409 D: serde::Deserializer<'de>,
1410 {
1411 deserializer.deserialize_any(TypeVisitor {
1412 _phantom: PhantomData,
1413 })
1414 }
1415}
1416
1417#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1419#[serde(field_identifier, rename_all = "camelCase")]
1420enum TypeFields {
1421 Type,
1422 Element,
1423 Attributes,
1424 AdditionalAttributes,
1425 Name,
1426}
1427
1428macro_rules! type_field_name {
1432 (Type) => {
1433 "type"
1434 };
1435 (Element) => {
1436 "element"
1437 };
1438 (Attributes) => {
1439 "attributes"
1440 };
1441 (AdditionalAttributes) => {
1442 "additionalAttributes"
1443 };
1444 (Name) => {
1445 "name"
1446 };
1447}
1448
1449impl TypeFields {
1450 fn as_str(&self) -> &'static str {
1451 match self {
1452 TypeFields::Type => type_field_name!(Type),
1453 TypeFields::Element => type_field_name!(Element),
1454 TypeFields::Attributes => type_field_name!(Attributes),
1455 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1456 TypeFields::Name => type_field_name!(Name),
1457 }
1458 }
1459}
1460
1461#[derive(Debug, Deserialize)]
1466struct AttributesTypeMap(
1467 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1468 BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1469);
1470
1471struct TypeVisitor<N> {
1472 _phantom: PhantomData<N>,
1473}
1474
1475impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1476 type Value = Type<N>;
1477
1478 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1479 formatter.write_str("builtin type or reference to type defined in commonTypes")
1480 }
1481
1482 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1483 where
1484 M: MapAccess<'de>,
1485 {
1486 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1487
1488 let mut type_name: Option<SmolStr> = None;
1489 let mut element: Option<Type<N>> = None;
1490 let mut attributes: Option<AttributesTypeMap> = None;
1491 let mut additional_attributes: Option<bool> = None;
1492 let mut name: Option<SmolStr> = None;
1493
1494 while let Some(key) = map.next_key()? {
1498 match key {
1499 TypeField => {
1500 if type_name.is_some() {
1501 return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1502 }
1503 type_name = Some(map.next_value()?);
1504 }
1505 Element => {
1506 if element.is_some() {
1507 return Err(serde::de::Error::duplicate_field(Element.as_str()));
1508 }
1509 element = Some(map.next_value()?);
1510 }
1511 Attributes => {
1512 if attributes.is_some() {
1513 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1514 }
1515 attributes = Some(map.next_value()?);
1516 }
1517 AdditionalAttributes => {
1518 if additional_attributes.is_some() {
1519 return Err(serde::de::Error::duplicate_field(
1520 AdditionalAttributes.as_str(),
1521 ));
1522 }
1523 additional_attributes = Some(map.next_value()?);
1524 }
1525 Name => {
1526 if name.is_some() {
1527 return Err(serde::de::Error::duplicate_field(Name.as_str()));
1528 }
1529 name = Some(map.next_value()?);
1530 }
1531 }
1532 }
1533
1534 Self::build_schema_type::<M>(
1535 type_name.as_ref(),
1536 element,
1537 attributes,
1538 additional_attributes,
1539 name,
1540 )
1541 }
1542}
1543
1544impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1545 fn build_schema_type<M>(
1550 type_name: Option<&SmolStr>,
1551 element: Option<Type<N>>,
1552 attributes: Option<AttributesTypeMap>,
1553 additional_attributes: Option<bool>,
1554 name: Option<SmolStr>,
1555 ) -> std::result::Result<Type<N>, M::Error>
1556 where
1557 M: MapAccess<'de>,
1558 {
1559 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1560 let mut remaining_fields = [
1562 (TypeField, type_name.is_some()),
1563 (Element, element.is_some()),
1564 (Attributes, attributes.is_some()),
1565 (AdditionalAttributes, additional_attributes.is_some()),
1566 (Name, name.is_some()),
1567 ]
1568 .into_iter()
1569 .filter(|(_, present)| *present)
1570 .map(|(field, _)| field)
1571 .collect::<HashSet<_>>();
1572
1573 match type_name {
1574 Some(s) => {
1575 remaining_fields.remove(&TypeField);
1577 let error_if_fields = |fs: &[TypeFields],
1580 expected: &'static [&'static str]|
1581 -> std::result::Result<(), M::Error> {
1582 for f in fs {
1583 if remaining_fields.contains(f) {
1584 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1585 }
1586 }
1587 Ok(())
1588 };
1589 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1590 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1591 };
1592 match s.as_str() {
1593 "String" => {
1594 error_if_any_fields()?;
1595 Ok(Type::Type {
1596 ty: TypeVariant::String,
1597 loc: None,
1598 })
1599 }
1600 "Long" => {
1601 error_if_any_fields()?;
1602 Ok(Type::Type {
1603 ty: TypeVariant::Long,
1604 loc: None,
1605 })
1606 }
1607 "Boolean" => {
1608 error_if_any_fields()?;
1609 Ok(Type::Type {
1610 ty: TypeVariant::Boolean,
1611 loc: None,
1612 })
1613 }
1614 "Set" => {
1615 error_if_fields(
1616 &[Attributes, AdditionalAttributes, Name],
1617 &[type_field_name!(Element)],
1618 )?;
1619
1620 match element {
1621 Some(element) => Ok(Type::Type {
1622 ty: TypeVariant::Set {
1623 element: Box::new(element),
1624 },
1625 loc: None,
1626 }),
1627 None => Err(serde::de::Error::missing_field(Element.as_str())),
1628 }
1629 }
1630 "Record" => {
1631 error_if_fields(
1632 &[Element, Name],
1633 &[
1634 type_field_name!(Attributes),
1635 type_field_name!(AdditionalAttributes),
1636 ],
1637 )?;
1638
1639 if let Some(attributes) = attributes {
1640 let additional_attributes =
1641 additional_attributes.unwrap_or_else(partial_schema_default);
1642 Ok(Type::Type {
1643 ty: TypeVariant::Record(RecordType {
1644 attributes: attributes
1645 .0
1646 .into_iter()
1647 .map(
1648 |(
1649 k,
1650 TypeOfAttribute {
1651 ty,
1652 required,
1653 annotations,
1654 #[cfg(feature = "extended-schema")]
1655 loc,
1656 },
1657 )| {
1658 (
1659 k,
1660 TypeOfAttribute {
1661 ty: ty.into_n(),
1662 required,
1663 annotations,
1664 #[cfg(feature = "extended-schema")]
1665 loc,
1666 },
1667 )
1668 },
1669 )
1670 .collect(),
1671 additional_attributes,
1672 }),
1673 loc: None,
1674 })
1675 } else {
1676 Err(serde::de::Error::missing_field(Attributes.as_str()))
1677 }
1678 }
1679 "Entity" => {
1680 error_if_fields(
1681 &[Element, Attributes, AdditionalAttributes],
1682 &[type_field_name!(Name)],
1683 )?;
1684 match name {
1685 Some(name) => Ok(Type::Type {
1686 ty: TypeVariant::Entity {
1687 name: RawName::from_normalized_str(&name)
1688 .map_err(|err| {
1689 serde::de::Error::custom(format!(
1690 "invalid entity type `{name}`: {err}"
1691 ))
1692 })?
1693 .into(),
1694 },
1695 loc: None,
1696 }),
1697 None => Err(serde::de::Error::missing_field(Name.as_str())),
1698 }
1699 }
1700 "EntityOrCommon" => {
1701 error_if_fields(
1702 &[Element, Attributes, AdditionalAttributes],
1703 &[type_field_name!(Name)],
1704 )?;
1705 match name {
1706 Some(name) => Ok(Type::Type {
1707 ty: TypeVariant::EntityOrCommon {
1708 type_name: RawName::from_normalized_str(&name)
1709 .map_err(|err| {
1710 serde::de::Error::custom(format!(
1711 "invalid entity or common type `{name}`: {err}"
1712 ))
1713 })?
1714 .into(),
1715 },
1716 loc: None,
1717 }),
1718 None => Err(serde::de::Error::missing_field(Name.as_str())),
1719 }
1720 }
1721 "Extension" => {
1722 error_if_fields(
1723 &[Element, Attributes, AdditionalAttributes],
1724 &[type_field_name!(Name)],
1725 )?;
1726
1727 match name {
1728 Some(name) => Ok(Type::Type {
1729 ty: TypeVariant::Extension {
1730 name: UnreservedId::from_normalized_str(&name).map_err(
1731 |err| {
1732 serde::de::Error::custom(format!(
1733 "invalid extension type `{name}`: {err}"
1734 ))
1735 },
1736 )?,
1737 },
1738 loc: None,
1739 }),
1740 None => Err(serde::de::Error::missing_field(Name.as_str())),
1741 }
1742 }
1743 type_name => {
1744 error_if_any_fields()?;
1745 Ok(Type::CommonTypeRef {
1746 type_name: N::from(RawName::from_normalized_str(type_name).map_err(
1747 |err| {
1748 serde::de::Error::custom(format!(
1749 "invalid common type `{type_name}`: {err}"
1750 ))
1751 },
1752 )?),
1753 loc: None,
1754 })
1755 }
1756 }
1757 }
1758 None => Err(serde::de::Error::missing_field(TypeField.as_str())),
1759 }
1760 }
1761}
1762
1763impl<N> From<TypeVariant<N>> for Type<N> {
1764 fn from(ty: TypeVariant<N>) -> Self {
1765 Self::Type { ty, loc: None }
1766 }
1767}
1768
1769#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1775#[educe(PartialEq, Eq, PartialOrd, Ord)]
1776#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1777#[serde(rename_all = "camelCase")]
1778#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1779#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1780pub struct RecordType<N> {
1781 pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
1783 #[serde(default = "partial_schema_default")]
1785 #[serde(skip_serializing_if = "is_partial_schema_default")]
1786 pub additional_attributes: bool,
1787}
1788
1789impl<N> Default for RecordType<N> {
1790 fn default() -> Self {
1791 Self {
1792 attributes: BTreeMap::new(),
1793 additional_attributes: partial_schema_default(),
1794 }
1795 }
1796}
1797
1798impl<N> RecordType<N> {
1799 pub fn is_empty_record(&self) -> bool {
1801 self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
1802 }
1803}
1804
1805impl RecordType<RawName> {
1806 pub fn conditionally_qualify_type_references(
1808 self,
1809 ns: Option<&InternalName>,
1810 ) -> RecordType<ConditionalName> {
1811 RecordType {
1812 attributes: self
1813 .attributes
1814 .into_iter()
1815 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
1816 .collect(),
1817 additional_attributes: self.additional_attributes,
1818 }
1819 }
1820}
1821
1822impl RecordType<ConditionalName> {
1823 pub fn fully_qualify_type_references(
1830 self,
1831 all_defs: &AllDefs,
1832 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1833 Ok(RecordType {
1834 attributes: self
1835 .attributes
1836 .into_iter()
1837 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
1838 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1839 additional_attributes: self.additional_attributes,
1840 })
1841 }
1842}
1843
1844#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1852#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1853#[serde(tag = "type")]
1854#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1855#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1856#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1857pub enum TypeVariant<N> {
1858 String,
1860 Long,
1862 Boolean,
1864 Set {
1866 element: Box<Type<N>>,
1868 },
1869 Record(RecordType<N>),
1871 Entity {
1873 name: N,
1878 },
1879 EntityOrCommon {
1881 #[serde(rename = "name")]
1902 type_name: N,
1903 },
1904 Extension {
1906 name: UnreservedId,
1908 },
1909}
1910
1911impl TypeVariant<RawName> {
1912 pub fn conditionally_qualify_type_references(
1914 self,
1915 ns: Option<&InternalName>,
1916 ) -> TypeVariant<ConditionalName> {
1917 match self {
1918 Self::Boolean => TypeVariant::Boolean,
1919 Self::Long => TypeVariant::Long,
1920 Self::String => TypeVariant::String,
1921 Self::Extension { name } => TypeVariant::Extension { name },
1922 Self::Entity { name } => TypeVariant::Entity {
1923 name: name.conditionally_qualify_with(ns, ReferenceType::Entity), },
1925 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1926 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
1927 },
1928 Self::Set { element } => TypeVariant::Set {
1929 element: Box::new(element.conditionally_qualify_type_references(ns)),
1930 },
1931 Self::Record(RecordType {
1932 attributes,
1933 additional_attributes,
1934 }) => TypeVariant::Record(RecordType {
1935 attributes: BTreeMap::from_iter(attributes.into_iter().map(
1936 |(
1937 attr,
1938 TypeOfAttribute {
1939 ty,
1940 required,
1941 annotations,
1942 #[cfg(feature = "extended-schema")]
1943 loc,
1944 },
1945 )| {
1946 (
1947 attr,
1948 TypeOfAttribute {
1949 ty: ty.conditionally_qualify_type_references(ns),
1950 required,
1951 annotations,
1952 #[cfg(feature = "extended-schema")]
1953 loc,
1954 },
1955 )
1956 },
1957 )),
1958 additional_attributes,
1959 }),
1960 }
1961 }
1962
1963 fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
1964 match self {
1965 Self::Boolean => TypeVariant::Boolean,
1966 Self::Long => TypeVariant::Long,
1967 Self::String => TypeVariant::String,
1968 Self::Entity { name } => TypeVariant::Entity { name: name.into() },
1969 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1970 type_name: type_name.into(),
1971 },
1972 Self::Record(RecordType {
1973 attributes,
1974 additional_attributes,
1975 }) => TypeVariant::Record(RecordType {
1976 attributes: attributes
1977 .into_iter()
1978 .map(|(k, v)| (k, v.into_n()))
1979 .collect(),
1980 additional_attributes,
1981 }),
1982 Self::Set { element } => TypeVariant::Set {
1983 element: Box::new(element.into_n()),
1984 },
1985 Self::Extension { name } => TypeVariant::Extension { name },
1986 }
1987 }
1988}
1989
1990impl TypeVariant<ConditionalName> {
1991 pub fn fully_qualify_type_references(
1998 self,
1999 all_defs: &AllDefs,
2000 ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
2001 match self {
2002 Self::Boolean => Ok(TypeVariant::Boolean),
2003 Self::Long => Ok(TypeVariant::Long),
2004 Self::String => Ok(TypeVariant::String),
2005 Self::Extension { name } => Ok(TypeVariant::Extension { name }),
2006 Self::Entity { name } => Ok(TypeVariant::Entity {
2007 name: name.resolve(all_defs)?,
2008 }),
2009 Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
2010 type_name: type_name.resolve(all_defs)?,
2011 }),
2012 Self::Set { element } => Ok(TypeVariant::Set {
2013 element: Box::new(element.fully_qualify_type_references(all_defs)?),
2014 }),
2015 Self::Record(RecordType {
2016 attributes,
2017 additional_attributes,
2018 }) => Ok(TypeVariant::Record(RecordType {
2019 attributes: attributes
2020 .into_iter()
2021 .map(
2022 |(
2023 attr,
2024 TypeOfAttribute {
2025 ty,
2026 required,
2027 annotations,
2028 #[cfg(feature = "extended-schema")]
2029 loc,
2030 },
2031 )| {
2032 Ok((
2033 attr,
2034 TypeOfAttribute {
2035 ty: ty.fully_qualify_type_references(all_defs)?,
2036 required,
2037 annotations,
2038 #[cfg(feature = "extended-schema")]
2039 loc,
2040 },
2041 ))
2042 },
2043 )
2044 .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
2045 additional_attributes,
2046 })),
2047 }
2048 }
2049}
2050
2051#[allow(
2053 clippy::trivially_copy_pass_by_ref,
2054 reason = "Reference required to work with derived serde serialize implementation"
2055)]
2056fn is_partial_schema_default(b: &bool) -> bool {
2057 *b == partial_schema_default()
2058}
2059
2060#[cfg(feature = "arbitrary")]
2061#[allow(clippy::panic)]
2063impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
2064 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
2065 use std::collections::BTreeSet;
2066
2067 Ok(Type::Type {
2068 ty: match u.int_in_range::<u8>(1..=8)? {
2069 1 => TypeVariant::String,
2070 2 => TypeVariant::Long,
2071 3 => TypeVariant::Boolean,
2072 4 => TypeVariant::Set {
2073 element: Box::new(u.arbitrary()?),
2074 },
2075 5 => {
2076 let attributes = {
2077 let attr_names: BTreeSet<String> = u.arbitrary()?;
2078 attr_names
2079 .into_iter()
2080 .map(|attr_name| {
2081 Ok((attr_name.into(), u.arbitrary::<TypeOfAttribute<RawName>>()?))
2082 })
2083 .collect::<arbitrary::Result<_>>()?
2084 };
2085 TypeVariant::Record(RecordType {
2086 attributes,
2087 additional_attributes: u.arbitrary()?,
2088 })
2089 }
2090 6 => TypeVariant::Entity {
2091 name: u.arbitrary()?,
2092 },
2093 7 => TypeVariant::Extension {
2094 #[allow(clippy::unwrap_used)]
2096 name: "ipaddr".parse().unwrap(),
2097 },
2098 8 => TypeVariant::Extension {
2099 #[allow(clippy::unwrap_used)]
2101 name: "decimal".parse().unwrap(),
2102 },
2103 n => panic!("bad index: {n}"),
2104 },
2105 loc: None,
2106 })
2107 }
2108 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
2109 (1, None) }
2111}
2112
2113#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2132#[educe(PartialEq, Eq, PartialOrd, Ord)]
2133#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2134pub struct TypeOfAttribute<N> {
2135 #[serde(flatten)]
2137 pub ty: Type<N>,
2138 #[serde(default)]
2140 #[serde(skip_serializing_if = "Annotations::is_empty")]
2141 pub annotations: Annotations,
2142 #[serde(default = "record_attribute_required_default")]
2144 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
2145 pub required: bool,
2146
2147 #[cfg(feature = "extended-schema")]
2149 #[educe(Eq(ignore))]
2150 #[serde(skip)]
2151 pub loc: Option<Loc>,
2152}
2153
2154impl TypeOfAttribute<RawName> {
2155 fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
2156 TypeOfAttribute {
2157 ty: self.ty.into_n(),
2158
2159 required: self.required,
2160 annotations: self.annotations,
2161 #[cfg(feature = "extended-schema")]
2162 loc: self.loc,
2163 }
2164 }
2165
2166 pub fn conditionally_qualify_type_references(
2168 self,
2169 ns: Option<&InternalName>,
2170 ) -> TypeOfAttribute<ConditionalName> {
2171 TypeOfAttribute {
2172 ty: self.ty.conditionally_qualify_type_references(ns),
2173 required: self.required,
2174 annotations: self.annotations,
2175 #[cfg(feature = "extended-schema")]
2176 loc: self.loc,
2177 }
2178 }
2179}
2180
2181impl TypeOfAttribute<ConditionalName> {
2182 pub fn fully_qualify_type_references(
2189 self,
2190 all_defs: &AllDefs,
2191 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
2192 Ok(TypeOfAttribute {
2193 ty: self.ty.fully_qualify_type_references(all_defs)?,
2194 required: self.required,
2195 annotations: self.annotations,
2196 #[cfg(feature = "extended-schema")]
2197 loc: self.loc,
2198 })
2199 }
2200}
2201
2202#[cfg(feature = "arbitrary")]
2203impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
2204 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2205 Ok(Self {
2206 ty: u.arbitrary::<Type<RawName>>()?,
2207 required: u.arbitrary()?,
2208 annotations: u.arbitrary()?,
2209 #[cfg(feature = "extended-schema")]
2210 loc: None,
2211 })
2212 }
2213
2214 fn size_hint(depth: usize) -> (usize, Option<usize>) {
2215 arbitrary::size_hint::and_all(&[
2216 <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
2217 <bool as arbitrary::Arbitrary>::size_hint(depth),
2218 <crate::est::Annotations as arbitrary::Arbitrary>::size_hint(depth),
2219 ])
2220 }
2221}
2222
2223#[allow(
2225 clippy::trivially_copy_pass_by_ref,
2226 reason = "Reference required to work with derived serde serialize implementation"
2227)]
2228fn is_record_attribute_required_default(b: &bool) -> bool {
2229 *b == record_attribute_required_default()
2230}
2231
2232fn partial_schema_default() -> bool {
2235 false
2236}
2237
2238fn record_attribute_required_default() -> bool {
2240 true
2241}
2242
2243#[cfg(test)]
2244mod test {
2245 use crate::{
2246 extensions::Extensions,
2247 test_utils::{expect_err, ExpectedErrorMessageBuilder},
2248 };
2249 use cool_asserts::assert_matches;
2250
2251 use crate::validator::ValidatorSchema;
2252
2253 use super::*;
2254
2255 #[test]
2256 fn test_entity_type_parser1() {
2257 let user = r#"
2258 {
2259 "memberOfTypes" : ["UserGroup"]
2260 }
2261 "#;
2262 assert_matches!(serde_json::from_str::<EntityType<RawName>>(user), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2263 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
2264 assert_eq!(
2265 et.shape,
2266 AttributesOrContext(Type::Type {
2267 ty: TypeVariant::Record(RecordType {
2268 attributes: BTreeMap::new(),
2269 additional_attributes: false
2270 }),
2271 loc: None
2272 }),
2273 );});
2274 }
2275
2276 #[test]
2277 fn test_entity_type_parser2() {
2278 let src = r#"
2279 { }
2280 "#;
2281 assert_matches!(serde_json::from_str::<EntityType<RawName>>(src), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2282 assert_eq!(et.member_of_types.len(), 0);
2283 assert_eq!(
2284 et.shape,
2285 AttributesOrContext(Type::Type {
2286 ty: TypeVariant::Record(RecordType {
2287 attributes: BTreeMap::new(),
2288 additional_attributes: false
2289 }),
2290 loc: None
2291 }),
2292 );});
2293 }
2294
2295 #[test]
2296 fn test_action_type_parser1() {
2297 let src = r#"
2298 {
2299 "appliesTo" : {
2300 "resourceTypes": ["Album"],
2301 "principalTypes": ["User"]
2302 },
2303 "memberOf": [{"id": "readWrite"}]
2304 }
2305 "#;
2306 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2307 let spec = ApplySpec {
2308 resource_types: vec!["Album".parse().unwrap()],
2309 principal_types: vec!["User".parse().unwrap()],
2310 context: AttributesOrContext::default(),
2311 };
2312 assert_eq!(at.applies_to, Some(spec));
2313 assert_eq!(
2314 at.member_of,
2315 Some(vec![ActionEntityUID {
2316 ty: None,
2317 id: "readWrite".into(),
2318 #[cfg(feature = "extended-schema")]
2319 loc: None
2320 }])
2321 );
2322 }
2323
2324 #[test]
2325 fn test_action_type_parser2() {
2326 let src = r#"
2327 { }
2328 "#;
2329 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2330 assert_eq!(at.applies_to, None);
2331 assert!(at.member_of.is_none());
2332 }
2333
2334 #[test]
2335 fn test_schema_file_parser() {
2336 let src = serde_json::json!(
2337 {
2338 "entityTypes": {
2339
2340 "User": {
2341 "memberOfTypes": ["UserGroup"]
2342 },
2343 "Photo": {
2344 "memberOfTypes": ["Album", "Account"]
2345 },
2346
2347 "Album": {
2348 "memberOfTypes": ["Album", "Account"]
2349 },
2350 "Account": { },
2351 "UserGroup": { }
2352 },
2353
2354 "actions": {
2355 "readOnly": { },
2356 "readWrite": { },
2357 "createAlbum": {
2358 "appliesTo" : {
2359 "resourceTypes": ["Account", "Album"],
2360 "principalTypes": ["User"]
2361 },
2362 "memberOf": [{"id": "readWrite"}]
2363 },
2364 "addPhotoToAlbum": {
2365 "appliesTo" : {
2366 "resourceTypes": ["Album"],
2367 "principalTypes": ["User"]
2368 },
2369 "memberOf": [{"id": "readWrite"}]
2370 },
2371 "viewPhoto": {
2372 "appliesTo" : {
2373 "resourceTypes": ["Photo"],
2374 "principalTypes": ["User"]
2375 },
2376 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2377 },
2378 "viewComments": {
2379 "appliesTo" : {
2380 "resourceTypes": ["Photo"],
2381 "principalTypes": ["User"]
2382 },
2383 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2384 }
2385 }
2386 });
2387 let schema_file: NamespaceDefinition<RawName> =
2388 serde_json::from_value(src).expect("Parse Error");
2389
2390 assert_eq!(schema_file.entity_types.len(), 5);
2391 assert_eq!(schema_file.actions.len(), 6);
2392 }
2393
2394 #[test]
2395 fn test_parse_namespaces() {
2396 let src = r#"
2397 {
2398 "foo::foo::bar::baz": {
2399 "entityTypes": {},
2400 "actions": {}
2401 }
2402 }"#;
2403 let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
2404 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
2405 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
2406 }
2407
2408 #[test]
2409 #[should_panic(expected = "unknown field `requiredddddd`")]
2410 fn test_schema_file_with_misspelled_required() {
2411 let src = serde_json::json!(
2412 {
2413 "entityTypes": {
2414 "User": {
2415 "shape": {
2416 "type": "Record",
2417 "attributes": {
2418 "favorite": {
2419 "type": "Entity",
2420 "name": "Photo",
2421 "requiredddddd": false
2422 }
2423 }
2424 }
2425 }
2426 },
2427 "actions": {}
2428 });
2429 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2430 println!("{schema:#?}");
2431 }
2432
2433 #[test]
2434 #[should_panic(expected = "unknown field `nameeeeee`")]
2435 fn test_schema_file_with_misspelled_field() {
2436 let src = serde_json::json!(
2437 {
2438 "entityTypes": {
2439 "User": {
2440 "shape": {
2441 "type": "Record",
2442 "attributes": {
2443 "favorite": {
2444 "type": "Entity",
2445 "nameeeeee": "Photo",
2446 }
2447 }
2448 }
2449 }
2450 },
2451 "actions": {}
2452 });
2453 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2454 println!("{schema:#?}");
2455 }
2456
2457 #[test]
2458 #[should_panic(expected = "unknown field `extra`")]
2459 fn test_schema_file_with_extra_field() {
2460 let src = serde_json::json!(
2461 {
2462 "entityTypes": {
2463 "User": {
2464 "shape": {
2465 "type": "Record",
2466 "attributes": {
2467 "favorite": {
2468 "type": "Entity",
2469 "name": "Photo",
2470 "extra": "Should not exist"
2471 }
2472 }
2473 }
2474 }
2475 },
2476 "actions": {}
2477 });
2478 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2479 println!("{schema:#?}");
2480 }
2481
2482 #[test]
2483 #[should_panic(expected = "unknown field `memberOfTypes`")]
2484 fn test_schema_file_with_misplaced_field() {
2485 let src = serde_json::json!(
2486 {
2487 "entityTypes": {
2488 "User": {
2489 "shape": {
2490 "memberOfTypes": [],
2491 "type": "Record",
2492 "attributes": {
2493 "favorite": {
2494 "type": "Entity",
2495 "name": "Photo",
2496 }
2497 }
2498 }
2499 }
2500 },
2501 "actions": {}
2502 });
2503 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2504 println!("{schema:#?}");
2505 }
2506
2507 #[test]
2508 fn schema_file_with_missing_field() {
2509 let src = serde_json::json!(
2510 {
2511 "": {
2512 "entityTypes": {
2513 "User": {
2514 "shape": {
2515 "type": "Record",
2516 "attributes": {
2517 "favorite": {
2518 "type": "Entity",
2519 }
2520 }
2521 }
2522 }
2523 },
2524 "actions": {}
2525 }
2526 });
2527 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2528 assert_matches!(schema, Err(e) => {
2529 expect_err(
2530 &src,
2531 &miette::Report::new(e),
2532 &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2533 .build());
2534 });
2535 }
2536
2537 #[test]
2538 #[should_panic(expected = "missing field `type`")]
2539 fn schema_file_with_missing_type() {
2540 let src = serde_json::json!(
2541 {
2542 "entityTypes": {
2543 "User": {
2544 "shape": { }
2545 }
2546 },
2547 "actions": {}
2548 });
2549 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2550 println!("{schema:#?}");
2551 }
2552
2553 #[test]
2554 fn schema_file_unexpected_malformed_attribute() {
2555 let src = serde_json::json!(
2556 { "": {
2557 "entityTypes": {
2558 "User": {
2559 "shape": {
2560 "type": "Record",
2561 "attributes": {
2562 "a": {
2563 "type": "Long",
2564 "attributes": {
2565 "b": {"foo": "bar"}
2566 }
2567 }
2568 }
2569 }
2570 }
2571 },
2572 "actions": {}
2573 }});
2574 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2575 assert_matches!(schema, Err(e) => {
2576 expect_err(
2577 "",
2578 &miette::Report::new(e),
2579 &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2580 );
2581 });
2582 }
2583
2584 #[test]
2585 fn error_in_nested_attribute_fails_fast_top_level_attr() {
2586 let src = serde_json::json!(
2587 {
2588 "": {
2589 "entityTypes": {
2590 "User": {
2591 "shape": {
2592 "type": "Record",
2593 "attributes": {
2594 "foo": {
2595 "type": "Record",
2596 "element": { "type": "Long" }
2598 },
2599 "bar": { "type": "Long" }
2600 }
2601 }
2602 }
2603 },
2604 "actions": {}
2605 }
2606 }
2607 );
2608
2609 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2610 assert_matches!(schema, Err(e) => {
2611 expect_err(
2612 "",
2613 &miette::Report::new(e),
2614 &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2615 );
2616 });
2617 }
2618
2619 #[test]
2620 fn error_in_nested_attribute_fails_fast_nested_attr() {
2621 let src = serde_json::json!(
2622 { "": {
2623 "entityTypes": {
2624 "a": {
2625 "shape": {
2626 "type": "Record",
2627 "attributes": {
2628 "foo": { "type": "Entity", "name": "b" },
2629 "baz": { "type": "Record",
2630 "attributes": {
2631 "z": "Boolean"
2633 }
2634 }
2635 }
2636 }
2637 },
2638 "b": {}
2639 }
2640 } }
2641 );
2642
2643 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2644 assert_matches!(schema, Err(e) => {
2645 expect_err(
2646 "",
2647 &miette::Report::new(e),
2648 &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2649 );
2650 });
2651 }
2652
2653 #[test]
2654 fn missing_namespace() {
2655 let src = r#"
2656 {
2657 "entityTypes": { "User": { } },
2658 "actions": {}
2659 }"#;
2660 let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2661 assert_matches!(schema, Err(e) => {
2662 expect_err(
2663 src,
2664 &miette::Report::new(e),
2665 &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations` at line 3 column 35"#)
2666 .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2667 .build());
2668 });
2669 }
2670}
2671
2672#[cfg(test)]
2674mod strengthened_types {
2675 use cool_asserts::assert_matches;
2676
2677 use super::{
2678 ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
2679 };
2680
2681 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
2684 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
2685 }
2686
2687 #[test]
2688 fn invalid_namespace() {
2689 let src = serde_json::json!(
2690 {
2691 "\n" : {
2692 "entityTypes": {},
2693 "actions": {}
2694 }
2695 });
2696 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2697 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
2698
2699 let src = serde_json::json!(
2700 {
2701 "1" : {
2702 "entityTypes": {},
2703 "actions": {}
2704 }
2705 });
2706 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2707 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
2708
2709 let src = serde_json::json!(
2710 {
2711 "*1" : {
2712 "entityTypes": {},
2713 "actions": {}
2714 }
2715 });
2716 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2717 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
2718
2719 let src = serde_json::json!(
2720 {
2721 "::" : {
2722 "entityTypes": {},
2723 "actions": {}
2724 }
2725 });
2726 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2727 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
2728
2729 let src = serde_json::json!(
2730 {
2731 "A::" : {
2732 "entityTypes": {},
2733 "actions": {}
2734 }
2735 });
2736 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2737 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
2738 }
2739
2740 #[test]
2741 fn invalid_common_type() {
2742 let src = serde_json::json!(
2743 {
2744 "entityTypes": {},
2745 "actions": {},
2746 "commonTypes": {
2747 "" : {
2748 "type": "String"
2749 }
2750 }
2751 });
2752 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2753 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2754
2755 let src = serde_json::json!(
2756 {
2757 "entityTypes": {},
2758 "actions": {},
2759 "commonTypes": {
2760 "~" : {
2761 "type": "String"
2762 }
2763 }
2764 });
2765 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2766 assert_error_matches(schema, "invalid id `~`: invalid token");
2767
2768 let src = serde_json::json!(
2769 {
2770 "entityTypes": {},
2771 "actions": {},
2772 "commonTypes": {
2773 "A::B" : {
2774 "type": "String"
2775 }
2776 }
2777 });
2778 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2779 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2780 }
2781
2782 #[test]
2783 fn invalid_entity_type() {
2784 let src = serde_json::json!(
2785 {
2786 "entityTypes": {
2787 "": {}
2788 },
2789 "actions": {}
2790 });
2791 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2792 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2793
2794 let src = serde_json::json!(
2795 {
2796 "entityTypes": {
2797 "*": {}
2798 },
2799 "actions": {}
2800 });
2801 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2802 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
2803
2804 let src = serde_json::json!(
2805 {
2806 "entityTypes": {
2807 "A::B": {}
2808 },
2809 "actions": {}
2810 });
2811 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2812 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2813 }
2814
2815 #[test]
2816 fn invalid_member_of_types() {
2817 let src = serde_json::json!(
2818 {
2819 "memberOfTypes": [""]
2820 });
2821 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2822 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2823
2824 let src = serde_json::json!(
2825 {
2826 "memberOfTypes": ["*"]
2827 });
2828 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2829 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2830
2831 let src = serde_json::json!(
2832 {
2833 "memberOfTypes": ["A::"]
2834 });
2835 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2836 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2837
2838 let src = serde_json::json!(
2839 {
2840 "memberOfTypes": ["::A"]
2841 });
2842 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2843 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2844 }
2845
2846 #[test]
2847 fn invalid_apply_spec() {
2848 let src = serde_json::json!(
2849 {
2850 "resourceTypes": [""]
2851 });
2852 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2853 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2854
2855 let src = serde_json::json!(
2856 {
2857 "resourceTypes": ["*"]
2858 });
2859 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2860 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2861
2862 let src = serde_json::json!(
2863 {
2864 "resourceTypes": ["A::"]
2865 });
2866 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2867 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2868
2869 let src = serde_json::json!(
2870 {
2871 "resourceTypes": ["::A"]
2872 });
2873 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2874 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2875 }
2876
2877 #[test]
2878 fn invalid_schema_entity_types() {
2879 let src = serde_json::json!(
2880 {
2881 "type": "Entity",
2882 "name": ""
2883 });
2884 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2885 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
2886
2887 let src = serde_json::json!(
2888 {
2889 "type": "Entity",
2890 "name": "*"
2891 });
2892 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2893 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
2894
2895 let src = serde_json::json!(
2896 {
2897 "type": "Entity",
2898 "name": "::A"
2899 });
2900 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2901 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
2902
2903 let src = serde_json::json!(
2904 {
2905 "type": "Entity",
2906 "name": "A::"
2907 });
2908 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2909 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
2910 }
2911
2912 #[test]
2913 fn invalid_action_euid() {
2914 let src = serde_json::json!(
2915 {
2916 "id": "action",
2917 "type": ""
2918 });
2919 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2920 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2921
2922 let src = serde_json::json!(
2923 {
2924 "id": "action",
2925 "type": "*"
2926 });
2927 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2928 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2929
2930 let src = serde_json::json!(
2931 {
2932 "id": "action",
2933 "type": "Action::"
2934 });
2935 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2936 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
2937
2938 let src = serde_json::json!(
2939 {
2940 "id": "action",
2941 "type": "::Action"
2942 });
2943 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2944 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
2945 }
2946
2947 #[test]
2948 fn invalid_schema_common_types() {
2949 let src = serde_json::json!(
2950 {
2951 "type": ""
2952 });
2953 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2954 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
2955
2956 let src = serde_json::json!(
2957 {
2958 "type": "*"
2959 });
2960 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2961 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
2962
2963 let src = serde_json::json!(
2964 {
2965 "type": "::A"
2966 });
2967 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2968 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
2969
2970 let src = serde_json::json!(
2971 {
2972 "type": "A::"
2973 });
2974 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2975 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
2976 }
2977
2978 #[test]
2979 fn invalid_schema_extension_types() {
2980 let src = serde_json::json!(
2981 {
2982 "type": "Extension",
2983 "name": ""
2984 });
2985 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2986 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
2987
2988 let src = serde_json::json!(
2989 {
2990 "type": "Extension",
2991 "name": "*"
2992 });
2993 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2994 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
2995
2996 let src = serde_json::json!(
2997 {
2998 "type": "Extension",
2999 "name": "__cedar::decimal"
3000 });
3001 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3002 assert_error_matches(
3003 schema,
3004 "invalid extension type `__cedar::decimal`: unexpected token `::`",
3005 );
3006
3007 let src = serde_json::json!(
3008 {
3009 "type": "Extension",
3010 "name": "__cedar::"
3011 });
3012 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3013 assert_error_matches(
3014 schema,
3015 "invalid extension type `__cedar::`: unexpected token `::`",
3016 );
3017
3018 let src = serde_json::json!(
3019 {
3020 "type": "Extension",
3021 "name": "::__cedar"
3022 });
3023 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3024 assert_error_matches(
3025 schema,
3026 "invalid extension type `::__cedar`: unexpected token `::`",
3027 );
3028 }
3029}
3030
3031#[cfg(test)]
3033mod entity_tags {
3034 use super::*;
3035 use crate::test_utils::{expect_err, ExpectedErrorMessageBuilder};
3036 use cool_asserts::assert_matches;
3037 use serde_json::json;
3038
3039 #[track_caller]
3041 fn example_json_schema() -> serde_json::Value {
3042 json!({"": {
3043 "entityTypes": {
3044 "User" : {
3045 "shape" : {
3046 "type" : "Record",
3047 "attributes" : {
3048 "jobLevel" : {
3049 "type" : "Long"
3050 },
3051 }
3052 },
3053 "tags" : {
3054 "type" : "Set",
3055 "element": { "type": "String" }
3056 }
3057 },
3058 "Document" : {
3059 "shape" : {
3060 "type" : "Record",
3061 "attributes" : {
3062 "owner" : {
3063 "type" : "Entity",
3064 "name" : "User"
3065 },
3066 }
3067 },
3068 "tags" : {
3069 "type" : "Set",
3070 "element": { "type": "String" }
3071 }
3072 }
3073 },
3074 "actions": {}
3075 }})
3076 }
3077
3078 #[test]
3079 fn roundtrip() {
3080 let json = example_json_schema();
3081 let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
3082 let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
3083 assert_eq!(json, serialized_json_schema);
3084 }
3085
3086 #[test]
3087 fn basic() {
3088 let json = example_json_schema();
3089 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3090 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3091 assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3092 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });});
3094 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(doc), ..} => {
3095 assert_matches!(&doc.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3096 assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); });
3098 })})
3099 }
3100
3101 #[test]
3103 fn tag_type_is_common_type() {
3104 let json = json!({"": {
3105 "commonTypes": {
3106 "T": { "type": "String" },
3107 },
3108 "entityTypes": {
3109 "User" : {
3110 "shape" : {
3111 "type" : "Record",
3112 "attributes" : {
3113 "jobLevel" : {
3114 "type" : "Long"
3115 },
3116 }
3117 },
3118 "tags" : { "type" : "T" },
3119 },
3120 },
3121 "actions": {}
3122 }});
3123 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3124 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType {kind: EntityTypeKind::Standard(user), ..} => {
3125 assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name, .. }) => {
3126 assert_eq!(&format!("{type_name}"), "T");
3127 });
3128 })});
3129 }
3130
3131 #[test]
3133 fn tag_type_is_entity_type() {
3134 let json = json!({"": {
3135 "entityTypes": {
3136 "User" : {
3137 "shape" : {
3138 "type" : "Record",
3139 "attributes" : {
3140 "jobLevel" : {
3141 "type" : "Long"
3142 },
3143 }
3144 },
3145 "tags" : { "type" : "Entity", "name": "User" },
3146 },
3147 },
3148 "actions": {}
3149 }});
3150 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3151 assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3152 assert_matches!(&user.tags, Some(Type::Type{ ty: TypeVariant::Entity{ name }, ..}) => {
3153 assert_eq!(&format!("{name}"), "User");
3154 });
3155 })});
3156 }
3157
3158 #[test]
3160 fn bad_tags() {
3161 let json = json!({"": {
3162 "entityTypes": {
3163 "User": {
3164 "shape": {
3165 "type": "Record",
3166 "attributes": {
3167 "jobLevel": {
3168 "type": "Long"
3169 },
3170 },
3171 "tags": { "type": "String" },
3172 }
3173 },
3174 },
3175 "actions": {}
3176 }});
3177 assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
3178 expect_err(
3179 &json,
3180 &miette::Report::new(e),
3181 &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
3182 .build(),
3183 );
3184 });
3185 }
3186}
3187
3188#[cfg(test)]
3190mod test_json_roundtrip {
3191 use super::*;
3192
3193 #[track_caller] fn roundtrip(schema: &Fragment<RawName>) {
3195 let json = serde_json::to_value(schema.clone()).unwrap();
3196 let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
3197 assert_eq!(schema, &new_schema);
3198 }
3199
3200 #[test]
3201 fn empty_namespace() {
3202 let fragment = Fragment(BTreeMap::from([(None, NamespaceDefinition::new([], []))]));
3203 roundtrip(&fragment);
3204 }
3205
3206 #[test]
3207 fn nonempty_namespace() {
3208 let fragment = Fragment(BTreeMap::from([(
3209 Some("a".parse().unwrap()),
3210 NamespaceDefinition::new([], []),
3211 )]));
3212 roundtrip(&fragment);
3213 }
3214
3215 #[test]
3216 fn nonempty_entity_types() {
3217 let fragment = Fragment(BTreeMap::from([(
3218 None,
3219 NamespaceDefinition::new(
3220 [(
3221 "a".parse().unwrap(),
3222 EntityType {
3223 kind: EntityTypeKind::Standard(StandardEntityType {
3224 member_of_types: vec!["a".parse().unwrap()],
3225 shape: AttributesOrContext(Type::Type {
3226 ty: TypeVariant::Record(RecordType {
3227 attributes: BTreeMap::new(),
3228 additional_attributes: false,
3229 }),
3230 loc: None,
3231 }),
3232 tags: None,
3233 }),
3234 annotations: Annotations::new(),
3235 loc: None,
3236 },
3237 )],
3238 [(
3239 "action".into(),
3240 ActionType {
3241 attributes: None,
3242 applies_to: Some(ApplySpec {
3243 resource_types: vec!["a".parse().unwrap()],
3244 principal_types: vec!["a".parse().unwrap()],
3245 context: AttributesOrContext(Type::Type {
3246 ty: TypeVariant::Record(RecordType {
3247 attributes: BTreeMap::new(),
3248 additional_attributes: false,
3249 }),
3250 loc: None,
3251 }),
3252 }),
3253 member_of: None,
3254 annotations: Annotations::new(),
3255 loc: None,
3256 #[cfg(feature = "extended-schema")]
3257 defn_loc: None,
3258 },
3259 )],
3260 ),
3261 )]));
3262 roundtrip(&fragment);
3263 }
3264
3265 #[test]
3266 fn multiple_namespaces() {
3267 let fragment = Fragment(BTreeMap::from([
3268 (
3269 Some("foo".parse().unwrap()),
3270 NamespaceDefinition::new(
3271 [(
3272 "a".parse().unwrap(),
3273 EntityType {
3274 kind: EntityTypeKind::Standard(StandardEntityType {
3275 member_of_types: vec!["a".parse().unwrap()],
3276 shape: AttributesOrContext(Type::Type {
3277 ty: TypeVariant::Record(RecordType {
3278 attributes: BTreeMap::new(),
3279 additional_attributes: false,
3280 }),
3281 loc: None,
3282 }),
3283 tags: None,
3284 }),
3285 annotations: Annotations::new(),
3286 loc: None,
3287 },
3288 )],
3289 [],
3290 ),
3291 ),
3292 (
3293 None,
3294 NamespaceDefinition::new(
3295 [],
3296 [(
3297 "action".into(),
3298 ActionType {
3299 attributes: None,
3300 applies_to: Some(ApplySpec {
3301 resource_types: vec!["foo::a".parse().unwrap()],
3302 principal_types: vec!["foo::a".parse().unwrap()],
3303 context: AttributesOrContext(Type::Type {
3304 ty: TypeVariant::Record(RecordType {
3305 attributes: BTreeMap::new(),
3306 additional_attributes: false,
3307 }),
3308 loc: None,
3309 }),
3310 }),
3311 member_of: None,
3312 annotations: Annotations::new(),
3313 loc: None,
3314 #[cfg(feature = "extended-schema")]
3315 defn_loc: None,
3316 },
3317 )],
3318 ),
3319 ),
3320 ]));
3321 roundtrip(&fragment);
3322 }
3323}
3324
3325#[cfg(test)]
3330mod test_duplicates_error {
3331 use super::*;
3332
3333 #[test]
3334 #[should_panic(expected = "invalid entry: found duplicate key")]
3335 fn namespace() {
3336 let src = r#"{
3337 "Foo": {
3338 "entityTypes" : {},
3339 "actions": {}
3340 },
3341 "Foo": {
3342 "entityTypes" : {},
3343 "actions": {}
3344 }
3345 }"#;
3346 Fragment::from_json_str(src).unwrap();
3347 }
3348
3349 #[test]
3350 #[should_panic(expected = "invalid entry: found duplicate key")]
3351 fn entity_type() {
3352 let src = r#"{
3353 "Foo": {
3354 "entityTypes" : {
3355 "Bar": {},
3356 "Bar": {}
3357 },
3358 "actions": {}
3359 }
3360 }"#;
3361 Fragment::from_json_str(src).unwrap();
3362 }
3363
3364 #[test]
3365 #[should_panic(expected = "invalid entry: found duplicate key")]
3366 fn action() {
3367 let src = r#"{
3368 "Foo": {
3369 "entityTypes" : {},
3370 "actions": {
3371 "Bar": {},
3372 "Bar": {}
3373 }
3374 }
3375 }"#;
3376 Fragment::from_json_str(src).unwrap();
3377 }
3378
3379 #[test]
3380 #[should_panic(expected = "invalid entry: found duplicate key")]
3381 fn common_types() {
3382 let src = r#"{
3383 "Foo": {
3384 "entityTypes" : {},
3385 "actions": { },
3386 "commonTypes": {
3387 "Bar": {"type": "Long"},
3388 "Bar": {"type": "String"}
3389 }
3390 }
3391 }"#;
3392 Fragment::from_json_str(src).unwrap();
3393 }
3394
3395 #[test]
3396 #[should_panic(expected = "invalid entry: found duplicate key")]
3397 fn record_type() {
3398 let src = r#"{
3399 "Foo": {
3400 "entityTypes" : {
3401 "Bar": {
3402 "shape": {
3403 "type": "Record",
3404 "attributes": {
3405 "Baz": {"type": "Long"},
3406 "Baz": {"type": "String"}
3407 }
3408 }
3409 }
3410 },
3411 "actions": { }
3412 }
3413 }"#;
3414 Fragment::from_json_str(src).unwrap();
3415 }
3416
3417 #[test]
3418 #[should_panic(expected = "missing field `resourceTypes`")]
3419 fn missing_resource() {
3420 let src = r#"{
3421 "Foo": {
3422 "entityTypes" : {},
3423 "actions": {
3424 "foo" : {
3425 "appliesTo" : {
3426 "principalTypes" : ["a"]
3427 }
3428 }
3429 }
3430 }
3431 }"#;
3432 Fragment::from_json_str(src).unwrap();
3433 }
3434
3435 #[test]
3436 #[should_panic(expected = "missing field `principalTypes`")]
3437 fn missing_principal() {
3438 let src = r#"{
3439 "Foo": {
3440 "entityTypes" : {},
3441 "actions": {
3442 "foo" : {
3443 "appliesTo" : {
3444 "resourceTypes" : ["a"]
3445 }
3446 }
3447 }
3448 }
3449 }"#;
3450 Fragment::from_json_str(src).unwrap();
3451 }
3452
3453 #[test]
3454 #[should_panic(expected = "missing field `resourceTypes`")]
3455 fn missing_both() {
3456 let src = r#"{
3457 "Foo": {
3458 "entityTypes" : {},
3459 "actions": {
3460 "foo" : {
3461 "appliesTo" : {
3462 }
3463 }
3464 }
3465 }
3466 }"#;
3467 Fragment::from_json_str(src).unwrap();
3468 }
3469}
3470
3471#[cfg(test)]
3472mod annotations {
3473 use crate::validator::RawName;
3474 use cool_asserts::assert_matches;
3475
3476 use super::Fragment;
3477
3478 #[test]
3479 fn empty_namespace() {
3480 let src = serde_json::json!(
3481 {
3482 "" : {
3483 "entityTypes": {},
3484 "actions": {},
3485 "annotations": {
3486 "doc": "this is a doc"
3487 }
3488 }
3489 });
3490 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3491 assert_matches!(schema, Err(err) => {
3492 assert_eq!(&err.to_string(), "annotations are not allowed on the empty namespace");
3493 });
3494 }
3495
3496 #[test]
3497 fn basic() {
3498 let src = serde_json::json!(
3499 {
3500 "N" : {
3501 "entityTypes": {},
3502 "actions": {},
3503 "annotations": {
3504 "doc": "this is a doc"
3505 }
3506 }
3507 });
3508 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3509 assert_matches!(schema, Ok(_));
3510
3511 let src = serde_json::json!(
3512 {
3513 "N" : {
3514 "entityTypes": {
3515 "a": {
3516 "annotations": {
3517 "a": "",
3518 "d": null,
3520 "b": "c",
3521 },
3522 "shape": {
3523 "type": "Long",
3524 }
3525 }
3526 },
3527 "actions": {},
3528 "annotations": {
3529 "doc": "this is a doc"
3530 }
3531 }
3532 });
3533 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3534 assert_matches!(schema, Ok(_));
3535
3536 let src = serde_json::json!(
3537 {
3538 "N" : {
3539 "entityTypes": {
3540 "a": {
3541 "annotations": {
3542 "a": "",
3543 "b": "c",
3544 },
3545 "shape": {
3546 "type": "Long",
3547 }
3548 }
3549 },
3550 "actions": {
3551 "a": {
3552 "annotations": {
3553 "doc": "this is a doc"
3554 },
3555 "appliesTo": {
3556 "principalTypes": ["A"],
3557 "resourceTypes": ["B"],
3558 }
3559 },
3560 },
3561 "annotations": {
3562 "doc": "this is a doc"
3563 }
3564 }
3565 });
3566 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3567 assert_matches!(schema, Ok(_));
3568
3569 let src = serde_json::json!({
3570 "N": {
3571 "entityTypes": {},
3572 "actions": {},
3573 "commonTypes": {
3574 "Task": {
3575 "annotations": {
3576 "doc": "a common type representing a task"
3577 },
3578 "type": "Record",
3579 "attributes": {
3580 "id": {
3581 "type": "Long",
3582 "annotations": {
3583 "doc": "task id"
3584 }
3585 },
3586 "name": {
3587 "type": "String"
3588 },
3589 "state": {
3590 "type": "String"
3591 }
3592 }
3593 }}}});
3594 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3595 assert_matches!(schema, Ok(_));
3596
3597 let src = serde_json::json!({
3598 "N": {
3599 "entityTypes": {
3600 "User" : {
3601 "shape" : {
3602 "type" : "Record",
3603 "attributes" : {
3604 "name" : {
3605 "annotations": {
3606 "a": null,
3607 },
3608 "type" : "String"
3609 },
3610 "age" : {
3611 "type" : "Long"
3612 }
3613 }
3614 }
3615 }
3616 },
3617 "actions": {},
3618 "commonTypes": {}
3619 }});
3620 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3621 assert_matches!(schema, Ok(_));
3622
3623 let src = serde_json::json!({
3625 "N": {
3626 "entityTypes": {
3627 "User" : {
3628 "shape" : {
3629 "type" : "Record",
3630 "attributes" : {
3631 "name" : {
3632 "annotations": {
3633 "first_layer": "b"
3634 },
3635 "type" : "Record",
3636 "attributes": {
3637 "a": {
3638 "type": "Record",
3639 "annotations": {
3640 "second_layer": "d"
3641 },
3642 "attributes": {
3643 "...": {
3644 "annotations": {
3645 "last_layer": null,
3646 },
3647 "type": "Long"
3648 }
3649 }
3650 }
3651 }
3652 },
3653 "age" : {
3654 "type" : "Long"
3655 }
3656 }
3657 }
3658 }
3659 },
3660 "actions": {},
3661 "commonTypes": {}
3662 }});
3663 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3664 assert_matches!(schema, Ok(_));
3665 }
3666
3667 #[track_caller]
3668 fn test_unknown_fields(src: serde_json::Value, field: &str, expected: &str) {
3669 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3670 assert_matches!(schema, Err(errs) => {
3671 assert_eq!(errs.to_string(), format!("unknown field {field}, expected one of {expected}"));
3672 });
3673 }
3674
3675 const ENTITY_TYPE_EXPECTED_ATTRIBUTES: &str =
3676 "`memberOfTypes`, `shape`, `tags`, `enum`, `annotations`";
3677 const NAMESPACE_EXPECTED_ATTRIBUTES: &str =
3678 "`commonTypes`, `entityTypes`, `actions`, `annotations`";
3679 const ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES: &str =
3680 "`type`, `element`, `attributes`, `additionalAttributes`, `name`";
3681 const APPLIES_TO_EXPECTED_ATTRIBUTES: &str = "`resourceTypes`, `principalTypes`, `context`";
3682
3683 #[test]
3684 fn unknown_fields() {
3685 let src = serde_json::json!(
3686 {
3687 "N": {
3688 "entityTypes": {
3689 "UserGroup": {
3690 "shape44": {
3691 "type": "Record",
3692 "attributes": {}
3693 },
3694 "memberOfTypes": [
3695 "UserGroup"
3696 ]
3697 }},
3698 "actions": {},
3699 }});
3700 test_unknown_fields(src, "`shape44`", ENTITY_TYPE_EXPECTED_ATTRIBUTES);
3701
3702 let src = serde_json::json!(
3703 {
3704 "N": {
3705 "entityTypes": {},
3706 "actions": {},
3707 "commonTypes": {
3708 "C": {
3709 "type": "Set",
3710 "element": {
3711 "annotations": {
3712 "doc": "this is a doc"
3713 },
3714 "type": "Long"
3715 }
3716 }
3717 }}});
3718 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3719
3720 let src = serde_json::json!(
3721 {
3722 "N": {
3723 "entityTypes": {},
3724 "actions": {},
3725 "commonTypes": {
3726 "C": {
3727 "type": "Long",
3728 "foo": 1,
3729 "annotations": {
3730 "doc": "this is a doc"
3731 },
3732 }}}});
3733 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3734
3735 let src = serde_json::json!(
3736 {
3737 "N": {
3738 "entityTypes": {},
3739 "actions": {},
3740 "commonTypes": {
3741 "C": {
3742 "type": "Record",
3743 "attributes": {
3744 "a": {
3745 "annotations": {
3746 "doc": "this is a doc"
3747 },
3748 "type": "Long",
3749 "foo": 2,
3750 "required": true,
3751 }
3752 },
3753 }}}});
3754 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3755
3756 let src = serde_json::json!(
3757 {
3758 "N": {
3759 "entityTypes": {},
3760 "actions": {},
3761 "commonTypes": {
3762 "C": {
3763 "type": "Record",
3764 "attributes": {
3765 "a": {
3766 "annotations": {
3767 "doc": "this is a doc"
3768 },
3769 "type": "Record",
3770 "attributes": {
3771 "b": {
3772 "annotations": {
3773 "doc": "this is a doc"
3774 },
3775 "type": "Long",
3776 "bar": 3,
3777 },
3778 },
3779 "required": true,
3780 }
3781 },
3782 }}}});
3783 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3784
3785 let src = serde_json::json!(
3786 {
3787 "N": {
3788 "entityTypes": {
3789 "UserGroup": {
3790 "shape": {
3791 "annotations": {
3792 "doc": "this is a doc"
3793 },
3794 "type": "Record",
3795 "attributes": {}
3796 },
3797 "memberOfTypes": [
3798 "UserGroup"
3799 ]
3800 }},
3801 "actions": {},
3802 }});
3803 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3804
3805 let src = serde_json::json!(
3806 {
3807 "N": {
3808 "entityTypes": {},
3809 "actions": {
3810 "a": {
3811 "appliesTo": {
3812 "annotations": {
3813 "doc": "this is a doc"
3814 },
3815 "principalTypes": ["A"],
3816 "resourceTypes": ["B"],
3817 }
3818 },
3819 },
3820 }});
3821 test_unknown_fields(src, "`annotations`", APPLIES_TO_EXPECTED_ATTRIBUTES);
3822
3823 let src = serde_json::json!(
3824 {
3825 "N" : {
3826 "entityTypes": {},
3827 "actions": {},
3828 "foo": "",
3829 "annotations": {
3830 "doc": "this is a doc"
3831 }
3832 }
3833 });
3834 test_unknown_fields(src, "`foo`", NAMESPACE_EXPECTED_ATTRIBUTES);
3835
3836 let src = serde_json::json!(
3837 {
3838 "" : {
3839 "entityTypes": {},
3840 "actions": {},
3841 "commonTypes": {
3842 "a": {
3843 "type": "Long",
3844 "annotations": {
3845 "foo": ""
3846 },
3847 "bar": 1,
3848 }
3849 }
3850 }
3851 });
3852 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3853
3854 let src = serde_json::json!(
3855 {
3856 "N" : {
3857 "entityTypes": {},
3858 "actions": {},
3859 "commonTypes": {
3860 "a": {
3861 "type": "Record",
3862 "annotations": {
3863 "foo": ""
3864 },
3865 "attributes": {
3866 "a": {
3867 "bar": 1,
3868 "type": "Long"
3869 }
3870 }
3871 }
3872 }
3873 }
3874 });
3875 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3876 }
3877}
3878
3879#[cfg(test)]
3880mod ord {
3881 use super::{InternalName, RawName, Type, TypeVariant};
3882 use std::collections::BTreeSet;
3883
3884 #[test]
3886 #[allow(clippy::collection_is_never_read)]
3887 fn type_ord() {
3888 let mut set: BTreeSet<Type<RawName>> = BTreeSet::default();
3889 set.insert(Type::Type {
3890 ty: TypeVariant::String,
3891 loc: None,
3892 });
3893 let mut set: BTreeSet<Type<InternalName>> = BTreeSet::default();
3894 set.insert(Type::Type {
3895 ty: TypeVariant::String,
3896 loc: None,
3897 });
3898 }
3899}
3900
3901#[cfg(test)]
3902#[allow(clippy::indexing_slicing)]
3904mod enumerated_entity_types {
3905 use cool_asserts::assert_matches;
3906
3907 use crate::validator::{
3908 json_schema::{EntityType, EntityTypeKind, Fragment},
3909 RawName,
3910 };
3911
3912 #[test]
3913 fn basic() {
3914 let src = serde_json::json!({
3915 "": {
3916 "entityTypes": {
3917 "Foo": {
3918 "enum": ["foo", "bar"],
3919 "annotations": {
3920 "a": "b",
3921 }
3922 },
3923 },
3924 "actions": {},
3925 }
3926 });
3927 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3928 assert_matches!(schema, Ok(frag) => {
3929 assert_matches!(&frag.0[&None].entity_types[&"Foo".parse().unwrap()], EntityType {
3930 kind: EntityTypeKind::Enum {choices},
3931 ..
3932 } => {
3933 assert_eq!(Vec::from(choices.clone()), ["foo", "bar"]);
3934 });
3935 });
3936
3937 let src = serde_json::json!({
3938 "": {
3939 "entityTypes": {
3940 "Foo": {
3941 "enum": [],
3942 "annotations": {
3943 "a": "b",
3944 }
3945 },
3946 },
3947 "actions": {},
3948 }
3949 });
3950 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3951 assert_matches!(schema, Err(errs) => {
3952 assert_eq!(errs.to_string(), "the vector provided was empty, NonEmpty needs at least one element");
3954 });
3955
3956 let src = serde_json::json!({
3957 "": {
3958 "entityTypes": {
3959 "Foo": {
3960 "enum": null,
3961 },
3962 },
3963 "actions": {},
3964 }
3965 });
3966 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3967 assert_matches!(schema, Err(errs) => {
3968 assert_eq!(errs.to_string(), "invalid type: null, expected a sequence");
3969 });
3970
3971 let src = serde_json::json!({
3972 "": {
3973 "entityTypes": {
3974 "Foo": {
3975 "enum": ["foo"],
3976 "memberOfTypes": ["bar"],
3977 },
3978 },
3979 "actions": {},
3980 }
3981 });
3982 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3983 assert_matches!(schema, Err(errs) => {
3984 assert_eq!(errs.to_string(), "unexpected field: memberOfTypes");
3985 });
3986 }
3987}