1use cedar_policy_core::{
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 nonempty::nonempty;
29use serde::{
30 de::{MapAccess, Visitor},
31 ser::SerializeMap,
32 Deserialize, Deserializer, Serialize, Serializer,
33};
34use serde_with::serde_as;
35use smol_str::{SmolStr, ToSmolStr};
36use std::{
37 collections::{BTreeMap, HashMap, HashSet},
38 fmt::Display,
39 marker::PhantomData,
40 str::FromStr,
41};
42use thiserror::Error;
43
44use crate::{
45 cedar_schema::{
46 self, fmt::ToCedarSchemaSyntaxError, parser::parse_cedar_schema_fragment, SchemaWarning,
47 },
48 err::{schema_errors::*, Result},
49 AllDefs, CedarSchemaError, CedarSchemaParseError, ConditionalName, RawName, ReferenceType,
50};
51
52#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
54#[educe(PartialEq, Eq)]
55#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
56pub struct CommonType<N> {
57 #[serde(flatten)]
59 pub ty: Type<N>,
60 #[serde(default)]
62 #[serde(skip_serializing_if = "Annotations::is_empty")]
63 pub annotations: Annotations,
64 #[serde(skip)]
70 #[educe(PartialEq(ignore))]
71 pub loc: Option<Loc>,
72}
73
74#[derive(Educe, Debug, Clone, Deserialize)]
94#[educe(PartialEq, Eq)]
95#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
96#[serde(transparent)]
97#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
98#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
99#[cfg_attr(feature = "wasm", serde(rename = "SchemaJson"))]
100pub struct Fragment<N>(
101 #[serde(deserialize_with = "deserialize_schema_fragment")]
102 #[cfg_attr(
103 feature = "wasm",
104 tsify(type = "Record<string, NamespaceDefinition<N>>")
105 )]
106 pub BTreeMap<Option<Name>, NamespaceDefinition<N>>,
107);
108
109fn deserialize_schema_fragment<'de, D, N: Deserialize<'de> + From<RawName>>(
111 deserializer: D,
112) -> std::result::Result<BTreeMap<Option<Name>, NamespaceDefinition<N>>, D::Error>
113where
114 D: Deserializer<'de>,
115{
116 let raw: BTreeMap<SmolStr, NamespaceDefinition<N>> =
117 serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
118 Ok(BTreeMap::from_iter(
119 raw.into_iter()
120 .map(|(key, value)| {
121 let key = if key.is_empty() {
122 if !value.annotations.is_empty() {
123 Err(serde::de::Error::custom(
124 "annotations are not allowed on the empty namespace".to_string(),
125 ))?
126 }
127 None
128 } else {
129 Some(Name::from_normalized_str(&key).map_err(|err| {
130 serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
131 })?)
132 };
133 Ok((key, value))
134 })
135 .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition<N>)>, D::Error>>(
136 )?,
137 ))
138}
139
140impl<N: Serialize> Serialize for Fragment<N> {
141 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
143 where
144 S: Serializer,
145 {
146 let mut map = serializer.serialize_map(Some(self.0.len()))?;
147 for (k, v) in &self.0 {
148 let k: SmolStr = match k {
149 None => "".into(),
150 Some(name) => name.to_smolstr(),
151 };
152 map.serialize_entry(&k, &v)?;
153 }
154 map.end()
155 }
156}
157
158impl Fragment<RawName> {
159 pub fn from_json_str(json: &str) -> Result<Self> {
162 serde_json::from_str(json).map_err(|e| JsonDeserializationError::new(e, Some(json)).into())
163 }
164
165 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
168 serde_json::from_value(json).map_err(|e| JsonDeserializationError::new(e, None).into())
169 }
170
171 pub fn from_json_file(file: impl std::io::Read) -> Result<Self> {
173 serde_json::from_reader(file).map_err(|e| JsonDeserializationError::new(e, None).into())
174 }
175
176 pub fn from_cedarschema_str<'a>(
178 src: &str,
179 extensions: &Extensions<'a>,
180 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
181 {
182 parse_cedar_schema_fragment(src, extensions)
183 .map_err(|e| CedarSchemaParseError::new(e, src).into())
184 }
185
186 pub fn from_cedarschema_file<'a>(
188 mut file: impl std::io::Read,
189 extensions: &'a Extensions<'_>,
190 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
191 {
192 let mut src = String::new();
193 file.read_to_string(&mut src)?;
194 Self::from_cedarschema_str(&src, extensions)
195 }
196}
197
198impl<N: Display> Fragment<N> {
199 pub fn to_cedarschema(&self) -> std::result::Result<String, ToCedarSchemaSyntaxError> {
201 let src = cedar_schema::fmt::json_schema_to_cedar_schema_str(self)?;
202 Ok(src)
203 }
204}
205
206#[derive(Educe, Debug, Clone, Serialize)]
209#[educe(PartialEq, Eq, PartialOrd, Ord, Hash)]
210#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
211#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
212pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
213
214impl From<CommonTypeId> for UnreservedId {
215 fn from(value: CommonTypeId) -> Self {
216 value.0
217 }
218}
219
220impl AsRef<UnreservedId> for CommonTypeId {
221 fn as_ref(&self) -> &UnreservedId {
222 &self.0
223 }
224}
225
226impl CommonTypeId {
227 pub fn new(id: UnreservedId) -> std::result::Result<Self, ReservedCommonTypeBasenameError> {
229 if Self::is_reserved_schema_keyword(&id) {
230 Err(ReservedCommonTypeBasenameError { id })
231 } else {
232 Ok(Self(id))
233 }
234 }
235
236 pub fn unchecked(id: UnreservedId) -> Self {
239 Self(id)
240 }
241
242 fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
247 matches!(
248 id.as_ref(),
249 "Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
250 )
251 }
252
253 #[cfg(feature = "arbitrary")]
256 fn make_into_valid_common_type_id(id: UnreservedId) -> Self {
257 Self::new(id.clone()).unwrap_or_else(|_| {
258 #[allow(clippy::unwrap_used)]
260 let new_id = format!("_{id}").parse().unwrap();
261 #[allow(clippy::unwrap_used)]
263 Self::new(new_id).unwrap()
264 })
265 }
266}
267
268impl Display for CommonTypeId {
269 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
270 self.0.fmt(f)
271 }
272}
273
274#[cfg(feature = "arbitrary")]
275impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
276 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
277 let id: UnreservedId = u.arbitrary()?;
278 Ok(CommonTypeId::make_into_valid_common_type_id(id))
279 }
280
281 fn size_hint(depth: usize) -> (usize, Option<usize>) {
282 <UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
283 }
284}
285
286impl<'de> Deserialize<'de> for CommonTypeId {
288 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
289 where
290 D: Deserializer<'de>,
291 {
292 UnreservedId::deserialize(deserializer).and_then(|id| {
293 CommonTypeId::new(id).map_err(|e| serde::de::Error::custom(format!("{e}")))
294 })
295 }
296}
297
298#[derive(Debug, Error, PartialEq, Eq, Clone)]
300#[error("this is reserved and cannot be the basename of a common-type declaration: {id}")]
301pub struct ReservedCommonTypeBasenameError {
302 pub(crate) id: UnreservedId,
304}
305
306#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
316#[educe(PartialEq, Eq)]
317#[serde_as]
318#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
319#[serde(bound(serialize = "N: Serialize"))]
320#[serde(deny_unknown_fields)]
321#[serde(rename_all = "camelCase")]
322#[doc(hidden)]
323#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
324#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
325pub struct NamespaceDefinition<N> {
326 #[serde(default)]
327 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
328 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
329 pub common_types: BTreeMap<CommonTypeId, CommonType<N>>,
330 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
331 pub entity_types: BTreeMap<UnreservedId, EntityType<N>>,
332 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
333 pub actions: BTreeMap<SmolStr, ActionType<N>>,
334 #[serde(default)]
336 #[serde(skip_serializing_if = "Annotations::is_empty")]
337 pub annotations: Annotations,
338}
339
340impl<N> NamespaceDefinition<N> {
341 #[cfg(test)]
344 pub fn new(
345 entity_types: impl IntoIterator<Item = (UnreservedId, EntityType<N>)>,
346 actions: impl IntoIterator<Item = (SmolStr, ActionType<N>)>,
347 ) -> Self {
348 Self {
349 common_types: BTreeMap::new(),
350 entity_types: entity_types.into_iter().collect(),
351 actions: actions.into_iter().collect(),
352 annotations: Annotations::new(),
353 }
354 }
355}
356
357impl NamespaceDefinition<RawName> {
358 pub fn conditionally_qualify_type_references(
360 self,
361 ns: Option<&InternalName>,
362 ) -> NamespaceDefinition<ConditionalName> {
363 NamespaceDefinition {
364 common_types: self
365 .common_types
366 .into_iter()
367 .map(|(k, v)| {
368 (
369 k,
370 CommonType {
371 ty: v.ty.conditionally_qualify_type_references(ns),
372 annotations: v.annotations,
373 loc: v.loc,
374 },
375 )
376 })
377 .collect(),
378 entity_types: self
379 .entity_types
380 .into_iter()
381 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
382 .collect(),
383 actions: self
384 .actions
385 .into_iter()
386 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
387 .collect(),
388 annotations: self.annotations,
389 }
390 }
391}
392
393impl NamespaceDefinition<ConditionalName> {
394 pub fn fully_qualify_type_references(
401 self,
402 all_defs: &AllDefs,
403 ) -> Result<NamespaceDefinition<InternalName>> {
404 Ok(NamespaceDefinition {
405 common_types: self
406 .common_types
407 .into_iter()
408 .map(|(k, v)| {
409 Ok((
410 k,
411 CommonType {
412 ty: v.ty.fully_qualify_type_references(all_defs)?,
413 annotations: v.annotations,
414 loc: v.loc,
415 },
416 ))
417 })
418 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
419 entity_types: self
420 .entity_types
421 .into_iter()
422 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
423 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
424 actions: self
425 .actions
426 .into_iter()
427 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
428 .collect::<Result<_>>()?,
429 annotations: self.annotations,
430 })
431 }
432}
433
434#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
443#[educe(PartialEq, Eq)]
444#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
445#[serde(deny_unknown_fields)]
446#[serde(rename_all = "camelCase")]
447#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
448#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
449pub struct EntityType<N> {
450 #[serde(default)]
453 #[serde(skip_serializing_if = "Vec::is_empty")]
454 pub member_of_types: Vec<N>,
455 #[serde(default)]
457 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
458 pub shape: AttributesOrContext<N>,
459 #[serde(default)]
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub tags: Option<Type<N>>,
463 #[serde(default)]
465 #[serde(skip_serializing_if = "Annotations::is_empty")]
466 pub annotations: Annotations,
467 #[serde(skip)]
473 #[educe(PartialEq(ignore))]
474 pub loc: Option<Loc>,
475}
476
477impl EntityType<RawName> {
478 pub fn conditionally_qualify_type_references(
480 self,
481 ns: Option<&InternalName>,
482 ) -> EntityType<ConditionalName> {
483 EntityType {
484 member_of_types: self
485 .member_of_types
486 .into_iter()
487 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
489 shape: self.shape.conditionally_qualify_type_references(ns),
490 tags: self
491 .tags
492 .map(|ty| ty.conditionally_qualify_type_references(ns)),
493 annotations: self.annotations,
494 loc: self.loc,
495 }
496 }
497}
498
499impl EntityType<ConditionalName> {
500 pub fn fully_qualify_type_references(
507 self,
508 all_defs: &AllDefs,
509 ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
510 Ok(EntityType {
511 member_of_types: self
512 .member_of_types
513 .into_iter()
514 .map(|cname| cname.resolve(all_defs))
515 .collect::<std::result::Result<_, _>>()?,
516 shape: self.shape.fully_qualify_type_references(all_defs)?,
517 tags: self
518 .tags
519 .map(|ty| ty.fully_qualify_type_references(all_defs))
520 .transpose()?,
521 annotations: self.annotations,
522 loc: self.loc,
523 })
524 }
525}
526
527#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
534#[educe(PartialEq, Eq)]
535#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
536#[serde(transparent)]
537#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
538#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
539pub struct AttributesOrContext<N>(
540 pub Type<N>,
543);
544
545impl<N> AttributesOrContext<N> {
546 pub fn into_inner(self) -> Type<N> {
548 self.0
549 }
550
551 pub fn is_empty_record(&self) -> bool {
553 self.0.is_empty_record()
554 }
555
556 pub fn loc(&self) -> Option<&Loc> {
558 self.0.loc()
559 }
560}
561
562impl<N> Default for AttributesOrContext<N> {
563 fn default() -> Self {
564 Self::from(RecordType::default())
565 }
566}
567
568impl<N: Display> Display for AttributesOrContext<N> {
569 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
570 self.0.fmt(f)
571 }
572}
573
574impl<N> From<RecordType<N>> for AttributesOrContext<N> {
575 fn from(rty: RecordType<N>) -> AttributesOrContext<N> {
576 Self(Type::Type {
577 ty: TypeVariant::Record(rty),
578 loc: None,
579 })
580 }
581}
582
583impl AttributesOrContext<RawName> {
584 pub fn conditionally_qualify_type_references(
586 self,
587 ns: Option<&InternalName>,
588 ) -> AttributesOrContext<ConditionalName> {
589 AttributesOrContext(self.0.conditionally_qualify_type_references(ns))
590 }
591}
592
593impl AttributesOrContext<ConditionalName> {
594 pub fn fully_qualify_type_references(
601 self,
602 all_defs: &AllDefs,
603 ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
604 Ok(AttributesOrContext(
605 self.0.fully_qualify_type_references(all_defs)?,
606 ))
607 }
608}
609
610#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
618#[educe(PartialEq, Eq)]
619#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
620#[serde(deny_unknown_fields)]
621#[serde(rename_all = "camelCase")]
622#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
623#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
624pub struct ActionType<N> {
625 #[serde(default)]
629 #[serde(skip_serializing_if = "Option::is_none")]
630 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
631 #[serde(default)]
633 #[serde(skip_serializing_if = "Option::is_none")]
634 pub applies_to: Option<ApplySpec<N>>,
635 #[serde(default)]
637 #[serde(skip_serializing_if = "Option::is_none")]
638 pub member_of: Option<Vec<ActionEntityUID<N>>>,
639 #[serde(default)]
641 #[serde(skip_serializing_if = "Annotations::is_empty")]
642 pub annotations: Annotations,
643 #[serde(skip)]
649 #[educe(PartialEq(ignore))]
650 pub loc: Option<Loc>,
651}
652
653impl ActionType<RawName> {
654 pub fn conditionally_qualify_type_references(
656 self,
657 ns: Option<&InternalName>,
658 ) -> ActionType<ConditionalName> {
659 ActionType {
660 attributes: self.attributes,
661 applies_to: self
662 .applies_to
663 .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
664 member_of: self.member_of.map(|v| {
665 v.into_iter()
666 .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
667 .collect()
668 }),
669 annotations: self.annotations,
670 loc: self.loc,
671 }
672 }
673}
674
675impl ActionType<ConditionalName> {
676 pub fn fully_qualify_type_references(
683 self,
684 all_defs: &AllDefs,
685 ) -> Result<ActionType<InternalName>> {
686 Ok(ActionType {
687 attributes: self.attributes,
688 applies_to: self
689 .applies_to
690 .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
691 .transpose()?,
692 member_of: self
693 .member_of
694 .map(|v| {
695 v.into_iter()
696 .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
697 .collect::<std::result::Result<_, ActionNotDefinedError>>()
698 })
699 .transpose()?,
700 annotations: self.annotations,
701 loc: self.loc,
702 })
703 }
704}
705
706#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
716#[educe(PartialEq, Eq)]
717#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
718#[serde(deny_unknown_fields)]
719#[serde(rename_all = "camelCase")]
720#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
721#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
722pub struct ApplySpec<N> {
723 pub resource_types: Vec<N>,
725 pub principal_types: Vec<N>,
727 #[serde(default)]
729 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
730 pub context: AttributesOrContext<N>,
731}
732
733impl ApplySpec<RawName> {
734 pub fn conditionally_qualify_type_references(
736 self,
737 ns: Option<&InternalName>,
738 ) -> ApplySpec<ConditionalName> {
739 ApplySpec {
740 resource_types: self
741 .resource_types
742 .into_iter()
743 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
745 principal_types: self
746 .principal_types
747 .into_iter()
748 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
750 context: self.context.conditionally_qualify_type_references(ns),
751 }
752 }
753}
754
755impl ApplySpec<ConditionalName> {
756 pub fn fully_qualify_type_references(
763 self,
764 all_defs: &AllDefs,
765 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
766 Ok(ApplySpec {
767 resource_types: self
768 .resource_types
769 .into_iter()
770 .map(|cname| cname.resolve(all_defs))
771 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
772 principal_types: self
773 .principal_types
774 .into_iter()
775 .map(|cname| cname.resolve(all_defs))
776 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
777 context: self.context.fully_qualify_type_references(all_defs)?,
778 })
779 }
780}
781
782#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
784#[educe(PartialEq, Eq, Hash)]
785#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
786#[serde(deny_unknown_fields)]
787#[serde(rename_all = "camelCase")]
788#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
789#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
790pub struct ActionEntityUID<N> {
791 pub id: SmolStr,
793
794 #[serde(rename = "type")]
805 #[serde(default)]
806 #[serde(skip_serializing_if = "Option::is_none")]
807 ty: Option<N>,
808}
809
810impl ActionEntityUID<RawName> {
811 pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
814 Self { id, ty }
815 }
816
817 pub fn default_type(id: SmolStr) -> Self {
822 Self { id, ty: None }
823 }
824}
825
826impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
827 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
828 if let Some(ty) = &self.ty {
829 write!(f, "{}::", ty)?
830 } else {
831 write!(f, "Action::")?
832 }
833 write!(f, "\"{}\"", self.id.escape_debug())
834 }
835}
836
837impl ActionEntityUID<RawName> {
838 pub fn conditionally_qualify_type_references(
840 self,
841 ns: Option<&InternalName>,
842 ) -> ActionEntityUID<ConditionalName> {
843 ActionEntityUID {
846 id: self.id,
847 ty: {
848 #[allow(clippy::expect_used)]
850 let raw_name = self
851 .ty
852 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
853 Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
854 },
855 }
856 }
857
858 pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
860 ActionEntityUID {
863 id: self.id,
864 ty: {
865 #[allow(clippy::expect_used)]
867 let raw_name = self
868 .ty
869 .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
870 Some(raw_name.qualify_with(ns))
871 },
872 }
873 }
874}
875
876impl ActionEntityUID<ConditionalName> {
877 pub fn ty(&self) -> &ConditionalName {
879 #[allow(clippy::expect_used)]
881 self.ty.as_ref().expect("by INVARIANT on self.ty")
882 }
883
884 pub fn fully_qualify_type_references(
892 self,
893 all_defs: &AllDefs,
894 ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
895 for possibility in self.possibilities() {
896 if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
900 if all_defs.is_defined_as_action(&euid) {
901 return Ok(possibility);
902 }
903 }
904 }
905 Err(ActionNotDefinedError(nonempty!(self)))
906 }
907
908 pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
912 self.ty()
915 .possibilities()
916 .map(|possibility| ActionEntityUID {
917 id: self.id.clone(),
918 ty: Some(possibility.clone()),
919 })
920 }
921
922 pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
926 ActionEntityUID {
927 id: self.id.clone(),
928 ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
929 }
930 }
931}
932
933impl ActionEntityUID<Name> {
934 pub fn ty(&self) -> &Name {
936 #[allow(clippy::expect_used)]
938 self.ty.as_ref().expect("by INVARIANT on self.ty")
939 }
940}
941
942impl ActionEntityUID<InternalName> {
943 pub fn ty(&self) -> &InternalName {
945 #[allow(clippy::expect_used)]
947 self.ty.as_ref().expect("by INVARIANT on self.ty")
948 }
949}
950
951impl From<ActionEntityUID<Name>> for EntityUID {
952 fn from(aeuid: ActionEntityUID<Name>) -> Self {
953 EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
954 }
955}
956
957impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
958 type Error = <InternalName as TryInto<Name>>::Error;
959 fn try_from(aeuid: ActionEntityUID<InternalName>) -> std::result::Result<Self, Self::Error> {
960 let ty = Name::try_from(aeuid.ty().clone())?;
961 Ok(EntityUID::from_components(
962 ty.into(),
963 Eid::new(aeuid.id),
964 None,
965 ))
966 }
967}
968
969impl From<EntityUID> for ActionEntityUID<Name> {
970 fn from(euid: EntityUID) -> Self {
971 let (ty, id) = euid.components();
972 ActionEntityUID {
973 ty: Some(ty.into()),
974 id: <Eid as AsRef<SmolStr>>::as_ref(&id).clone(),
975 }
976 }
977}
978
979#[derive(Educe, Debug, Clone, Serialize)]
986#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
987#[serde(untagged)]
992#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
993#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
994pub enum Type<N> {
995 Type {
999 #[serde(flatten)]
1001 ty: TypeVariant<N>,
1002 #[serde(skip)]
1008 #[educe(PartialEq(ignore))]
1009 #[educe(PartialOrd(ignore))]
1010 loc: Option<Loc>,
1011 },
1012 CommonTypeRef {
1018 #[serde(rename = "type")]
1023 type_name: N,
1024 #[serde(skip)]
1030 #[educe(PartialEq(ignore))]
1031 #[educe(PartialOrd(ignore))]
1032 loc: Option<Loc>,
1033 },
1034}
1035
1036impl<N> Type<N> {
1037 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
1040 match self {
1041 Type::Type {
1042 ty: TypeVariant::Record(RecordType { attributes, .. }),
1043 ..
1044 } => attributes
1045 .iter()
1046 .map(|(_, ty)| ty.ty.common_type_references())
1047 .fold(Box::new(std::iter::empty()), |it, tys| {
1048 Box::new(it.chain(tys))
1049 }),
1050 Type::Type {
1051 ty: TypeVariant::Set { element },
1052 ..
1053 } => element.common_type_references(),
1054 Type::Type {
1055 ty: TypeVariant::EntityOrCommon { type_name },
1056 ..
1057 } => Box::new(std::iter::once(type_name)),
1058 Type::CommonTypeRef { type_name, .. } => Box::new(std::iter::once(type_name)),
1059 _ => Box::new(std::iter::empty()),
1060 }
1061 }
1062
1063 pub fn is_extension(&self) -> Option<bool> {
1069 match self {
1070 Self::Type {
1071 ty: TypeVariant::Extension { .. },
1072 ..
1073 } => Some(true),
1074 Self::Type {
1075 ty: TypeVariant::Set { element },
1076 ..
1077 } => element.is_extension(),
1078 Self::Type {
1079 ty: TypeVariant::Record(RecordType { attributes, .. }),
1080 ..
1081 } => attributes
1082 .values()
1083 .try_fold(false, |a, e| match e.ty.is_extension() {
1084 Some(true) => Some(true),
1085 Some(false) => Some(a),
1086 None => None,
1087 }),
1088 Self::Type { .. } => Some(false),
1089 Self::CommonTypeRef { .. } => None,
1090 }
1091 }
1092
1093 pub fn is_empty_record(&self) -> bool {
1096 match self {
1097 Self::Type {
1098 ty: TypeVariant::Record(rty),
1099 ..
1100 } => rty.is_empty_record(),
1101 _ => false,
1102 }
1103 }
1104
1105 pub fn loc(&self) -> Option<&Loc> {
1107 match self {
1108 Self::Type { loc, .. } => loc.as_ref(),
1109 Self::CommonTypeRef { loc, .. } => loc.as_ref(),
1110 }
1111 }
1112}
1113
1114impl Type<RawName> {
1115 pub fn conditionally_qualify_type_references(
1117 self,
1118 ns: Option<&InternalName>,
1119 ) -> Type<ConditionalName> {
1120 match self {
1121 Self::Type { ty, loc } => Type::Type {
1122 ty: ty.conditionally_qualify_type_references(ns),
1123 loc,
1124 },
1125 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1126 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
1127 loc,
1128 },
1129 }
1130 }
1131
1132 fn into_n<N: From<RawName>>(self) -> Type<N> {
1133 match self {
1134 Self::Type { ty, loc } => Type::Type {
1135 ty: ty.into_n(),
1136 loc,
1137 },
1138 Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1139 type_name: type_name.into(),
1140 loc,
1141 },
1142 }
1143 }
1144}
1145
1146impl Type<ConditionalName> {
1147 pub fn fully_qualify_type_references(
1153 self,
1154 all_defs: &AllDefs,
1155 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1156 match self {
1157 Self::Type { ty, loc } => Ok(Type::Type {
1158 ty: ty.fully_qualify_type_references(all_defs)?,
1159 loc,
1160 }),
1161 Self::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef {
1162 type_name: type_name.resolve(all_defs)?,
1163 loc,
1164 }),
1165 }
1166 }
1167}
1168
1169impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1170 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1171 where
1172 D: serde::Deserializer<'de>,
1173 {
1174 deserializer.deserialize_any(TypeVisitor {
1175 _phantom: PhantomData,
1176 })
1177 }
1178}
1179
1180#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1182#[serde(field_identifier, rename_all = "camelCase")]
1183enum TypeFields {
1184 Type,
1185 Element,
1186 Attributes,
1187 AdditionalAttributes,
1188 Name,
1189}
1190
1191macro_rules! type_field_name {
1195 (Type) => {
1196 "type"
1197 };
1198 (Element) => {
1199 "element"
1200 };
1201 (Attributes) => {
1202 "attributes"
1203 };
1204 (AdditionalAttributes) => {
1205 "additionalAttributes"
1206 };
1207 (Name) => {
1208 "name"
1209 };
1210}
1211
1212impl TypeFields {
1213 fn as_str(&self) -> &'static str {
1214 match self {
1215 TypeFields::Type => type_field_name!(Type),
1216 TypeFields::Element => type_field_name!(Element),
1217 TypeFields::Attributes => type_field_name!(Attributes),
1218 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1219 TypeFields::Name => type_field_name!(Name),
1220 }
1221 }
1222}
1223
1224#[derive(Debug, Deserialize)]
1229struct AttributesTypeMap(
1230 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1231 BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1232);
1233
1234struct TypeVisitor<N> {
1235 _phantom: PhantomData<N>,
1236}
1237
1238impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1239 type Value = Type<N>;
1240
1241 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1242 formatter.write_str("builtin type or reference to type defined in commonTypes")
1243 }
1244
1245 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1246 where
1247 M: MapAccess<'de>,
1248 {
1249 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1250
1251 let mut type_name: Option<SmolStr> = None;
1252 let mut element: Option<Type<N>> = None;
1253 let mut attributes: Option<AttributesTypeMap> = None;
1254 let mut additional_attributes: Option<bool> = None;
1255 let mut name: Option<SmolStr> = None;
1256
1257 while let Some(key) = map.next_key()? {
1261 match key {
1262 TypeField => {
1263 if type_name.is_some() {
1264 return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1265 }
1266 type_name = Some(map.next_value()?);
1267 }
1268 Element => {
1269 if element.is_some() {
1270 return Err(serde::de::Error::duplicate_field(Element.as_str()));
1271 }
1272 element = Some(map.next_value()?);
1273 }
1274 Attributes => {
1275 if attributes.is_some() {
1276 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1277 }
1278 attributes = Some(map.next_value()?);
1279 }
1280 AdditionalAttributes => {
1281 if additional_attributes.is_some() {
1282 return Err(serde::de::Error::duplicate_field(
1283 AdditionalAttributes.as_str(),
1284 ));
1285 }
1286 additional_attributes = Some(map.next_value()?);
1287 }
1288 Name => {
1289 if name.is_some() {
1290 return Err(serde::de::Error::duplicate_field(Name.as_str()));
1291 }
1292 name = Some(map.next_value()?);
1293 }
1294 }
1295 }
1296
1297 Self::build_schema_type::<M>(
1298 type_name.as_ref(),
1299 element,
1300 attributes,
1301 additional_attributes,
1302 name,
1303 )
1304 }
1305}
1306
1307impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1308 fn build_schema_type<M>(
1313 type_name: Option<&SmolStr>,
1314 element: Option<Type<N>>,
1315 attributes: Option<AttributesTypeMap>,
1316 additional_attributes: Option<bool>,
1317 name: Option<SmolStr>,
1318 ) -> std::result::Result<Type<N>, M::Error>
1319 where
1320 M: MapAccess<'de>,
1321 {
1322 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1323 let mut remaining_fields = [
1325 (TypeField, type_name.is_some()),
1326 (Element, element.is_some()),
1327 (Attributes, attributes.is_some()),
1328 (AdditionalAttributes, additional_attributes.is_some()),
1329 (Name, name.is_some()),
1330 ]
1331 .into_iter()
1332 .filter(|(_, present)| *present)
1333 .map(|(field, _)| field)
1334 .collect::<HashSet<_>>();
1335
1336 match type_name {
1337 Some(s) => {
1338 remaining_fields.remove(&TypeField);
1340 let error_if_fields = |fs: &[TypeFields],
1343 expected: &'static [&'static str]|
1344 -> std::result::Result<(), M::Error> {
1345 for f in fs {
1346 if remaining_fields.contains(f) {
1347 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1348 }
1349 }
1350 Ok(())
1351 };
1352 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1353 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1354 };
1355 match s.as_str() {
1356 "String" => {
1357 error_if_any_fields()?;
1358 Ok(Type::Type {
1359 ty: TypeVariant::String,
1360 loc: None,
1361 })
1362 }
1363 "Long" => {
1364 error_if_any_fields()?;
1365 Ok(Type::Type {
1366 ty: TypeVariant::Long,
1367 loc: None,
1368 })
1369 }
1370 "Boolean" => {
1371 error_if_any_fields()?;
1372 Ok(Type::Type {
1373 ty: TypeVariant::Boolean,
1374 loc: None,
1375 })
1376 }
1377 "Set" => {
1378 error_if_fields(
1379 &[Attributes, AdditionalAttributes, Name],
1380 &[type_field_name!(Element)],
1381 )?;
1382
1383 match element {
1384 Some(element) => Ok(Type::Type {
1385 ty: TypeVariant::Set {
1386 element: Box::new(element),
1387 },
1388 loc: None,
1389 }),
1390 None => Err(serde::de::Error::missing_field(Element.as_str())),
1391 }
1392 }
1393 "Record" => {
1394 error_if_fields(
1395 &[Element, Name],
1396 &[
1397 type_field_name!(Attributes),
1398 type_field_name!(AdditionalAttributes),
1399 ],
1400 )?;
1401
1402 if let Some(attributes) = attributes {
1403 let additional_attributes =
1404 additional_attributes.unwrap_or_else(partial_schema_default);
1405 Ok(Type::Type {
1406 ty: TypeVariant::Record(RecordType {
1407 attributes: attributes
1408 .0
1409 .into_iter()
1410 .map(
1411 |(
1412 k,
1413 TypeOfAttribute {
1414 ty,
1415 required,
1416 annotations,
1417 },
1418 )| {
1419 (
1420 k,
1421 TypeOfAttribute {
1422 ty: ty.into_n(),
1423
1424 required,
1425 annotations,
1426 },
1427 )
1428 },
1429 )
1430 .collect(),
1431 additional_attributes,
1432 }),
1433 loc: None,
1434 })
1435 } else {
1436 Err(serde::de::Error::missing_field(Attributes.as_str()))
1437 }
1438 }
1439 "Entity" => {
1440 error_if_fields(
1441 &[Element, Attributes, AdditionalAttributes],
1442 &[type_field_name!(Name)],
1443 )?;
1444 match name {
1445 Some(name) => Ok(Type::Type {
1446 ty: TypeVariant::Entity {
1447 name: RawName::from_normalized_str(&name)
1448 .map_err(|err| {
1449 serde::de::Error::custom(format!(
1450 "invalid entity type `{name}`: {err}"
1451 ))
1452 })?
1453 .into(),
1454 },
1455 loc: None,
1456 }),
1457 None => Err(serde::de::Error::missing_field(Name.as_str())),
1458 }
1459 }
1460 "EntityOrCommon" => {
1461 error_if_fields(
1462 &[Element, Attributes, AdditionalAttributes],
1463 &[type_field_name!(Name)],
1464 )?;
1465 match name {
1466 Some(name) => Ok(Type::Type {
1467 ty: TypeVariant::EntityOrCommon {
1468 type_name: RawName::from_normalized_str(&name)
1469 .map_err(|err| {
1470 serde::de::Error::custom(format!(
1471 "invalid entity or common type `{name}`: {err}"
1472 ))
1473 })?
1474 .into(),
1475 },
1476 loc: None,
1477 }),
1478 None => Err(serde::de::Error::missing_field(Name.as_str())),
1479 }
1480 }
1481 "Extension" => {
1482 error_if_fields(
1483 &[Element, Attributes, AdditionalAttributes],
1484 &[type_field_name!(Name)],
1485 )?;
1486
1487 match name {
1488 Some(name) => Ok(Type::Type {
1489 ty: TypeVariant::Extension {
1490 name: UnreservedId::from_normalized_str(&name).map_err(
1491 |err| {
1492 serde::de::Error::custom(format!(
1493 "invalid extension type `{name}`: {err}"
1494 ))
1495 },
1496 )?,
1497 },
1498 loc: None,
1499 }),
1500 None => Err(serde::de::Error::missing_field(Name.as_str())),
1501 }
1502 }
1503 type_name => {
1504 error_if_any_fields()?;
1505 Ok(Type::CommonTypeRef {
1506 type_name: N::from(RawName::from_normalized_str(type_name).map_err(
1507 |err| {
1508 serde::de::Error::custom(format!(
1509 "invalid common type `{type_name}`: {err}"
1510 ))
1511 },
1512 )?),
1513 loc: None,
1514 })
1515 }
1516 }
1517 }
1518 None => Err(serde::de::Error::missing_field(TypeField.as_str())),
1519 }
1520 }
1521}
1522
1523impl<N> From<TypeVariant<N>> for Type<N> {
1524 fn from(ty: TypeVariant<N>) -> Self {
1525 Self::Type { ty, loc: None }
1526 }
1527}
1528
1529#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1535#[educe(PartialEq, Eq, PartialOrd, Ord)]
1536#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1537#[serde(rename_all = "camelCase")]
1538#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1539#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1540pub struct RecordType<N> {
1541 pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
1543 #[serde(default = "partial_schema_default")]
1545 #[serde(skip_serializing_if = "is_partial_schema_default")]
1546 pub additional_attributes: bool,
1547}
1548
1549impl<N> Default for RecordType<N> {
1550 fn default() -> Self {
1551 Self {
1552 attributes: BTreeMap::new(),
1553 additional_attributes: partial_schema_default(),
1554 }
1555 }
1556}
1557
1558impl<N> RecordType<N> {
1559 pub fn is_empty_record(&self) -> bool {
1561 self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
1562 }
1563}
1564
1565impl RecordType<RawName> {
1566 pub fn conditionally_qualify_type_references(
1568 self,
1569 ns: Option<&InternalName>,
1570 ) -> RecordType<ConditionalName> {
1571 RecordType {
1572 attributes: self
1573 .attributes
1574 .into_iter()
1575 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
1576 .collect(),
1577 additional_attributes: self.additional_attributes,
1578 }
1579 }
1580}
1581
1582impl RecordType<ConditionalName> {
1583 pub fn fully_qualify_type_references(
1590 self,
1591 all_defs: &AllDefs,
1592 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1593 Ok(RecordType {
1594 attributes: self
1595 .attributes
1596 .into_iter()
1597 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
1598 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1599 additional_attributes: self.additional_attributes,
1600 })
1601 }
1602}
1603
1604#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1612#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1613#[serde(tag = "type")]
1614#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1615#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1616#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1617pub enum TypeVariant<N> {
1618 String,
1620 Long,
1622 Boolean,
1624 Set {
1626 element: Box<Type<N>>,
1628 },
1629 Record(RecordType<N>),
1631 Entity {
1633 name: N,
1638 },
1639 EntityOrCommon {
1641 #[serde(rename = "name")]
1662 type_name: N,
1663 },
1664 Extension {
1666 name: UnreservedId,
1668 },
1669}
1670
1671impl TypeVariant<RawName> {
1672 pub fn conditionally_qualify_type_references(
1674 self,
1675 ns: Option<&InternalName>,
1676 ) -> TypeVariant<ConditionalName> {
1677 match self {
1678 Self::Boolean => TypeVariant::Boolean,
1679 Self::Long => TypeVariant::Long,
1680 Self::String => TypeVariant::String,
1681 Self::Extension { name } => TypeVariant::Extension { name },
1682 Self::Entity { name } => TypeVariant::Entity {
1683 name: name.conditionally_qualify_with(ns, ReferenceType::Entity), },
1685 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1686 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
1687 },
1688 Self::Set { element } => TypeVariant::Set {
1689 element: Box::new(element.conditionally_qualify_type_references(ns)),
1690 },
1691 Self::Record(RecordType {
1692 attributes,
1693 additional_attributes,
1694 }) => TypeVariant::Record(RecordType {
1695 attributes: BTreeMap::from_iter(attributes.into_iter().map(
1696 |(
1697 attr,
1698 TypeOfAttribute {
1699 ty,
1700 required,
1701 annotations,
1702 },
1703 )| {
1704 (
1705 attr,
1706 TypeOfAttribute {
1707 ty: ty.conditionally_qualify_type_references(ns),
1708
1709 required,
1710 annotations,
1711 },
1712 )
1713 },
1714 )),
1715 additional_attributes,
1716 }),
1717 }
1718 }
1719
1720 fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
1721 match self {
1722 Self::Boolean => TypeVariant::Boolean,
1723 Self::Long => TypeVariant::Long,
1724 Self::String => TypeVariant::String,
1725 Self::Entity { name } => TypeVariant::Entity { name: name.into() },
1726 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1727 type_name: type_name.into(),
1728 },
1729 Self::Record(RecordType {
1730 attributes,
1731 additional_attributes,
1732 }) => TypeVariant::Record(RecordType {
1733 attributes: attributes
1734 .into_iter()
1735 .map(|(k, v)| (k, v.into_n()))
1736 .collect(),
1737 additional_attributes,
1738 }),
1739 Self::Set { element } => TypeVariant::Set {
1740 element: Box::new(element.into_n()),
1741 },
1742 Self::Extension { name } => TypeVariant::Extension { name },
1743 }
1744 }
1745}
1746
1747impl TypeVariant<ConditionalName> {
1748 pub fn fully_qualify_type_references(
1755 self,
1756 all_defs: &AllDefs,
1757 ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
1758 match self {
1759 Self::Boolean => Ok(TypeVariant::Boolean),
1760 Self::Long => Ok(TypeVariant::Long),
1761 Self::String => Ok(TypeVariant::String),
1762 Self::Extension { name } => Ok(TypeVariant::Extension { name }),
1763 Self::Entity { name } => Ok(TypeVariant::Entity {
1764 name: name.resolve(all_defs)?,
1765 }),
1766 Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
1767 type_name: type_name.resolve(all_defs)?,
1768 }),
1769 Self::Set { element } => Ok(TypeVariant::Set {
1770 element: Box::new(element.fully_qualify_type_references(all_defs)?),
1771 }),
1772 Self::Record(RecordType {
1773 attributes,
1774 additional_attributes,
1775 }) => Ok(TypeVariant::Record(RecordType {
1776 attributes: attributes
1777 .into_iter()
1778 .map(
1779 |(
1780 attr,
1781 TypeOfAttribute {
1782 ty,
1783 required,
1784 annotations,
1785 },
1786 )| {
1787 Ok((
1788 attr,
1789 TypeOfAttribute {
1790 ty: ty.fully_qualify_type_references(all_defs)?,
1791 required,
1792 annotations,
1793 },
1794 ))
1795 },
1796 )
1797 .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
1798 additional_attributes,
1799 })),
1800 }
1801 }
1802}
1803
1804fn is_partial_schema_default(b: &bool) -> bool {
1806 *b == partial_schema_default()
1807}
1808
1809#[cfg(feature = "arbitrary")]
1810#[allow(clippy::panic)]
1812impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
1813 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
1814 use std::collections::BTreeSet;
1815
1816 Ok(Type::Type {
1817 ty: match u.int_in_range::<u8>(1..=8)? {
1818 1 => TypeVariant::String,
1819 2 => TypeVariant::Long,
1820 3 => TypeVariant::Boolean,
1821 4 => TypeVariant::Set {
1822 element: Box::new(u.arbitrary()?),
1823 },
1824 5 => {
1825 let attributes = {
1826 let attr_names: BTreeSet<String> = u.arbitrary()?;
1827 attr_names
1828 .into_iter()
1829 .map(|attr_name| {
1830 Ok((
1831 attr_name.into(),
1832 u.arbitrary::<TypeOfAttribute<RawName>>()?.into(),
1833 ))
1834 })
1835 .collect::<arbitrary::Result<_>>()?
1836 };
1837 TypeVariant::Record(RecordType {
1838 attributes,
1839 additional_attributes: u.arbitrary()?,
1840 })
1841 }
1842 6 => TypeVariant::Entity {
1843 name: u.arbitrary()?,
1844 },
1845 7 => TypeVariant::Extension {
1846 #[allow(clippy::unwrap_used)]
1848 name: "ipaddr".parse().unwrap(),
1849 },
1850 8 => TypeVariant::Extension {
1851 #[allow(clippy::unwrap_used)]
1853 name: "decimal".parse().unwrap(),
1854 },
1855 n => panic!("bad index: {n}"),
1856 },
1857 loc: None,
1858 })
1859 }
1860 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
1861 (1, None) }
1863}
1864
1865#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1884#[educe(PartialEq, Eq, PartialOrd, Ord)]
1885#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1886pub struct TypeOfAttribute<N> {
1887 #[serde(flatten)]
1889 pub ty: Type<N>,
1890 #[serde(default)]
1892 #[serde(skip_serializing_if = "Annotations::is_empty")]
1893 pub annotations: Annotations,
1894 #[serde(default = "record_attribute_required_default")]
1896 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
1897 pub required: bool,
1898}
1899
1900impl TypeOfAttribute<RawName> {
1901 fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
1902 TypeOfAttribute {
1903 ty: self.ty.into_n(),
1904
1905 required: self.required,
1906 annotations: self.annotations,
1907 }
1908 }
1909
1910 pub fn conditionally_qualify_type_references(
1912 self,
1913 ns: Option<&InternalName>,
1914 ) -> TypeOfAttribute<ConditionalName> {
1915 TypeOfAttribute {
1916 ty: self.ty.conditionally_qualify_type_references(ns),
1917 required: self.required,
1918 annotations: self.annotations,
1919 }
1920 }
1921}
1922
1923impl TypeOfAttribute<ConditionalName> {
1924 pub fn fully_qualify_type_references(
1931 self,
1932 all_defs: &AllDefs,
1933 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
1934 Ok(TypeOfAttribute {
1935 ty: self.ty.fully_qualify_type_references(all_defs)?,
1936 required: self.required,
1937 annotations: self.annotations,
1938 })
1939 }
1940}
1941
1942#[cfg(feature = "arbitrary")]
1943impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
1944 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1945 Ok(Self {
1946 ty: u.arbitrary::<Type<RawName>>()?,
1947 required: u.arbitrary()?,
1948 annotations: u.arbitrary()?,
1949 })
1950 }
1951
1952 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1953 arbitrary::size_hint::and_all(&[
1954 <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
1955 <bool as arbitrary::Arbitrary>::size_hint(depth),
1956 <cedar_policy_core::est::Annotations as arbitrary::Arbitrary>::size_hint(depth),
1957 ])
1958 }
1959}
1960
1961fn is_record_attribute_required_default(b: &bool) -> bool {
1963 *b == record_attribute_required_default()
1964}
1965
1966fn partial_schema_default() -> bool {
1969 false
1970}
1971
1972fn record_attribute_required_default() -> bool {
1974 true
1975}
1976
1977#[cfg(test)]
1978mod test {
1979 use cedar_policy_core::{
1980 extensions::Extensions,
1981 test_utils::{expect_err, ExpectedErrorMessageBuilder},
1982 };
1983 use cool_asserts::assert_matches;
1984
1985 use crate::ValidatorSchema;
1986
1987 use super::*;
1988
1989 #[test]
1990 fn test_entity_type_parser1() {
1991 let user = r#"
1992 {
1993 "memberOfTypes" : ["UserGroup"]
1994 }
1995 "#;
1996 let et = serde_json::from_str::<EntityType<RawName>>(user).expect("Parse Error");
1997 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
1998 assert_eq!(
1999 et.shape,
2000 AttributesOrContext(Type::Type {
2001 ty: TypeVariant::Record(RecordType {
2002 attributes: BTreeMap::new(),
2003 additional_attributes: false
2004 }),
2005 loc: None
2006 }),
2007 );
2008 }
2009
2010 #[test]
2011 fn test_entity_type_parser2() {
2012 let src = r#"
2013 { }
2014 "#;
2015 let et = serde_json::from_str::<EntityType<RawName>>(src).expect("Parse Error");
2016 assert_eq!(et.member_of_types.len(), 0);
2017 assert_eq!(
2018 et.shape,
2019 AttributesOrContext(Type::Type {
2020 ty: TypeVariant::Record(RecordType {
2021 attributes: BTreeMap::new(),
2022 additional_attributes: false
2023 }),
2024 loc: None
2025 }),
2026 );
2027 }
2028
2029 #[test]
2030 fn test_action_type_parser1() {
2031 let src = r#"
2032 {
2033 "appliesTo" : {
2034 "resourceTypes": ["Album"],
2035 "principalTypes": ["User"]
2036 },
2037 "memberOf": [{"id": "readWrite"}]
2038 }
2039 "#;
2040 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2041 let spec = ApplySpec {
2042 resource_types: vec!["Album".parse().unwrap()],
2043 principal_types: vec!["User".parse().unwrap()],
2044 context: AttributesOrContext::default(),
2045 };
2046 assert_eq!(at.applies_to, Some(spec));
2047 assert_eq!(
2048 at.member_of,
2049 Some(vec![ActionEntityUID {
2050 ty: None,
2051 id: "readWrite".into()
2052 }])
2053 );
2054 }
2055
2056 #[test]
2057 fn test_action_type_parser2() {
2058 let src = r#"
2059 { }
2060 "#;
2061 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2062 assert_eq!(at.applies_to, None);
2063 assert!(at.member_of.is_none());
2064 }
2065
2066 #[test]
2067 fn test_schema_file_parser() {
2068 let src = serde_json::json!(
2069 {
2070 "entityTypes": {
2071
2072 "User": {
2073 "memberOfTypes": ["UserGroup"]
2074 },
2075 "Photo": {
2076 "memberOfTypes": ["Album", "Account"]
2077 },
2078
2079 "Album": {
2080 "memberOfTypes": ["Album", "Account"]
2081 },
2082 "Account": { },
2083 "UserGroup": { }
2084 },
2085
2086 "actions": {
2087 "readOnly": { },
2088 "readWrite": { },
2089 "createAlbum": {
2090 "appliesTo" : {
2091 "resourceTypes": ["Account", "Album"],
2092 "principalTypes": ["User"]
2093 },
2094 "memberOf": [{"id": "readWrite"}]
2095 },
2096 "addPhotoToAlbum": {
2097 "appliesTo" : {
2098 "resourceTypes": ["Album"],
2099 "principalTypes": ["User"]
2100 },
2101 "memberOf": [{"id": "readWrite"}]
2102 },
2103 "viewPhoto": {
2104 "appliesTo" : {
2105 "resourceTypes": ["Photo"],
2106 "principalTypes": ["User"]
2107 },
2108 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2109 },
2110 "viewComments": {
2111 "appliesTo" : {
2112 "resourceTypes": ["Photo"],
2113 "principalTypes": ["User"]
2114 },
2115 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2116 }
2117 }
2118 });
2119 let schema_file: NamespaceDefinition<RawName> =
2120 serde_json::from_value(src).expect("Parse Error");
2121
2122 assert_eq!(schema_file.entity_types.len(), 5);
2123 assert_eq!(schema_file.actions.len(), 6);
2124 }
2125
2126 #[test]
2127 fn test_parse_namespaces() {
2128 let src = r#"
2129 {
2130 "foo::foo::bar::baz": {
2131 "entityTypes": {},
2132 "actions": {}
2133 }
2134 }"#;
2135 let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
2136 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
2137 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
2138 }
2139
2140 #[test]
2141 #[should_panic(expected = "unknown field `requiredddddd`")]
2142 fn test_schema_file_with_misspelled_required() {
2143 let src = serde_json::json!(
2144 {
2145 "entityTypes": {
2146 "User": {
2147 "shape": {
2148 "type": "Record",
2149 "attributes": {
2150 "favorite": {
2151 "type": "Entity",
2152 "name": "Photo",
2153 "requiredddddd": false
2154 }
2155 }
2156 }
2157 }
2158 },
2159 "actions": {}
2160 });
2161 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2162 println!("{:#?}", schema);
2163 }
2164
2165 #[test]
2166 #[should_panic(expected = "unknown field `nameeeeee`")]
2167 fn test_schema_file_with_misspelled_field() {
2168 let src = serde_json::json!(
2169 {
2170 "entityTypes": {
2171 "User": {
2172 "shape": {
2173 "type": "Record",
2174 "attributes": {
2175 "favorite": {
2176 "type": "Entity",
2177 "nameeeeee": "Photo",
2178 }
2179 }
2180 }
2181 }
2182 },
2183 "actions": {}
2184 });
2185 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2186 println!("{:#?}", schema);
2187 }
2188
2189 #[test]
2190 #[should_panic(expected = "unknown field `extra`")]
2191 fn test_schema_file_with_extra_field() {
2192 let src = serde_json::json!(
2193 {
2194 "entityTypes": {
2195 "User": {
2196 "shape": {
2197 "type": "Record",
2198 "attributes": {
2199 "favorite": {
2200 "type": "Entity",
2201 "name": "Photo",
2202 "extra": "Should not exist"
2203 }
2204 }
2205 }
2206 }
2207 },
2208 "actions": {}
2209 });
2210 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2211 println!("{:#?}", schema);
2212 }
2213
2214 #[test]
2215 #[should_panic(expected = "unknown field `memberOfTypes`")]
2216 fn test_schema_file_with_misplaced_field() {
2217 let src = serde_json::json!(
2218 {
2219 "entityTypes": {
2220 "User": {
2221 "shape": {
2222 "memberOfTypes": [],
2223 "type": "Record",
2224 "attributes": {
2225 "favorite": {
2226 "type": "Entity",
2227 "name": "Photo",
2228 }
2229 }
2230 }
2231 }
2232 },
2233 "actions": {}
2234 });
2235 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2236 println!("{:#?}", schema);
2237 }
2238
2239 #[test]
2240 fn schema_file_with_missing_field() {
2241 let src = serde_json::json!(
2242 {
2243 "": {
2244 "entityTypes": {
2245 "User": {
2246 "shape": {
2247 "type": "Record",
2248 "attributes": {
2249 "favorite": {
2250 "type": "Entity",
2251 }
2252 }
2253 }
2254 }
2255 },
2256 "actions": {}
2257 }
2258 });
2259 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2260 assert_matches!(schema, Err(e) => {
2261 expect_err(
2262 &src,
2263 &miette::Report::new(e),
2264 &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2265 .build());
2266 });
2267 }
2268
2269 #[test]
2270 #[should_panic(expected = "missing field `type`")]
2271 fn schema_file_with_missing_type() {
2272 let src = serde_json::json!(
2273 {
2274 "entityTypes": {
2275 "User": {
2276 "shape": { }
2277 }
2278 },
2279 "actions": {}
2280 });
2281 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2282 println!("{:#?}", schema);
2283 }
2284
2285 #[test]
2286 fn schema_file_unexpected_malformed_attribute() {
2287 let src = serde_json::json!(
2288 { "": {
2289 "entityTypes": {
2290 "User": {
2291 "shape": {
2292 "type": "Record",
2293 "attributes": {
2294 "a": {
2295 "type": "Long",
2296 "attributes": {
2297 "b": {"foo": "bar"}
2298 }
2299 }
2300 }
2301 }
2302 }
2303 },
2304 "actions": {}
2305 }});
2306 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2307 assert_matches!(schema, Err(e) => {
2308 expect_err(
2309 "",
2310 &miette::Report::new(e),
2311 &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2312 );
2313 });
2314 }
2315
2316 #[test]
2317 fn error_in_nested_attribute_fails_fast_top_level_attr() {
2318 let src = serde_json::json!(
2319 {
2320 "": {
2321 "entityTypes": {
2322 "User": {
2323 "shape": {
2324 "type": "Record",
2325 "attributes": {
2326 "foo": {
2327 "type": "Record",
2328 "element": { "type": "Long" }
2330 },
2331 "bar": { "type": "Long" }
2332 }
2333 }
2334 }
2335 },
2336 "actions": {}
2337 }
2338 }
2339 );
2340
2341 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2342 assert_matches!(schema, Err(e) => {
2343 expect_err(
2344 "",
2345 &miette::Report::new(e),
2346 &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2347 );
2348 });
2349 }
2350
2351 #[test]
2352 fn error_in_nested_attribute_fails_fast_nested_attr() {
2353 let src = serde_json::json!(
2354 { "": {
2355 "entityTypes": {
2356 "a": {
2357 "shape": {
2358 "type": "Record",
2359 "attributes": {
2360 "foo": { "type": "Entity", "name": "b" },
2361 "baz": { "type": "Record",
2362 "attributes": {
2363 "z": "Boolean"
2365 }
2366 }
2367 }
2368 }
2369 },
2370 "b": {}
2371 }
2372 } }
2373 );
2374
2375 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2376 assert_matches!(schema, Err(e) => {
2377 expect_err(
2378 "",
2379 &miette::Report::new(e),
2380 &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2381 );
2382 });
2383 }
2384
2385 #[test]
2386 fn missing_namespace() {
2387 let src = r#"
2388 {
2389 "entityTypes": { "User": { } },
2390 "actions": {}
2391 }"#;
2392 let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2393 assert_matches!(schema, Err(e) => {
2394 expect_err(
2395 src,
2396 &miette::Report::new(e),
2397 &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations` at line 3 column 35"#)
2398 .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2399 .build());
2400 });
2401 }
2402}
2403
2404#[cfg(test)]
2406mod strengthened_types {
2407 use cool_asserts::assert_matches;
2408
2409 use super::{
2410 ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
2411 };
2412
2413 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
2416 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
2417 }
2418
2419 #[test]
2420 fn invalid_namespace() {
2421 let src = serde_json::json!(
2422 {
2423 "\n" : {
2424 "entityTypes": {},
2425 "actions": {}
2426 }
2427 });
2428 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2429 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
2430
2431 let src = serde_json::json!(
2432 {
2433 "1" : {
2434 "entityTypes": {},
2435 "actions": {}
2436 }
2437 });
2438 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2439 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
2440
2441 let src = serde_json::json!(
2442 {
2443 "*1" : {
2444 "entityTypes": {},
2445 "actions": {}
2446 }
2447 });
2448 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2449 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
2450
2451 let src = serde_json::json!(
2452 {
2453 "::" : {
2454 "entityTypes": {},
2455 "actions": {}
2456 }
2457 });
2458 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2459 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
2460
2461 let src = serde_json::json!(
2462 {
2463 "A::" : {
2464 "entityTypes": {},
2465 "actions": {}
2466 }
2467 });
2468 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2469 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
2470 }
2471
2472 #[test]
2473 fn invalid_common_type() {
2474 let src = serde_json::json!(
2475 {
2476 "entityTypes": {},
2477 "actions": {},
2478 "commonTypes": {
2479 "" : {
2480 "type": "String"
2481 }
2482 }
2483 });
2484 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2485 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2486
2487 let src = serde_json::json!(
2488 {
2489 "entityTypes": {},
2490 "actions": {},
2491 "commonTypes": {
2492 "~" : {
2493 "type": "String"
2494 }
2495 }
2496 });
2497 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2498 assert_error_matches(schema, "invalid id `~`: invalid token");
2499
2500 let src = serde_json::json!(
2501 {
2502 "entityTypes": {},
2503 "actions": {},
2504 "commonTypes": {
2505 "A::B" : {
2506 "type": "String"
2507 }
2508 }
2509 });
2510 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2511 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2512 }
2513
2514 #[test]
2515 fn invalid_entity_type() {
2516 let src = serde_json::json!(
2517 {
2518 "entityTypes": {
2519 "": {}
2520 },
2521 "actions": {}
2522 });
2523 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2524 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2525
2526 let src = serde_json::json!(
2527 {
2528 "entityTypes": {
2529 "*": {}
2530 },
2531 "actions": {}
2532 });
2533 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2534 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
2535
2536 let src = serde_json::json!(
2537 {
2538 "entityTypes": {
2539 "A::B": {}
2540 },
2541 "actions": {}
2542 });
2543 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2544 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2545 }
2546
2547 #[test]
2548 fn invalid_member_of_types() {
2549 let src = serde_json::json!(
2550 {
2551 "memberOfTypes": [""]
2552 });
2553 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2554 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2555
2556 let src = serde_json::json!(
2557 {
2558 "memberOfTypes": ["*"]
2559 });
2560 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2561 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2562
2563 let src = serde_json::json!(
2564 {
2565 "memberOfTypes": ["A::"]
2566 });
2567 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2568 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2569
2570 let src = serde_json::json!(
2571 {
2572 "memberOfTypes": ["::A"]
2573 });
2574 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2575 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2576 }
2577
2578 #[test]
2579 fn invalid_apply_spec() {
2580 let src = serde_json::json!(
2581 {
2582 "resourceTypes": [""]
2583 });
2584 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2585 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2586
2587 let src = serde_json::json!(
2588 {
2589 "resourceTypes": ["*"]
2590 });
2591 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2592 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2593
2594 let src = serde_json::json!(
2595 {
2596 "resourceTypes": ["A::"]
2597 });
2598 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2599 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2600
2601 let src = serde_json::json!(
2602 {
2603 "resourceTypes": ["::A"]
2604 });
2605 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2606 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2607 }
2608
2609 #[test]
2610 fn invalid_schema_entity_types() {
2611 let src = serde_json::json!(
2612 {
2613 "type": "Entity",
2614 "name": ""
2615 });
2616 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2617 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
2618
2619 let src = serde_json::json!(
2620 {
2621 "type": "Entity",
2622 "name": "*"
2623 });
2624 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2625 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
2626
2627 let src = serde_json::json!(
2628 {
2629 "type": "Entity",
2630 "name": "::A"
2631 });
2632 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2633 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
2634
2635 let src = serde_json::json!(
2636 {
2637 "type": "Entity",
2638 "name": "A::"
2639 });
2640 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2641 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
2642 }
2643
2644 #[test]
2645 fn invalid_action_euid() {
2646 let src = serde_json::json!(
2647 {
2648 "id": "action",
2649 "type": ""
2650 });
2651 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2652 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2653
2654 let src = serde_json::json!(
2655 {
2656 "id": "action",
2657 "type": "*"
2658 });
2659 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2660 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2661
2662 let src = serde_json::json!(
2663 {
2664 "id": "action",
2665 "type": "Action::"
2666 });
2667 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2668 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
2669
2670 let src = serde_json::json!(
2671 {
2672 "id": "action",
2673 "type": "::Action"
2674 });
2675 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2676 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
2677 }
2678
2679 #[test]
2680 fn invalid_schema_common_types() {
2681 let src = serde_json::json!(
2682 {
2683 "type": ""
2684 });
2685 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2686 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
2687
2688 let src = serde_json::json!(
2689 {
2690 "type": "*"
2691 });
2692 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2693 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
2694
2695 let src = serde_json::json!(
2696 {
2697 "type": "::A"
2698 });
2699 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2700 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
2701
2702 let src = serde_json::json!(
2703 {
2704 "type": "A::"
2705 });
2706 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2707 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
2708 }
2709
2710 #[test]
2711 fn invalid_schema_extension_types() {
2712 let src = serde_json::json!(
2713 {
2714 "type": "Extension",
2715 "name": ""
2716 });
2717 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2718 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
2719
2720 let src = serde_json::json!(
2721 {
2722 "type": "Extension",
2723 "name": "*"
2724 });
2725 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2726 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
2727
2728 let src = serde_json::json!(
2729 {
2730 "type": "Extension",
2731 "name": "__cedar::decimal"
2732 });
2733 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2734 assert_error_matches(
2735 schema,
2736 "invalid extension type `__cedar::decimal`: unexpected token `::`",
2737 );
2738
2739 let src = serde_json::json!(
2740 {
2741 "type": "Extension",
2742 "name": "__cedar::"
2743 });
2744 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2745 assert_error_matches(
2746 schema,
2747 "invalid extension type `__cedar::`: unexpected token `::`",
2748 );
2749
2750 let src = serde_json::json!(
2751 {
2752 "type": "Extension",
2753 "name": "::__cedar"
2754 });
2755 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2756 assert_error_matches(
2757 schema,
2758 "invalid extension type `::__cedar`: unexpected token `::`",
2759 );
2760 }
2761}
2762
2763#[cfg(test)]
2765mod entity_tags {
2766 use super::*;
2767 use cedar_policy_core::test_utils::{expect_err, ExpectedErrorMessageBuilder};
2768 use cool_asserts::assert_matches;
2769 use serde_json::json;
2770
2771 #[track_caller]
2773 fn example_json_schema() -> serde_json::Value {
2774 json!({"": {
2775 "entityTypes": {
2776 "User" : {
2777 "shape" : {
2778 "type" : "Record",
2779 "attributes" : {
2780 "jobLevel" : {
2781 "type" : "Long"
2782 },
2783 }
2784 },
2785 "tags" : {
2786 "type" : "Set",
2787 "element": { "type": "String" }
2788 }
2789 },
2790 "Document" : {
2791 "shape" : {
2792 "type" : "Record",
2793 "attributes" : {
2794 "owner" : {
2795 "type" : "Entity",
2796 "name" : "User"
2797 },
2798 }
2799 },
2800 "tags" : {
2801 "type" : "Set",
2802 "element": { "type": "String" }
2803 }
2804 }
2805 },
2806 "actions": {}
2807 }})
2808 }
2809
2810 #[test]
2811 fn roundtrip() {
2812 let json = example_json_schema();
2813 let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
2814 let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
2815 assert_eq!(json, serialized_json_schema);
2816 }
2817
2818 #[test]
2819 fn basic() {
2820 let json = example_json_schema();
2821 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2822 let user = &frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2823 assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Set { element }, loc: None }) => {
2824 assert_matches!(&**element, Type::Type { ty: TypeVariant::String, loc: None }); });
2826 let doc = &frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap();
2827 assert_matches!(&doc.tags, Some(Type::Type { ty: TypeVariant::Set { element }, loc: None }) => {
2828 assert_matches!(&**element, Type::Type { ty: TypeVariant::String, loc: None }); });
2830 })
2831 }
2832
2833 #[test]
2835 fn tag_type_is_common_type() {
2836 let json = json!({"": {
2837 "commonTypes": {
2838 "T": { "type": "String" },
2839 },
2840 "entityTypes": {
2841 "User" : {
2842 "shape" : {
2843 "type" : "Record",
2844 "attributes" : {
2845 "jobLevel" : {
2846 "type" : "Long"
2847 },
2848 }
2849 },
2850 "tags" : { "type" : "T" },
2851 },
2852 },
2853 "actions": {}
2854 }});
2855 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2856 let user = &frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2857 assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name, loc: None }) => {
2858 assert_eq!(&format!("{type_name}"), "T");
2859 });
2860 })
2861 }
2862
2863 #[test]
2865 fn tag_type_is_entity_type() {
2866 let json = json!({"": {
2867 "entityTypes": {
2868 "User" : {
2869 "shape" : {
2870 "type" : "Record",
2871 "attributes" : {
2872 "jobLevel" : {
2873 "type" : "Long"
2874 },
2875 }
2876 },
2877 "tags" : { "type" : "Entity", "name": "User" },
2878 },
2879 },
2880 "actions": {}
2881 }});
2882 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2883 let user = &frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2884 assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Entity { name }, loc: None }) => {
2885 assert_eq!(&format!("{name}"), "User");
2886 });
2887 })
2888 }
2889
2890 #[test]
2892 fn bad_tags() {
2893 let json = json!({"": {
2894 "entityTypes": {
2895 "User": {
2896 "shape": {
2897 "type": "Record",
2898 "attributes": {
2899 "jobLevel": {
2900 "type": "Long"
2901 },
2902 },
2903 "tags": { "type": "String" },
2904 }
2905 },
2906 },
2907 "actions": {}
2908 }});
2909 assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
2910 expect_err(
2911 &json,
2912 &miette::Report::new(e),
2913 &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
2914 .build(),
2915 );
2916 });
2917 }
2918}
2919
2920#[cfg(test)]
2922mod test_json_roundtrip {
2923 use super::*;
2924
2925 #[track_caller] fn roundtrip(schema: &Fragment<RawName>) {
2927 let json = serde_json::to_value(schema.clone()).unwrap();
2928 let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
2929 assert_eq!(schema, &new_schema);
2930 }
2931
2932 #[test]
2933 fn empty_namespace() {
2934 let fragment = Fragment(BTreeMap::from([(None, NamespaceDefinition::new([], []))]));
2935 roundtrip(&fragment);
2936 }
2937
2938 #[test]
2939 fn nonempty_namespace() {
2940 let fragment = Fragment(BTreeMap::from([(
2941 Some("a".parse().unwrap()),
2942 NamespaceDefinition::new([], []),
2943 )]));
2944 roundtrip(&fragment);
2945 }
2946
2947 #[test]
2948 fn nonempty_entity_types() {
2949 let fragment = Fragment(BTreeMap::from([(
2950 None,
2951 NamespaceDefinition::new(
2952 [(
2953 "a".parse().unwrap(),
2954 EntityType {
2955 member_of_types: vec!["a".parse().unwrap()],
2956 shape: AttributesOrContext(Type::Type {
2957 ty: TypeVariant::Record(RecordType {
2958 attributes: BTreeMap::new(),
2959 additional_attributes: false,
2960 }),
2961 loc: None,
2962 }),
2963 tags: None,
2964 annotations: Annotations::new(),
2965 loc: None,
2966 },
2967 )],
2968 [(
2969 "action".into(),
2970 ActionType {
2971 attributes: None,
2972 applies_to: Some(ApplySpec {
2973 resource_types: vec!["a".parse().unwrap()],
2974 principal_types: vec!["a".parse().unwrap()],
2975 context: AttributesOrContext(Type::Type {
2976 ty: TypeVariant::Record(RecordType {
2977 attributes: BTreeMap::new(),
2978 additional_attributes: false,
2979 }),
2980 loc: None,
2981 }),
2982 }),
2983 member_of: None,
2984 annotations: Annotations::new(),
2985 loc: None,
2986 },
2987 )],
2988 ),
2989 )]));
2990 roundtrip(&fragment);
2991 }
2992
2993 #[test]
2994 fn multiple_namespaces() {
2995 let fragment = Fragment(BTreeMap::from([
2996 (
2997 Some("foo".parse().unwrap()),
2998 NamespaceDefinition::new(
2999 [(
3000 "a".parse().unwrap(),
3001 EntityType {
3002 member_of_types: vec!["a".parse().unwrap()],
3003 shape: AttributesOrContext(Type::Type {
3004 ty: TypeVariant::Record(RecordType {
3005 attributes: BTreeMap::new(),
3006 additional_attributes: false,
3007 }),
3008 loc: None,
3009 }),
3010 tags: None,
3011 annotations: Annotations::new(),
3012 loc: None,
3013 },
3014 )],
3015 [],
3016 ),
3017 ),
3018 (
3019 None,
3020 NamespaceDefinition::new(
3021 [],
3022 [(
3023 "action".into(),
3024 ActionType {
3025 attributes: None,
3026 applies_to: Some(ApplySpec {
3027 resource_types: vec!["foo::a".parse().unwrap()],
3028 principal_types: vec!["foo::a".parse().unwrap()],
3029 context: AttributesOrContext(Type::Type {
3030 ty: TypeVariant::Record(RecordType {
3031 attributes: BTreeMap::new(),
3032 additional_attributes: false,
3033 }),
3034 loc: None,
3035 }),
3036 }),
3037 member_of: None,
3038 annotations: Annotations::new(),
3039 loc: None,
3040 },
3041 )],
3042 ),
3043 ),
3044 ]));
3045 roundtrip(&fragment);
3046 }
3047}
3048
3049#[cfg(test)]
3054mod test_duplicates_error {
3055 use super::*;
3056
3057 #[test]
3058 #[should_panic(expected = "invalid entry: found duplicate key")]
3059 fn namespace() {
3060 let src = r#"{
3061 "Foo": {
3062 "entityTypes" : {},
3063 "actions": {}
3064 },
3065 "Foo": {
3066 "entityTypes" : {},
3067 "actions": {}
3068 }
3069 }"#;
3070 Fragment::from_json_str(src).unwrap();
3071 }
3072
3073 #[test]
3074 #[should_panic(expected = "invalid entry: found duplicate key")]
3075 fn entity_type() {
3076 let src = r#"{
3077 "Foo": {
3078 "entityTypes" : {
3079 "Bar": {},
3080 "Bar": {}
3081 },
3082 "actions": {}
3083 }
3084 }"#;
3085 Fragment::from_json_str(src).unwrap();
3086 }
3087
3088 #[test]
3089 #[should_panic(expected = "invalid entry: found duplicate key")]
3090 fn action() {
3091 let src = r#"{
3092 "Foo": {
3093 "entityTypes" : {},
3094 "actions": {
3095 "Bar": {},
3096 "Bar": {}
3097 }
3098 }
3099 }"#;
3100 Fragment::from_json_str(src).unwrap();
3101 }
3102
3103 #[test]
3104 #[should_panic(expected = "invalid entry: found duplicate key")]
3105 fn common_types() {
3106 let src = r#"{
3107 "Foo": {
3108 "entityTypes" : {},
3109 "actions": { },
3110 "commonTypes": {
3111 "Bar": {"type": "Long"},
3112 "Bar": {"type": "String"}
3113 }
3114 }
3115 }"#;
3116 Fragment::from_json_str(src).unwrap();
3117 }
3118
3119 #[test]
3120 #[should_panic(expected = "invalid entry: found duplicate key")]
3121 fn record_type() {
3122 let src = r#"{
3123 "Foo": {
3124 "entityTypes" : {
3125 "Bar": {
3126 "shape": {
3127 "type": "Record",
3128 "attributes": {
3129 "Baz": {"type": "Long"},
3130 "Baz": {"type": "String"}
3131 }
3132 }
3133 }
3134 },
3135 "actions": { }
3136 }
3137 }"#;
3138 Fragment::from_json_str(src).unwrap();
3139 }
3140
3141 #[test]
3142 #[should_panic(expected = "missing field `resourceTypes`")]
3143 fn missing_resource() {
3144 let src = r#"{
3145 "Foo": {
3146 "entityTypes" : {},
3147 "actions": {
3148 "foo" : {
3149 "appliesTo" : {
3150 "principalTypes" : ["a"]
3151 }
3152 }
3153 }
3154 }
3155 }"#;
3156 Fragment::from_json_str(src).unwrap();
3157 }
3158
3159 #[test]
3160 #[should_panic(expected = "missing field `principalTypes`")]
3161 fn missing_principal() {
3162 let src = r#"{
3163 "Foo": {
3164 "entityTypes" : {},
3165 "actions": {
3166 "foo" : {
3167 "appliesTo" : {
3168 "resourceTypes" : ["a"]
3169 }
3170 }
3171 }
3172 }
3173 }"#;
3174 Fragment::from_json_str(src).unwrap();
3175 }
3176
3177 #[test]
3178 #[should_panic(expected = "missing field `resourceTypes`")]
3179 fn missing_both() {
3180 let src = r#"{
3181 "Foo": {
3182 "entityTypes" : {},
3183 "actions": {
3184 "foo" : {
3185 "appliesTo" : {
3186 }
3187 }
3188 }
3189 }
3190 }"#;
3191 Fragment::from_json_str(src).unwrap();
3192 }
3193}
3194
3195#[cfg(test)]
3196mod annotations {
3197 use crate::RawName;
3198 use cool_asserts::assert_matches;
3199
3200 use super::Fragment;
3201
3202 #[test]
3203 fn empty_namespace() {
3204 let src = serde_json::json!(
3205 {
3206 "" : {
3207 "entityTypes": {},
3208 "actions": {},
3209 "annotations": {
3210 "doc": "this is a doc"
3211 }
3212 }
3213 });
3214 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3215 assert_matches!(schema, Err(err) => {
3216 assert_eq!(&err.to_string(), "annotations are not allowed on the empty namespace");
3217 });
3218 }
3219
3220 #[test]
3221 fn basic() {
3222 let src = serde_json::json!(
3223 {
3224 "N" : {
3225 "entityTypes": {},
3226 "actions": {},
3227 "annotations": {
3228 "doc": "this is a doc"
3229 }
3230 }
3231 });
3232 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3233 assert_matches!(schema, Ok(_));
3234
3235 let src = serde_json::json!(
3236 {
3237 "N" : {
3238 "entityTypes": {
3239 "a": {
3240 "annotations": {
3241 "a": "",
3242 "d": null,
3244 "b": "c",
3245 },
3246 "shape": {
3247 "type": "Long",
3248 }
3249 }
3250 },
3251 "actions": {},
3252 "annotations": {
3253 "doc": "this is a doc"
3254 }
3255 }
3256 });
3257 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3258 assert_matches!(schema, Ok(_));
3259
3260 let src = serde_json::json!(
3261 {
3262 "N" : {
3263 "entityTypes": {
3264 "a": {
3265 "annotations": {
3266 "a": "",
3267 "b": "c",
3268 },
3269 "shape": {
3270 "type": "Long",
3271 }
3272 }
3273 },
3274 "actions": {
3275 "a": {
3276 "annotations": {
3277 "doc": "this is a doc"
3278 },
3279 "appliesTo": {
3280 "principalTypes": ["A"],
3281 "resourceTypes": ["B"],
3282 }
3283 },
3284 },
3285 "annotations": {
3286 "doc": "this is a doc"
3287 }
3288 }
3289 });
3290 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3291 assert_matches!(schema, Ok(_));
3292
3293 let src = serde_json::json!({
3294 "N": {
3295 "entityTypes": {},
3296 "actions": {},
3297 "commonTypes": {
3298 "Task": {
3299 "annotations": {
3300 "doc": "a common type representing a task"
3301 },
3302 "type": "Record",
3303 "attributes": {
3304 "id": {
3305 "type": "Long",
3306 "annotations": {
3307 "doc": "task id"
3308 }
3309 },
3310 "name": {
3311 "type": "String"
3312 },
3313 "state": {
3314 "type": "String"
3315 }
3316 }
3317 }}}});
3318 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3319 assert_matches!(schema, Ok(_));
3320
3321 let src = serde_json::json!({
3322 "N": {
3323 "entityTypes": {
3324 "User" : {
3325 "shape" : {
3326 "type" : "Record",
3327 "attributes" : {
3328 "name" : {
3329 "annotations": {
3330 "a": null,
3331 },
3332 "type" : "String"
3333 },
3334 "age" : {
3335 "type" : "Long"
3336 }
3337 }
3338 }
3339 }
3340 },
3341 "actions": {},
3342 "commonTypes": {}
3343 }});
3344 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3345 assert_matches!(schema, Ok(_));
3346
3347 let src = serde_json::json!({
3349 "N": {
3350 "entityTypes": {
3351 "User" : {
3352 "shape" : {
3353 "type" : "Record",
3354 "attributes" : {
3355 "name" : {
3356 "annotations": {
3357 "first_layer": "b"
3358 },
3359 "type" : "Record",
3360 "attributes": {
3361 "a": {
3362 "type": "Record",
3363 "annotations": {
3364 "second_layer": "d"
3365 },
3366 "attributes": {
3367 "...": {
3368 "annotations": {
3369 "last_layer": null,
3370 },
3371 "type": "Long"
3372 }
3373 }
3374 }
3375 }
3376 },
3377 "age" : {
3378 "type" : "Long"
3379 }
3380 }
3381 }
3382 }
3383 },
3384 "actions": {},
3385 "commonTypes": {}
3386 }});
3387 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3388 assert_matches!(schema, Ok(_));
3389 }
3390
3391 #[track_caller]
3392 fn test_unknown_fields(src: serde_json::Value, field: &str, expected: &str) {
3393 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3394 assert_matches!(schema, Err(errs) => {
3395 assert_eq!(errs.to_string(), format!("unknown field {field}, expected one of {expected}"));
3396 });
3397 }
3398
3399 const ENTITY_TYPE_EXPECTED_ATTRIBUTES: &str = "`memberOfTypes`, `shape`, `tags`, `annotations`";
3400 const NAMESPACE_EXPECTED_ATTRIBUTES: &str =
3401 "`commonTypes`, `entityTypes`, `actions`, `annotations`";
3402 const ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES: &str =
3403 "`type`, `element`, `attributes`, `additionalAttributes`, `name`";
3404 const APPLIES_TO_EXPECTED_ATTRIBUTES: &str = "`resourceTypes`, `principalTypes`, `context`";
3405
3406 #[test]
3407 fn unknown_fields() {
3408 let src = serde_json::json!(
3409 {
3410 "N": {
3411 "entityTypes": {
3412 "UserGroup": {
3413 "shape44": {
3414 "type": "Record",
3415 "attributes": {}
3416 },
3417 "memberOfTypes": [
3418 "UserGroup"
3419 ]
3420 }},
3421 "actions": {},
3422 }});
3423 test_unknown_fields(src, "`shape44`", ENTITY_TYPE_EXPECTED_ATTRIBUTES);
3424
3425 let src = serde_json::json!(
3426 {
3427 "N": {
3428 "entityTypes": {},
3429 "actions": {},
3430 "commonTypes": {
3431 "C": {
3432 "type": "Set",
3433 "element": {
3434 "annotations": {
3435 "doc": "this is a doc"
3436 },
3437 "type": "Long"
3438 }
3439 }
3440 }}});
3441 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3442
3443 let src = serde_json::json!(
3444 {
3445 "N": {
3446 "entityTypes": {},
3447 "actions": {},
3448 "commonTypes": {
3449 "C": {
3450 "type": "Long",
3451 "foo": 1,
3452 "annotations": {
3453 "doc": "this is a doc"
3454 },
3455 }}}});
3456 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3457
3458 let src = serde_json::json!(
3459 {
3460 "N": {
3461 "entityTypes": {},
3462 "actions": {},
3463 "commonTypes": {
3464 "C": {
3465 "type": "Record",
3466 "attributes": {
3467 "a": {
3468 "annotations": {
3469 "doc": "this is a doc"
3470 },
3471 "type": "Long",
3472 "foo": 2,
3473 "required": true,
3474 }
3475 },
3476 }}}});
3477 test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3478
3479 let src = serde_json::json!(
3480 {
3481 "N": {
3482 "entityTypes": {},
3483 "actions": {},
3484 "commonTypes": {
3485 "C": {
3486 "type": "Record",
3487 "attributes": {
3488 "a": {
3489 "annotations": {
3490 "doc": "this is a doc"
3491 },
3492 "type": "Record",
3493 "attributes": {
3494 "b": {
3495 "annotations": {
3496 "doc": "this is a doc"
3497 },
3498 "type": "Long",
3499 "bar": 3,
3500 },
3501 },
3502 "required": true,
3503 }
3504 },
3505 }}}});
3506 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3507
3508 let src = serde_json::json!(
3509 {
3510 "N": {
3511 "entityTypes": {
3512 "UserGroup": {
3513 "shape": {
3514 "annotations": {
3515 "doc": "this is a doc"
3516 },
3517 "type": "Record",
3518 "attributes": {}
3519 },
3520 "memberOfTypes": [
3521 "UserGroup"
3522 ]
3523 }},
3524 "actions": {},
3525 }});
3526 test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3527
3528 let src = serde_json::json!(
3529 {
3530 "N": {
3531 "entityTypes": {},
3532 "actions": {
3533 "a": {
3534 "appliesTo": {
3535 "annotations": {
3536 "doc": "this is a doc"
3537 },
3538 "principalTypes": ["A"],
3539 "resourceTypes": ["B"],
3540 }
3541 },
3542 },
3543 }});
3544 test_unknown_fields(src, "`annotations`", APPLIES_TO_EXPECTED_ATTRIBUTES);
3545
3546 let src = serde_json::json!(
3547 {
3548 "N" : {
3549 "entityTypes": {},
3550 "actions": {},
3551 "foo": "",
3552 "annotations": {
3553 "doc": "this is a doc"
3554 }
3555 }
3556 });
3557 test_unknown_fields(src, "`foo`", NAMESPACE_EXPECTED_ATTRIBUTES);
3558
3559 let src = serde_json::json!(
3560 {
3561 "" : {
3562 "entityTypes": {},
3563 "actions": {},
3564 "commonTypes": {
3565 "a": {
3566 "type": "Long",
3567 "annotations": {
3568 "foo": ""
3569 },
3570 "bar": 1
3571 }
3572 }
3573 }
3574 });
3575 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3576
3577 let src = serde_json::json!(
3578 {
3579 "N" : {
3580 "entityTypes": {},
3581 "actions": {},
3582 "commonTypes": {
3583 "a": {
3584 "type": "Record",
3585 "annotations": {
3586 "foo": ""
3587 },
3588 "attributes": {
3589 "a": {
3590 "bar": 1,
3591 "type": "Long"
3592 }
3593 }
3594 }
3595 }
3596 }
3597 });
3598 test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3599 }
3600}
3601
3602#[cfg(test)]
3603mod ord {
3604 use super::{InternalName, RawName, Type, TypeVariant};
3605 use std::collections::BTreeSet;
3606
3607 #[test]
3609 #[allow(clippy::collection_is_never_read)]
3610 fn type_ord() {
3611 let mut set: BTreeSet<Type<RawName>> = BTreeSet::default();
3612 set.insert(Type::Type {
3613 ty: TypeVariant::String,
3614 loc: None,
3615 });
3616 let mut set: BTreeSet<Type<InternalName>> = BTreeSet::default();
3617 set.insert(Type::Type {
3618 ty: TypeVariant::String,
3619 loc: None,
3620 });
3621 }
3622}