1use cedar_policy_core::{
20 ast::{Eid, EntityUID, InternalName, Name, UnreservedId},
21 entities::CedarValueJson,
22 extensions::Extensions,
23 FromNormalizedStr,
24};
25use nonempty::nonempty;
26use serde::{
27 de::{MapAccess, Visitor},
28 ser::SerializeMap,
29 Deserialize, Deserializer, Serialize, Serializer,
30};
31use serde_with::serde_as;
32use smol_str::{SmolStr, ToSmolStr};
33use std::{
34 collections::{BTreeMap, HashMap, HashSet},
35 fmt::Display,
36 marker::PhantomData,
37 str::FromStr,
38};
39use thiserror::Error;
40
41use crate::{
42 cedar_schema::{
43 self, fmt::ToCedarSchemaSyntaxError, parser::parse_cedar_schema_fragment, SchemaWarning,
44 },
45 err::{schema_errors::*, Result},
46 AllDefs, CedarSchemaError, CedarSchemaParseError, ConditionalName, RawName, ReferenceType,
47};
48
49#[derive(Debug, Clone, PartialEq, Deserialize)]
69#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
70#[serde(transparent)]
71#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
72#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
73#[cfg_attr(feature = "wasm", serde(rename = "SchemaJson"))]
74pub struct Fragment<N>(
75 #[serde(deserialize_with = "deserialize_schema_fragment")]
76 #[cfg_attr(
77 feature = "wasm",
78 tsify(type = "Record<string, NamespaceDefinition<N>>")
79 )]
80 pub BTreeMap<Option<Name>, NamespaceDefinition<N>>,
81);
82
83fn deserialize_schema_fragment<'de, D, N: Deserialize<'de> + From<RawName>>(
85 deserializer: D,
86) -> std::result::Result<BTreeMap<Option<Name>, NamespaceDefinition<N>>, D::Error>
87where
88 D: Deserializer<'de>,
89{
90 let raw: BTreeMap<SmolStr, NamespaceDefinition<N>> =
91 serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
92 Ok(BTreeMap::from_iter(
93 raw.into_iter()
94 .map(|(key, value)| {
95 let key = if key.is_empty() {
96 None
97 } else {
98 Some(Name::from_normalized_str(&key).map_err(|err| {
99 serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
100 })?)
101 };
102 Ok((key, value))
103 })
104 .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition<N>)>, D::Error>>(
105 )?,
106 ))
107}
108
109impl<N: Serialize> Serialize for Fragment<N> {
110 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
112 where
113 S: Serializer,
114 {
115 let mut map = serializer.serialize_map(Some(self.0.len()))?;
116 for (k, v) in &self.0 {
117 let k: SmolStr = match k {
118 None => "".into(),
119 Some(name) => name.to_smolstr(),
120 };
121 map.serialize_entry(&k, &v)?;
122 }
123 map.end()
124 }
125}
126
127impl Fragment<RawName> {
128 pub fn from_json_str(json: &str) -> Result<Self> {
131 serde_json::from_str(json).map_err(|e| JsonDeserializationError::new(e, Some(json)).into())
132 }
133
134 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
137 serde_json::from_value(json).map_err(|e| JsonDeserializationError::new(e, None).into())
138 }
139
140 pub fn from_json_file(file: impl std::io::Read) -> Result<Self> {
142 serde_json::from_reader(file).map_err(|e| JsonDeserializationError::new(e, None).into())
143 }
144
145 pub fn from_cedarschema_str<'a>(
147 src: &str,
148 extensions: &Extensions<'a>,
149 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
150 {
151 parse_cedar_schema_fragment(src, extensions)
152 .map_err(|e| CedarSchemaParseError::new(e, src).into())
153 }
154
155 pub fn from_cedarschema_file<'a>(
157 mut file: impl std::io::Read,
158 extensions: &'a Extensions<'_>,
159 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
160 {
161 let mut src = String::new();
162 file.read_to_string(&mut src)?;
163 Self::from_cedarschema_str(&src, extensions)
164 }
165}
166
167impl<N: Display> Fragment<N> {
168 pub fn to_cedarschema(&self) -> std::result::Result<String, ToCedarSchemaSyntaxError> {
170 let src = cedar_schema::fmt::json_schema_to_cedar_schema_str(self)?;
171 Ok(src)
172 }
173}
174
175#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, PartialOrd, Ord)]
178#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
179#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
180pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
181
182impl From<CommonTypeId> for UnreservedId {
183 fn from(value: CommonTypeId) -> Self {
184 value.0
185 }
186}
187
188impl AsRef<UnreservedId> for CommonTypeId {
189 fn as_ref(&self) -> &UnreservedId {
190 &self.0
191 }
192}
193
194impl CommonTypeId {
195 pub fn new(id: UnreservedId) -> std::result::Result<Self, ReservedCommonTypeBasenameError> {
197 if Self::is_reserved_schema_keyword(&id) {
198 Err(ReservedCommonTypeBasenameError { id })
199 } else {
200 Ok(Self(id))
201 }
202 }
203
204 pub fn unchecked(id: UnreservedId) -> Self {
207 Self(id)
208 }
209
210 fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
215 matches!(
216 id.as_ref(),
217 "Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
218 )
219 }
220
221 #[cfg(feature = "arbitrary")]
224 fn make_into_valid_common_type_id(id: UnreservedId) -> Self {
225 Self::new(id.clone()).unwrap_or_else(|_| {
226 #[allow(clippy::unwrap_used)]
228 let new_id = format!("_{id}").parse().unwrap();
229 #[allow(clippy::unwrap_used)]
231 Self::new(new_id).unwrap()
232 })
233 }
234}
235
236impl Display for CommonTypeId {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 self.0.fmt(f)
239 }
240}
241
242#[cfg(feature = "arbitrary")]
243impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
244 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
245 let id: UnreservedId = u.arbitrary()?;
246 Ok(CommonTypeId::make_into_valid_common_type_id(id))
247 }
248
249 fn size_hint(depth: usize) -> (usize, Option<usize>) {
250 <UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
251 }
252}
253
254impl<'de> Deserialize<'de> for CommonTypeId {
256 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
257 where
258 D: Deserializer<'de>,
259 {
260 UnreservedId::deserialize(deserializer).and_then(|id| {
261 CommonTypeId::new(id).map_err(|e| serde::de::Error::custom(format!("{e}")))
262 })
263 }
264}
265
266#[derive(Debug, Error, PartialEq, Clone)]
268#[error("this is reserved and cannot be the basename of a common-type declaration: {id}")]
269pub struct ReservedCommonTypeBasenameError {
270 pub(crate) id: UnreservedId,
272}
273
274#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
284#[serde_as]
285#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
286#[serde(bound(serialize = "N: Serialize"))]
287#[serde(deny_unknown_fields)]
288#[serde(rename_all = "camelCase")]
289#[doc(hidden)]
290#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
291#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
292pub struct NamespaceDefinition<N> {
293 #[serde(default)]
294 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
295 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
296 pub common_types: BTreeMap<CommonTypeId, Type<N>>,
297 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
298 pub entity_types: BTreeMap<UnreservedId, EntityType<N>>,
299 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
300 pub actions: BTreeMap<SmolStr, ActionType<N>>,
301}
302
303impl<N> NamespaceDefinition<N> {
304 pub fn new(
305 entity_types: impl IntoIterator<Item = (UnreservedId, EntityType<N>)>,
306 actions: impl IntoIterator<Item = (SmolStr, ActionType<N>)>,
307 ) -> Self {
308 Self {
309 common_types: BTreeMap::new(),
310 entity_types: entity_types.into_iter().collect(),
311 actions: actions.into_iter().collect(),
312 }
313 }
314}
315
316impl NamespaceDefinition<RawName> {
317 pub fn conditionally_qualify_type_references(
319 self,
320 ns: Option<&InternalName>,
321 ) -> NamespaceDefinition<ConditionalName> {
322 NamespaceDefinition {
323 common_types: self
324 .common_types
325 .into_iter()
326 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
327 .collect(),
328 entity_types: self
329 .entity_types
330 .into_iter()
331 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
332 .collect(),
333 actions: self
334 .actions
335 .into_iter()
336 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
337 .collect(),
338 }
339 }
340}
341
342impl NamespaceDefinition<ConditionalName> {
343 pub fn fully_qualify_type_references(
350 self,
351 all_defs: &AllDefs,
352 ) -> Result<NamespaceDefinition<InternalName>> {
353 Ok(NamespaceDefinition {
354 common_types: self
355 .common_types
356 .into_iter()
357 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
358 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
359 entity_types: self
360 .entity_types
361 .into_iter()
362 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
363 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
364 actions: self
365 .actions
366 .into_iter()
367 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
368 .collect::<Result<_>>()?,
369 })
370 }
371}
372
373#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
382#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
383#[serde(deny_unknown_fields)]
384#[serde(rename_all = "camelCase")]
385#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
386#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
387pub struct EntityType<N> {
388 #[serde(default)]
391 #[serde(skip_serializing_if = "Vec::is_empty")]
392 pub member_of_types: Vec<N>,
393 #[serde(default)]
395 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
396 pub shape: AttributesOrContext<N>,
397 #[serde(default)]
399 #[serde(skip_serializing_if = "Option::is_none")]
400 pub tags: Option<Type<N>>,
401}
402
403impl EntityType<RawName> {
404 pub fn conditionally_qualify_type_references(
406 self,
407 ns: Option<&InternalName>,
408 ) -> EntityType<ConditionalName> {
409 EntityType {
410 member_of_types: self
411 .member_of_types
412 .into_iter()
413 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
415 shape: self.shape.conditionally_qualify_type_references(ns),
416 tags: self
417 .tags
418 .map(|ty| ty.conditionally_qualify_type_references(ns)),
419 }
420 }
421}
422
423impl EntityType<ConditionalName> {
424 pub fn fully_qualify_type_references(
431 self,
432 all_defs: &AllDefs,
433 ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
434 Ok(EntityType {
435 member_of_types: self
436 .member_of_types
437 .into_iter()
438 .map(|cname| cname.resolve(all_defs))
439 .collect::<std::result::Result<_, _>>()?,
440 shape: self.shape.fully_qualify_type_references(all_defs)?,
441 tags: self
442 .tags
443 .map(|ty| ty.fully_qualify_type_references(all_defs))
444 .transpose()?,
445 })
446 }
447}
448
449#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
457#[serde(transparent)]
458#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
459#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
460pub struct AttributesOrContext<N>(
461 pub Type<N>,
464);
465
466impl<N> AttributesOrContext<N> {
467 pub fn into_inner(self) -> Type<N> {
469 self.0
470 }
471
472 pub fn is_empty_record(&self) -> bool {
474 self.0.is_empty_record()
475 }
476}
477
478impl<N> Default for AttributesOrContext<N> {
479 fn default() -> Self {
480 Self::from(RecordType::default())
481 }
482}
483
484impl<N: Display> Display for AttributesOrContext<N> {
485 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
486 self.0.fmt(f)
487 }
488}
489
490impl<N> From<RecordType<N>> for AttributesOrContext<N> {
491 fn from(rty: RecordType<N>) -> AttributesOrContext<N> {
492 Self(Type::Type(TypeVariant::Record(rty)))
493 }
494}
495
496impl AttributesOrContext<RawName> {
497 pub fn conditionally_qualify_type_references(
499 self,
500 ns: Option<&InternalName>,
501 ) -> AttributesOrContext<ConditionalName> {
502 AttributesOrContext(self.0.conditionally_qualify_type_references(ns))
503 }
504}
505
506impl AttributesOrContext<ConditionalName> {
507 pub fn fully_qualify_type_references(
514 self,
515 all_defs: &AllDefs,
516 ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
517 Ok(AttributesOrContext(
518 self.0.fully_qualify_type_references(all_defs)?,
519 ))
520 }
521}
522
523#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
531#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
532#[serde(deny_unknown_fields)]
533#[serde(rename_all = "camelCase")]
534#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
535#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
536pub struct ActionType<N> {
537 #[serde(default)]
541 #[serde(skip_serializing_if = "Option::is_none")]
542 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
543 #[serde(default)]
545 #[serde(skip_serializing_if = "Option::is_none")]
546 pub applies_to: Option<ApplySpec<N>>,
547 #[serde(default)]
549 #[serde(skip_serializing_if = "Option::is_none")]
550 pub member_of: Option<Vec<ActionEntityUID<N>>>,
551}
552
553impl ActionType<RawName> {
554 pub fn conditionally_qualify_type_references(
556 self,
557 ns: Option<&InternalName>,
558 ) -> ActionType<ConditionalName> {
559 ActionType {
560 attributes: self.attributes,
561 applies_to: self
562 .applies_to
563 .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
564 member_of: self.member_of.map(|v| {
565 v.into_iter()
566 .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
567 .collect()
568 }),
569 }
570 }
571}
572
573impl ActionType<ConditionalName> {
574 pub fn fully_qualify_type_references(
581 self,
582 all_defs: &AllDefs,
583 ) -> Result<ActionType<InternalName>> {
584 Ok(ActionType {
585 attributes: self.attributes,
586 applies_to: self
587 .applies_to
588 .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
589 .transpose()?,
590 member_of: self
591 .member_of
592 .map(|v| {
593 v.into_iter()
594 .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
595 .collect::<std::result::Result<_, ActionNotDefinedError>>()
596 })
597 .transpose()?,
598 })
599 }
600}
601
602#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
612#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
613#[serde(deny_unknown_fields)]
614#[serde(rename_all = "camelCase")]
615#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
616#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
617pub struct ApplySpec<N> {
618 pub resource_types: Vec<N>,
620 pub principal_types: Vec<N>,
622 #[serde(default)]
624 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
625 pub context: AttributesOrContext<N>,
626}
627
628impl ApplySpec<RawName> {
629 pub fn conditionally_qualify_type_references(
631 self,
632 ns: Option<&InternalName>,
633 ) -> ApplySpec<ConditionalName> {
634 ApplySpec {
635 resource_types: self
636 .resource_types
637 .into_iter()
638 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
640 principal_types: self
641 .principal_types
642 .into_iter()
643 .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) .collect(),
645 context: self.context.conditionally_qualify_type_references(ns),
646 }
647 }
648}
649
650impl ApplySpec<ConditionalName> {
651 pub fn fully_qualify_type_references(
658 self,
659 all_defs: &AllDefs,
660 ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
661 Ok(ApplySpec {
662 resource_types: self
663 .resource_types
664 .into_iter()
665 .map(|cname| cname.resolve(all_defs))
666 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
667 principal_types: self
668 .principal_types
669 .into_iter()
670 .map(|cname| cname.resolve(all_defs))
671 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
672 context: self.context.fully_qualify_type_references(all_defs)?,
673 })
674 }
675}
676
677#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
679#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
680#[serde(deny_unknown_fields)]
681#[serde(rename_all = "camelCase")]
682#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
683#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
684pub struct ActionEntityUID<N> {
685 pub id: SmolStr,
687
688 #[serde(rename = "type")]
699 #[serde(default)]
700 #[serde(skip_serializing_if = "Option::is_none")]
701 ty: Option<N>,
702}
703
704impl ActionEntityUID<RawName> {
705 pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
708 Self { id, ty }
709 }
710
711 pub fn default_type(id: SmolStr) -> Self {
716 Self { id, ty: None }
717 }
718}
719
720impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
721 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
722 if let Some(ty) = &self.ty {
723 write!(f, "{}::", ty)?
724 } else {
725 write!(f, "Action::")?
726 }
727 write!(f, "\"{}\"", self.id.escape_debug())
728 }
729}
730
731impl ActionEntityUID<RawName> {
732 pub fn conditionally_qualify_type_references(
734 self,
735 ns: Option<&InternalName>,
736 ) -> ActionEntityUID<ConditionalName> {
737 ActionEntityUID {
740 id: self.id,
741 ty: {
742 #[allow(clippy::expect_used)]
744 let raw_name = self
745 .ty
746 .unwrap_or(RawName::from_str("Action").expect("valid raw name"));
747 Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
748 },
749 }
750 }
751
752 pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
754 ActionEntityUID {
757 id: self.id,
758 ty: {
759 #[allow(clippy::expect_used)]
761 let raw_name = self
762 .ty
763 .unwrap_or(RawName::from_str("Action").expect("valid raw name"));
764 Some(raw_name.qualify_with(ns))
765 },
766 }
767 }
768}
769
770impl ActionEntityUID<ConditionalName> {
771 pub fn ty(&self) -> &ConditionalName {
773 #[allow(clippy::expect_used)]
775 self.ty.as_ref().expect("by INVARIANT on self.ty")
776 }
777
778 pub fn fully_qualify_type_references(
786 self,
787 all_defs: &AllDefs,
788 ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
789 for possibility in self.possibilities() {
790 if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
794 if all_defs.is_defined_as_action(&euid) {
795 return Ok(possibility);
796 }
797 }
798 }
799 Err(ActionNotDefinedError(nonempty!(self)))
800 }
801
802 pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
806 self.ty()
809 .possibilities()
810 .map(|possibility| ActionEntityUID {
811 id: self.id.clone(),
812 ty: Some(possibility.clone()),
813 })
814 }
815
816 pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
820 ActionEntityUID {
821 id: self.id.clone(),
822 ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
823 }
824 }
825}
826
827impl ActionEntityUID<Name> {
828 pub fn ty(&self) -> &Name {
830 #[allow(clippy::expect_used)]
832 self.ty.as_ref().expect("by INVARIANT on self.ty")
833 }
834}
835
836impl ActionEntityUID<InternalName> {
837 pub fn ty(&self) -> &InternalName {
839 #[allow(clippy::expect_used)]
841 self.ty.as_ref().expect("by INVARIANT on self.ty")
842 }
843}
844
845impl From<ActionEntityUID<Name>> for EntityUID {
846 fn from(aeuid: ActionEntityUID<Name>) -> Self {
847 EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
848 }
849}
850
851impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
852 type Error = <InternalName as TryInto<Name>>::Error;
853 fn try_from(aeuid: ActionEntityUID<InternalName>) -> std::result::Result<Self, Self::Error> {
854 let ty = Name::try_from(aeuid.ty().clone())?;
855 Ok(EntityUID::from_components(
856 ty.into(),
857 Eid::new(aeuid.id),
858 None,
859 ))
860 }
861}
862
863impl From<EntityUID> for ActionEntityUID<Name> {
864 fn from(euid: EntityUID) -> Self {
865 let (ty, id) = euid.components();
866 ActionEntityUID {
867 ty: Some(ty.into()),
868 id: <Eid as AsRef<SmolStr>>::as_ref(&id).clone(),
869 }
870 }
871}
872
873#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
880#[serde(untagged)]
885#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
886#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
887pub enum Type<N> {
888 Type(TypeVariant<N>),
892 CommonTypeRef {
898 #[serde(rename = "type")]
903 type_name: N,
904 },
905}
906
907impl<N> Type<N> {
908 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
911 match self {
912 Type::Type(TypeVariant::Record(RecordType { attributes, .. })) => attributes
913 .iter()
914 .map(|(_, ty)| ty.ty.common_type_references())
915 .fold(Box::new(std::iter::empty()), |it, tys| {
916 Box::new(it.chain(tys))
917 }),
918 Type::Type(TypeVariant::Set { element }) => element.common_type_references(),
919 Type::Type(TypeVariant::EntityOrCommon { type_name }) => {
920 Box::new(std::iter::once(type_name))
921 }
922 Type::CommonTypeRef { type_name } => Box::new(std::iter::once(type_name)),
923 _ => Box::new(std::iter::empty()),
924 }
925 }
926
927 pub fn is_extension(&self) -> Option<bool> {
933 match self {
934 Self::Type(TypeVariant::Extension { .. }) => Some(true),
935 Self::Type(TypeVariant::Set { element }) => element.is_extension(),
936 Self::Type(TypeVariant::Record(RecordType { attributes, .. })) => attributes
937 .values()
938 .try_fold(false, |a, e| match e.ty.is_extension() {
939 Some(true) => Some(true),
940 Some(false) => Some(a),
941 None => None,
942 }),
943 Self::Type(_) => Some(false),
944 Self::CommonTypeRef { .. } => None,
945 }
946 }
947
948 pub fn is_empty_record(&self) -> bool {
951 match self {
952 Self::Type(TypeVariant::Record(rty)) => rty.is_empty_record(),
953 _ => false,
954 }
955 }
956}
957
958impl Type<RawName> {
959 pub fn conditionally_qualify_type_references(
961 self,
962 ns: Option<&InternalName>,
963 ) -> Type<ConditionalName> {
964 match self {
965 Self::Type(tv) => Type::Type(tv.conditionally_qualify_type_references(ns)),
966 Self::CommonTypeRef { type_name } => Type::CommonTypeRef {
967 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
968 },
969 }
970 }
971
972 fn into_n<N: From<RawName>>(self) -> Type<N> {
973 match self {
974 Self::Type(tv) => Type::Type(tv.into_n()),
975 Self::CommonTypeRef { type_name } => Type::CommonTypeRef {
976 type_name: type_name.into(),
977 },
978 }
979 }
980}
981
982impl Type<ConditionalName> {
983 pub fn fully_qualify_type_references(
989 self,
990 all_defs: &AllDefs,
991 ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
992 match self {
993 Self::Type(tv) => Ok(Type::Type(tv.fully_qualify_type_references(all_defs)?)),
994 Self::CommonTypeRef { type_name } => Ok(Type::CommonTypeRef {
995 type_name: type_name.resolve(all_defs)?.clone(),
996 }),
997 }
998 }
999}
1000
1001impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1002 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1003 where
1004 D: serde::Deserializer<'de>,
1005 {
1006 deserializer.deserialize_any(TypeVisitor {
1007 _phantom: PhantomData,
1008 })
1009 }
1010}
1011
1012#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1014#[serde(field_identifier, rename_all = "camelCase")]
1015enum TypeFields {
1016 Type,
1017 Element,
1018 Attributes,
1019 AdditionalAttributes,
1020 Name,
1021}
1022
1023macro_rules! type_field_name {
1027 (Type) => {
1028 "type"
1029 };
1030 (Element) => {
1031 "element"
1032 };
1033 (Attributes) => {
1034 "attributes"
1035 };
1036 (AdditionalAttributes) => {
1037 "additionalAttributes"
1038 };
1039 (Name) => {
1040 "name"
1041 };
1042}
1043
1044impl TypeFields {
1045 fn as_str(&self) -> &'static str {
1046 match self {
1047 TypeFields::Type => type_field_name!(Type),
1048 TypeFields::Element => type_field_name!(Element),
1049 TypeFields::Attributes => type_field_name!(Attributes),
1050 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1051 TypeFields::Name => type_field_name!(Name),
1052 }
1053 }
1054}
1055
1056#[derive(Debug, Deserialize)]
1061struct AttributesTypeMap(
1062 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1063 BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1064);
1065
1066struct TypeVisitor<N> {
1067 _phantom: PhantomData<N>,
1068}
1069
1070impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1071 type Value = Type<N>;
1072
1073 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1074 formatter.write_str("builtin type or reference to type defined in commonTypes")
1075 }
1076
1077 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1078 where
1079 M: MapAccess<'de>,
1080 {
1081 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1082
1083 let mut type_name: Option<SmolStr> = None;
1084 let mut element: Option<Type<N>> = None;
1085 let mut attributes: Option<AttributesTypeMap> = None;
1086 let mut additional_attributes: Option<bool> = None;
1087 let mut name: Option<SmolStr> = None;
1088
1089 while let Some(key) = map.next_key()? {
1093 match key {
1094 TypeField => {
1095 if type_name.is_some() {
1096 return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1097 }
1098 type_name = Some(map.next_value()?);
1099 }
1100 Element => {
1101 if element.is_some() {
1102 return Err(serde::de::Error::duplicate_field(Element.as_str()));
1103 }
1104 element = Some(map.next_value()?);
1105 }
1106 Attributes => {
1107 if attributes.is_some() {
1108 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1109 }
1110 attributes = Some(map.next_value()?);
1111 }
1112 AdditionalAttributes => {
1113 if additional_attributes.is_some() {
1114 return Err(serde::de::Error::duplicate_field(
1115 AdditionalAttributes.as_str(),
1116 ));
1117 }
1118 additional_attributes = Some(map.next_value()?);
1119 }
1120 Name => {
1121 if name.is_some() {
1122 return Err(serde::de::Error::duplicate_field(Name.as_str()));
1123 }
1124 name = Some(map.next_value()?);
1125 }
1126 }
1127 }
1128
1129 Self::build_schema_type::<M>(type_name, element, attributes, additional_attributes, name)
1130 }
1131}
1132
1133impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1134 fn build_schema_type<M>(
1139 type_name: Option<SmolStr>,
1140 element: Option<Type<N>>,
1141 attributes: Option<AttributesTypeMap>,
1142 additional_attributes: Option<bool>,
1143 name: Option<SmolStr>,
1144 ) -> std::result::Result<Type<N>, M::Error>
1145 where
1146 M: MapAccess<'de>,
1147 {
1148 use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1149 let mut remaining_fields = [
1151 (TypeField, type_name.is_some()),
1152 (Element, element.is_some()),
1153 (Attributes, attributes.is_some()),
1154 (AdditionalAttributes, additional_attributes.is_some()),
1155 (Name, name.is_some()),
1156 ]
1157 .into_iter()
1158 .filter(|(_, present)| *present)
1159 .map(|(field, _)| field)
1160 .collect::<HashSet<_>>();
1161
1162 match type_name.as_ref() {
1163 Some(s) => {
1164 remaining_fields.remove(&TypeField);
1166 let error_if_fields = |fs: &[TypeFields],
1169 expected: &'static [&'static str]|
1170 -> std::result::Result<(), M::Error> {
1171 for f in fs {
1172 if remaining_fields.contains(f) {
1173 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1174 }
1175 }
1176 Ok(())
1177 };
1178 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1179 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1180 };
1181 match s.as_str() {
1182 "String" => {
1183 error_if_any_fields()?;
1184 Ok(Type::Type(TypeVariant::String))
1185 }
1186 "Long" => {
1187 error_if_any_fields()?;
1188 Ok(Type::Type(TypeVariant::Long))
1189 }
1190 "Boolean" => {
1191 error_if_any_fields()?;
1192 Ok(Type::Type(TypeVariant::Boolean))
1193 }
1194 "Set" => {
1195 error_if_fields(
1196 &[Attributes, AdditionalAttributes, Name],
1197 &[type_field_name!(Element)],
1198 )?;
1199
1200 match element {
1201 Some(element) => Ok(Type::Type(TypeVariant::Set {
1202 element: Box::new(element),
1203 })),
1204 None => Err(serde::de::Error::missing_field(Element.as_str())),
1205 }
1206 }
1207 "Record" => {
1208 error_if_fields(
1209 &[Element, Name],
1210 &[
1211 type_field_name!(Attributes),
1212 type_field_name!(AdditionalAttributes),
1213 ],
1214 )?;
1215
1216 if let Some(attributes) = attributes {
1217 let additional_attributes =
1218 additional_attributes.unwrap_or(partial_schema_default());
1219 Ok(Type::Type(TypeVariant::Record(RecordType {
1220 attributes: attributes
1221 .0
1222 .into_iter()
1223 .map(|(k, TypeOfAttribute { ty, required })| {
1224 (
1225 k,
1226 TypeOfAttribute {
1227 ty: ty.into_n(),
1228 required,
1229 },
1230 )
1231 })
1232 .collect(),
1233 additional_attributes: additional_attributes,
1234 })))
1235 } else {
1236 Err(serde::de::Error::missing_field(Attributes.as_str()))
1237 }
1238 }
1239 "Entity" => {
1240 error_if_fields(
1241 &[Element, Attributes, AdditionalAttributes],
1242 &[type_field_name!(Name)],
1243 )?;
1244 match name {
1245 Some(name) => Ok(Type::Type(TypeVariant::Entity {
1246 name: RawName::from_normalized_str(&name)
1247 .map_err(|err| {
1248 serde::de::Error::custom(format!(
1249 "invalid entity type `{name}`: {err}"
1250 ))
1251 })?
1252 .into(),
1253 })),
1254 None => Err(serde::de::Error::missing_field(Name.as_str())),
1255 }
1256 }
1257 "EntityOrCommon" => {
1258 error_if_fields(
1259 &[Element, Attributes, AdditionalAttributes],
1260 &[type_field_name!(Name)],
1261 )?;
1262 match name {
1263 Some(name) => Ok(Type::Type(TypeVariant::EntityOrCommon {
1264 type_name: RawName::from_normalized_str(&name)
1265 .map_err(|err| {
1266 serde::de::Error::custom(format!(
1267 "invalid entity or common type `{name}`: {err}"
1268 ))
1269 })?
1270 .into(),
1271 })),
1272 None => Err(serde::de::Error::missing_field(Name.as_str())),
1273 }
1274 }
1275 "Extension" => {
1276 error_if_fields(
1277 &[Element, Attributes, AdditionalAttributes],
1278 &[type_field_name!(Name)],
1279 )?;
1280
1281 match name {
1282 Some(name) => Ok(Type::Type(TypeVariant::Extension {
1283 name: UnreservedId::from_normalized_str(&name).map_err(|err| {
1284 serde::de::Error::custom(format!(
1285 "invalid extension type `{name}`: {err}"
1286 ))
1287 })?,
1288 })),
1289 None => Err(serde::de::Error::missing_field(Name.as_str())),
1290 }
1291 }
1292 type_name => {
1293 error_if_any_fields()?;
1294 Ok(Type::CommonTypeRef {
1295 type_name: N::from(RawName::from_normalized_str(type_name).map_err(
1296 |err| {
1297 serde::de::Error::custom(format!(
1298 "invalid common type `{type_name}`: {err}"
1299 ))
1300 },
1301 )?),
1302 })
1303 }
1304 }
1305 }
1306 None => Err(serde::de::Error::missing_field(TypeField.as_str())),
1307 }
1308 }
1309}
1310
1311impl<N> From<TypeVariant<N>> for Type<N> {
1312 fn from(variant: TypeVariant<N>) -> Self {
1313 Self::Type(variant)
1314 }
1315}
1316
1317#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1323#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1324#[serde(rename_all = "camelCase")]
1325#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1326#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1327pub struct RecordType<N> {
1328 pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
1330 #[serde(default = "partial_schema_default")]
1332 #[serde(skip_serializing_if = "is_partial_schema_default")]
1333 pub additional_attributes: bool,
1334}
1335
1336impl<N> Default for RecordType<N> {
1337 fn default() -> Self {
1338 Self {
1339 attributes: BTreeMap::new(),
1340 additional_attributes: partial_schema_default(),
1341 }
1342 }
1343}
1344
1345impl<N> RecordType<N> {
1346 pub fn is_empty_record(&self) -> bool {
1348 self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
1349 }
1350}
1351
1352impl RecordType<RawName> {
1353 pub fn conditionally_qualify_type_references(
1355 self,
1356 ns: Option<&InternalName>,
1357 ) -> RecordType<ConditionalName> {
1358 RecordType {
1359 attributes: self
1360 .attributes
1361 .into_iter()
1362 .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
1363 .collect(),
1364 additional_attributes: self.additional_attributes,
1365 }
1366 }
1367}
1368
1369impl RecordType<ConditionalName> {
1370 pub fn fully_qualify_type_references(
1377 self,
1378 all_defs: &AllDefs,
1379 ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1380 Ok(RecordType {
1381 attributes: self
1382 .attributes
1383 .into_iter()
1384 .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
1385 .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1386 additional_attributes: self.additional_attributes,
1387 })
1388 }
1389}
1390
1391#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1399#[serde(tag = "type")]
1400#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1401#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1402#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1403pub enum TypeVariant<N> {
1404 String,
1406 Long,
1408 Boolean,
1410 Set {
1412 element: Box<Type<N>>,
1414 },
1415 Record(RecordType<N>),
1417 Entity {
1419 name: N,
1424 },
1425 EntityOrCommon {
1427 #[serde(rename = "name")]
1448 type_name: N,
1449 },
1450 Extension {
1452 name: UnreservedId,
1454 },
1455}
1456
1457impl TypeVariant<RawName> {
1458 pub fn conditionally_qualify_type_references(
1460 self,
1461 ns: Option<&InternalName>,
1462 ) -> TypeVariant<ConditionalName> {
1463 match self {
1464 Self::Boolean => TypeVariant::Boolean,
1465 Self::Long => TypeVariant::Long,
1466 Self::String => TypeVariant::String,
1467 Self::Extension { name } => TypeVariant::Extension { name },
1468 Self::Entity { name } => TypeVariant::Entity {
1469 name: name.conditionally_qualify_with(ns, ReferenceType::Entity), },
1471 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1472 type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
1473 },
1474 Self::Set { element } => TypeVariant::Set {
1475 element: Box::new(element.conditionally_qualify_type_references(ns)),
1476 },
1477 Self::Record(RecordType {
1478 attributes,
1479 additional_attributes,
1480 }) => TypeVariant::Record(RecordType {
1481 attributes: BTreeMap::from_iter(attributes.into_iter().map(
1482 |(attr, TypeOfAttribute { ty, required })| {
1483 (
1484 attr,
1485 TypeOfAttribute {
1486 ty: ty.conditionally_qualify_type_references(ns),
1487 required,
1488 },
1489 )
1490 },
1491 )),
1492 additional_attributes,
1493 }),
1494 }
1495 }
1496
1497 fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
1498 match self {
1499 Self::Boolean => TypeVariant::Boolean,
1500 Self::Long => TypeVariant::Long,
1501 Self::String => TypeVariant::String,
1502 Self::Entity { name } => TypeVariant::Entity { name: name.into() },
1503 Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1504 type_name: type_name.into(),
1505 },
1506 Self::Record(RecordType {
1507 attributes,
1508 additional_attributes,
1509 }) => TypeVariant::Record(RecordType {
1510 attributes: attributes
1511 .into_iter()
1512 .map(|(k, v)| (k, v.into_n()))
1513 .collect(),
1514 additional_attributes,
1515 }),
1516 Self::Set { element } => TypeVariant::Set {
1517 element: Box::new(element.into_n()),
1518 },
1519 Self::Extension { name } => TypeVariant::Extension { name },
1520 }
1521 }
1522}
1523
1524impl TypeVariant<ConditionalName> {
1525 pub fn fully_qualify_type_references(
1532 self,
1533 all_defs: &AllDefs,
1534 ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
1535 match self {
1536 Self::Boolean => Ok(TypeVariant::Boolean),
1537 Self::Long => Ok(TypeVariant::Long),
1538 Self::String => Ok(TypeVariant::String),
1539 Self::Extension { name } => Ok(TypeVariant::Extension { name }),
1540 Self::Entity { name } => Ok(TypeVariant::Entity {
1541 name: name.resolve(all_defs)?.clone(),
1542 }),
1543 Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
1544 type_name: type_name.resolve(all_defs)?.clone(),
1545 }),
1546 Self::Set { element } => Ok(TypeVariant::Set {
1547 element: Box::new(element.fully_qualify_type_references(all_defs)?),
1548 }),
1549 Self::Record(RecordType {
1550 attributes,
1551 additional_attributes,
1552 }) => Ok(TypeVariant::Record(RecordType {
1553 attributes: attributes
1554 .into_iter()
1555 .map(|(attr, TypeOfAttribute { ty, required })| {
1556 Ok((
1557 attr,
1558 TypeOfAttribute {
1559 ty: ty.fully_qualify_type_references(all_defs)?,
1560 required,
1561 },
1562 ))
1563 })
1564 .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
1565 additional_attributes,
1566 })),
1567 }
1568 }
1569}
1570
1571fn is_partial_schema_default(b: &bool) -> bool {
1573 *b == partial_schema_default()
1574}
1575
1576#[cfg(feature = "arbitrary")]
1577#[allow(clippy::panic)]
1579impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
1580 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
1581 use std::collections::BTreeSet;
1582
1583 Ok(Type::Type(match u.int_in_range::<u8>(1..=8)? {
1584 1 => TypeVariant::String,
1585 2 => TypeVariant::Long,
1586 3 => TypeVariant::Boolean,
1587 4 => TypeVariant::Set {
1588 element: Box::new(u.arbitrary()?),
1589 },
1590 5 => {
1591 let attributes = {
1592 let attr_names: BTreeSet<String> = u.arbitrary()?;
1593 attr_names
1594 .into_iter()
1595 .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
1596 .collect::<arbitrary::Result<_>>()?
1597 };
1598 TypeVariant::Record(RecordType {
1599 attributes,
1600 additional_attributes: u.arbitrary()?,
1601 })
1602 }
1603 6 => TypeVariant::Entity {
1604 name: u.arbitrary()?,
1605 },
1606 7 => TypeVariant::Extension {
1607 #[allow(clippy::unwrap_used)]
1609 name: "ipaddr".parse().unwrap(),
1610 },
1611 8 => TypeVariant::Extension {
1612 #[allow(clippy::unwrap_used)]
1614 name: "decimal".parse().unwrap(),
1615 },
1616 n => panic!("bad index: {n}"),
1617 }))
1618 }
1619 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
1620 (1, None) }
1622}
1623
1624#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
1643#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1644pub struct TypeOfAttribute<N> {
1645 #[serde(flatten)]
1647 pub ty: Type<N>,
1648 #[serde(default = "record_attribute_required_default")]
1650 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
1651 pub required: bool,
1652}
1653
1654impl TypeOfAttribute<RawName> {
1655 fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
1656 TypeOfAttribute {
1657 ty: self.ty.into_n(),
1658 required: self.required,
1659 }
1660 }
1661
1662 pub fn conditionally_qualify_type_references(
1664 self,
1665 ns: Option<&InternalName>,
1666 ) -> TypeOfAttribute<ConditionalName> {
1667 TypeOfAttribute {
1668 ty: self.ty.conditionally_qualify_type_references(ns),
1669 required: self.required,
1670 }
1671 }
1672}
1673
1674impl TypeOfAttribute<ConditionalName> {
1675 pub fn fully_qualify_type_references(
1682 self,
1683 all_defs: &AllDefs,
1684 ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
1685 Ok(TypeOfAttribute {
1686 ty: self.ty.fully_qualify_type_references(all_defs)?,
1687 required: self.required,
1688 })
1689 }
1690}
1691
1692#[cfg(feature = "arbitrary")]
1693impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
1694 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
1695 Ok(Self {
1696 ty: u.arbitrary()?,
1697 required: u.arbitrary()?,
1698 })
1699 }
1700
1701 fn size_hint(depth: usize) -> (usize, Option<usize>) {
1702 arbitrary::size_hint::and(
1703 <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
1704 <bool as arbitrary::Arbitrary>::size_hint(depth),
1705 )
1706 }
1707}
1708
1709fn is_record_attribute_required_default(b: &bool) -> bool {
1711 *b == record_attribute_required_default()
1712}
1713
1714fn partial_schema_default() -> bool {
1717 false
1718}
1719
1720fn record_attribute_required_default() -> bool {
1722 true
1723}
1724
1725#[cfg(test)]
1726mod test {
1727 use cedar_policy_core::{
1728 extensions::Extensions,
1729 test_utils::{expect_err, ExpectedErrorMessageBuilder},
1730 };
1731 use cool_asserts::assert_matches;
1732
1733 use crate::ValidatorSchema;
1734
1735 use super::*;
1736
1737 #[test]
1738 fn test_entity_type_parser1() {
1739 let user = r#"
1740 {
1741 "memberOfTypes" : ["UserGroup"]
1742 }
1743 "#;
1744 let et = serde_json::from_str::<EntityType<RawName>>(user).expect("Parse Error");
1745 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
1746 assert_eq!(
1747 et.shape,
1748 AttributesOrContext(Type::Type(TypeVariant::Record(RecordType {
1749 attributes: BTreeMap::new(),
1750 additional_attributes: false
1751 }))),
1752 );
1753 }
1754
1755 #[test]
1756 fn test_entity_type_parser2() {
1757 let src = r#"
1758 { }
1759 "#;
1760 let et = serde_json::from_str::<EntityType<RawName>>(src).expect("Parse Error");
1761 assert_eq!(et.member_of_types.len(), 0);
1762 assert_eq!(
1763 et.shape,
1764 AttributesOrContext(Type::Type(TypeVariant::Record(RecordType {
1765 attributes: BTreeMap::new(),
1766 additional_attributes: false
1767 }))),
1768 );
1769 }
1770
1771 #[test]
1772 fn test_action_type_parser1() {
1773 let src = r#"
1774 {
1775 "appliesTo" : {
1776 "resourceTypes": ["Album"],
1777 "principalTypes": ["User"]
1778 },
1779 "memberOf": [{"id": "readWrite"}]
1780 }
1781 "#;
1782 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
1783 let spec = ApplySpec {
1784 resource_types: vec!["Album".parse().unwrap()],
1785 principal_types: vec!["User".parse().unwrap()],
1786 context: AttributesOrContext::default(),
1787 };
1788 assert_eq!(at.applies_to, Some(spec));
1789 assert_eq!(
1790 at.member_of,
1791 Some(vec![ActionEntityUID {
1792 ty: None,
1793 id: "readWrite".into()
1794 }])
1795 );
1796 }
1797
1798 #[test]
1799 fn test_action_type_parser2() {
1800 let src = r#"
1801 { }
1802 "#;
1803 let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
1804 assert_eq!(at.applies_to, None);
1805 assert!(at.member_of.is_none());
1806 }
1807
1808 #[test]
1809 fn test_schema_file_parser() {
1810 let src = serde_json::json!(
1811 {
1812 "entityTypes": {
1813
1814 "User": {
1815 "memberOfTypes": ["UserGroup"]
1816 },
1817 "Photo": {
1818 "memberOfTypes": ["Album", "Account"]
1819 },
1820
1821 "Album": {
1822 "memberOfTypes": ["Album", "Account"]
1823 },
1824 "Account": { },
1825 "UserGroup": { }
1826 },
1827
1828 "actions": {
1829 "readOnly": { },
1830 "readWrite": { },
1831 "createAlbum": {
1832 "appliesTo" : {
1833 "resourceTypes": ["Account", "Album"],
1834 "principalTypes": ["User"]
1835 },
1836 "memberOf": [{"id": "readWrite"}]
1837 },
1838 "addPhotoToAlbum": {
1839 "appliesTo" : {
1840 "resourceTypes": ["Album"],
1841 "principalTypes": ["User"]
1842 },
1843 "memberOf": [{"id": "readWrite"}]
1844 },
1845 "viewPhoto": {
1846 "appliesTo" : {
1847 "resourceTypes": ["Photo"],
1848 "principalTypes": ["User"]
1849 },
1850 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
1851 },
1852 "viewComments": {
1853 "appliesTo" : {
1854 "resourceTypes": ["Photo"],
1855 "principalTypes": ["User"]
1856 },
1857 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
1858 }
1859 }
1860 });
1861 let schema_file: NamespaceDefinition<RawName> =
1862 serde_json::from_value(src).expect("Parse Error");
1863
1864 assert_eq!(schema_file.entity_types.len(), 5);
1865 assert_eq!(schema_file.actions.len(), 6);
1866 }
1867
1868 #[test]
1869 fn test_parse_namespaces() {
1870 let src = r#"
1871 {
1872 "foo::foo::bar::baz": {
1873 "entityTypes": {},
1874 "actions": {}
1875 }
1876 }"#;
1877 let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
1878 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
1879 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
1880 }
1881
1882 #[test]
1883 #[should_panic(expected = "unknown field `requiredddddd`")]
1884 fn test_schema_file_with_misspelled_required() {
1885 let src = serde_json::json!(
1886 {
1887 "entityTypes": {
1888 "User": {
1889 "shape": {
1890 "type": "Record",
1891 "attributes": {
1892 "favorite": {
1893 "type": "Entity",
1894 "name": "Photo",
1895 "requiredddddd": false
1896 }
1897 }
1898 }
1899 }
1900 },
1901 "actions": {}
1902 });
1903 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
1904 println!("{:#?}", schema);
1905 }
1906
1907 #[test]
1908 #[should_panic(expected = "unknown field `nameeeeee`")]
1909 fn test_schema_file_with_misspelled_field() {
1910 let src = serde_json::json!(
1911 {
1912 "entityTypes": {
1913 "User": {
1914 "shape": {
1915 "type": "Record",
1916 "attributes": {
1917 "favorite": {
1918 "type": "Entity",
1919 "nameeeeee": "Photo",
1920 }
1921 }
1922 }
1923 }
1924 },
1925 "actions": {}
1926 });
1927 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
1928 println!("{:#?}", schema);
1929 }
1930
1931 #[test]
1932 #[should_panic(expected = "unknown field `extra`")]
1933 fn test_schema_file_with_extra_field() {
1934 let src = serde_json::json!(
1935 {
1936 "entityTypes": {
1937 "User": {
1938 "shape": {
1939 "type": "Record",
1940 "attributes": {
1941 "favorite": {
1942 "type": "Entity",
1943 "name": "Photo",
1944 "extra": "Should not exist"
1945 }
1946 }
1947 }
1948 }
1949 },
1950 "actions": {}
1951 });
1952 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
1953 println!("{:#?}", schema);
1954 }
1955
1956 #[test]
1957 #[should_panic(expected = "unknown field `memberOfTypes`")]
1958 fn test_schema_file_with_misplaced_field() {
1959 let src = serde_json::json!(
1960 {
1961 "entityTypes": {
1962 "User": {
1963 "shape": {
1964 "memberOfTypes": [],
1965 "type": "Record",
1966 "attributes": {
1967 "favorite": {
1968 "type": "Entity",
1969 "name": "Photo",
1970 }
1971 }
1972 }
1973 }
1974 },
1975 "actions": {}
1976 });
1977 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
1978 println!("{:#?}", schema);
1979 }
1980
1981 #[test]
1982 fn schema_file_with_missing_field() {
1983 let src = serde_json::json!(
1984 {
1985 "": {
1986 "entityTypes": {
1987 "User": {
1988 "shape": {
1989 "type": "Record",
1990 "attributes": {
1991 "favorite": {
1992 "type": "Entity",
1993 }
1994 }
1995 }
1996 }
1997 },
1998 "actions": {}
1999 }
2000 });
2001 let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2002 assert_matches!(schema, Err(e) => {
2003 expect_err(
2004 &src,
2005 &miette::Report::new(e),
2006 &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2007 .build());
2008 });
2009 }
2010
2011 #[test]
2012 #[should_panic(expected = "missing field `type`")]
2013 fn schema_file_with_missing_type() {
2014 let src = serde_json::json!(
2015 {
2016 "entityTypes": {
2017 "User": {
2018 "shape": { }
2019 }
2020 },
2021 "actions": {}
2022 });
2023 let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2024 println!("{:#?}", schema);
2025 }
2026
2027 #[test]
2028 fn schema_file_unexpected_malformed_attribute() {
2029 let src = serde_json::json!(
2030 { "": {
2031 "entityTypes": {
2032 "User": {
2033 "shape": {
2034 "type": "Record",
2035 "attributes": {
2036 "a": {
2037 "type": "Long",
2038 "attributes": {
2039 "b": {"foo": "bar"}
2040 }
2041 }
2042 }
2043 }
2044 }
2045 },
2046 "actions": {}
2047 }});
2048 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2049 assert_matches!(schema, Err(e) => {
2050 expect_err(
2051 "",
2052 &miette::Report::new(e),
2053 &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2054 );
2055 });
2056 }
2057
2058 #[test]
2059 fn error_in_nested_attribute_fails_fast_top_level_attr() {
2060 let src = serde_json::json!(
2061 {
2062 "": {
2063 "entityTypes": {
2064 "User": {
2065 "shape": {
2066 "type": "Record",
2067 "attributes": {
2068 "foo": {
2069 "type": "Record",
2070 "element": { "type": "Long" }
2072 },
2073 "bar": { "type": "Long" }
2074 }
2075 }
2076 }
2077 },
2078 "actions": {}
2079 }
2080 }
2081 );
2082
2083 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2084 assert_matches!(schema, Err(e) => {
2085 expect_err(
2086 "",
2087 &miette::Report::new(e),
2088 &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2089 );
2090 });
2091 }
2092
2093 #[test]
2094 fn error_in_nested_attribute_fails_fast_nested_attr() {
2095 let src = serde_json::json!(
2096 { "": {
2097 "entityTypes": {
2098 "a": {
2099 "shape": {
2100 "type": "Record",
2101 "attributes": {
2102 "foo": { "type": "Entity", "name": "b" },
2103 "baz": { "type": "Record",
2104 "attributes": {
2105 "z": "Boolean"
2107 }
2108 }
2109 }
2110 }
2111 },
2112 "b": {}
2113 }
2114 } }
2115 );
2116
2117 let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2118 assert_matches!(schema, Err(e) => {
2119 expect_err(
2120 "",
2121 &miette::Report::new(e),
2122 &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2123 );
2124 });
2125 }
2126
2127 #[test]
2128 fn missing_namespace() {
2129 let src = r#"
2130 {
2131 "entityTypes": { "User": { } },
2132 "actions": {}
2133 }"#;
2134 let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2135 assert_matches!(schema, Err(e) => {
2136 expect_err(
2137 src,
2138 &miette::Report::new(e),
2139 &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions` at line 3 column 35"#)
2140 .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2141 .build());
2142 });
2143 }
2144}
2145
2146#[cfg(test)]
2148mod strengthened_types {
2149 use cool_asserts::assert_matches;
2150
2151 use super::{
2152 ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
2153 };
2154
2155 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
2158 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
2159 }
2160
2161 #[test]
2162 fn invalid_namespace() {
2163 let src = serde_json::json!(
2164 {
2165 "\n" : {
2166 "entityTypes": {},
2167 "actions": {}
2168 }
2169 });
2170 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2171 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
2172
2173 let src = serde_json::json!(
2174 {
2175 "1" : {
2176 "entityTypes": {},
2177 "actions": {}
2178 }
2179 });
2180 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2181 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
2182
2183 let src = serde_json::json!(
2184 {
2185 "*1" : {
2186 "entityTypes": {},
2187 "actions": {}
2188 }
2189 });
2190 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2191 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
2192
2193 let src = serde_json::json!(
2194 {
2195 "::" : {
2196 "entityTypes": {},
2197 "actions": {}
2198 }
2199 });
2200 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2201 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
2202
2203 let src = serde_json::json!(
2204 {
2205 "A::" : {
2206 "entityTypes": {},
2207 "actions": {}
2208 }
2209 });
2210 let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2211 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
2212 }
2213
2214 #[test]
2215 fn invalid_common_type() {
2216 let src = serde_json::json!(
2217 {
2218 "entityTypes": {},
2219 "actions": {},
2220 "commonTypes": {
2221 "" : {
2222 "type": "String"
2223 }
2224 }
2225 });
2226 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2227 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2228
2229 let src = serde_json::json!(
2230 {
2231 "entityTypes": {},
2232 "actions": {},
2233 "commonTypes": {
2234 "~" : {
2235 "type": "String"
2236 }
2237 }
2238 });
2239 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2240 assert_error_matches(schema, "invalid id `~`: invalid token");
2241
2242 let src = serde_json::json!(
2243 {
2244 "entityTypes": {},
2245 "actions": {},
2246 "commonTypes": {
2247 "A::B" : {
2248 "type": "String"
2249 }
2250 }
2251 });
2252 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2253 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2254 }
2255
2256 #[test]
2257 fn invalid_entity_type() {
2258 let src = serde_json::json!(
2259 {
2260 "entityTypes": {
2261 "": {}
2262 },
2263 "actions": {}
2264 });
2265 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2266 assert_error_matches(schema, "invalid id ``: unexpected end of input");
2267
2268 let src = serde_json::json!(
2269 {
2270 "entityTypes": {
2271 "*": {}
2272 },
2273 "actions": {}
2274 });
2275 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2276 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
2277
2278 let src = serde_json::json!(
2279 {
2280 "entityTypes": {
2281 "A::B": {}
2282 },
2283 "actions": {}
2284 });
2285 let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2286 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2287 }
2288
2289 #[test]
2290 fn invalid_member_of_types() {
2291 let src = serde_json::json!(
2292 {
2293 "memberOfTypes": [""]
2294 });
2295 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2296 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2297
2298 let src = serde_json::json!(
2299 {
2300 "memberOfTypes": ["*"]
2301 });
2302 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2303 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2304
2305 let src = serde_json::json!(
2306 {
2307 "memberOfTypes": ["A::"]
2308 });
2309 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2310 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2311
2312 let src = serde_json::json!(
2313 {
2314 "memberOfTypes": ["::A"]
2315 });
2316 let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2317 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2318 }
2319
2320 #[test]
2321 fn invalid_apply_spec() {
2322 let src = serde_json::json!(
2323 {
2324 "resourceTypes": [""]
2325 });
2326 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2327 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2328
2329 let src = serde_json::json!(
2330 {
2331 "resourceTypes": ["*"]
2332 });
2333 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2334 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2335
2336 let src = serde_json::json!(
2337 {
2338 "resourceTypes": ["A::"]
2339 });
2340 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2341 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2342
2343 let src = serde_json::json!(
2344 {
2345 "resourceTypes": ["::A"]
2346 });
2347 let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2348 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2349 }
2350
2351 #[test]
2352 fn invalid_schema_entity_types() {
2353 let src = serde_json::json!(
2354 {
2355 "type": "Entity",
2356 "name": ""
2357 });
2358 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2359 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
2360
2361 let src = serde_json::json!(
2362 {
2363 "type": "Entity",
2364 "name": "*"
2365 });
2366 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2367 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
2368
2369 let src = serde_json::json!(
2370 {
2371 "type": "Entity",
2372 "name": "::A"
2373 });
2374 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2375 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
2376
2377 let src = serde_json::json!(
2378 {
2379 "type": "Entity",
2380 "name": "A::"
2381 });
2382 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2383 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
2384 }
2385
2386 #[test]
2387 fn invalid_action_euid() {
2388 let src = serde_json::json!(
2389 {
2390 "id": "action",
2391 "type": ""
2392 });
2393 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2394 assert_error_matches(schema, "invalid name ``: unexpected end of input");
2395
2396 let src = serde_json::json!(
2397 {
2398 "id": "action",
2399 "type": "*"
2400 });
2401 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2402 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2403
2404 let src = serde_json::json!(
2405 {
2406 "id": "action",
2407 "type": "Action::"
2408 });
2409 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2410 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
2411
2412 let src = serde_json::json!(
2413 {
2414 "id": "action",
2415 "type": "::Action"
2416 });
2417 let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2418 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
2419 }
2420
2421 #[test]
2422 fn invalid_schema_common_types() {
2423 let src = serde_json::json!(
2424 {
2425 "type": ""
2426 });
2427 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2428 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
2429
2430 let src = serde_json::json!(
2431 {
2432 "type": "*"
2433 });
2434 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2435 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
2436
2437 let src = serde_json::json!(
2438 {
2439 "type": "::A"
2440 });
2441 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2442 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
2443
2444 let src = serde_json::json!(
2445 {
2446 "type": "A::"
2447 });
2448 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2449 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
2450 }
2451
2452 #[test]
2453 fn invalid_schema_extension_types() {
2454 let src = serde_json::json!(
2455 {
2456 "type": "Extension",
2457 "name": ""
2458 });
2459 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2460 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
2461
2462 let src = serde_json::json!(
2463 {
2464 "type": "Extension",
2465 "name": "*"
2466 });
2467 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2468 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
2469
2470 let src = serde_json::json!(
2471 {
2472 "type": "Extension",
2473 "name": "__cedar::decimal"
2474 });
2475 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2476 assert_error_matches(
2477 schema,
2478 "invalid extension type `__cedar::decimal`: unexpected token `::`",
2479 );
2480
2481 let src = serde_json::json!(
2482 {
2483 "type": "Extension",
2484 "name": "__cedar::"
2485 });
2486 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2487 assert_error_matches(
2488 schema,
2489 "invalid extension type `__cedar::`: unexpected token `::`",
2490 );
2491
2492 let src = serde_json::json!(
2493 {
2494 "type": "Extension",
2495 "name": "::__cedar"
2496 });
2497 let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2498 assert_error_matches(
2499 schema,
2500 "invalid extension type `::__cedar`: unexpected token `::`",
2501 );
2502 }
2503}
2504
2505#[cfg(test)]
2507mod entity_tags {
2508 use super::*;
2509 use cedar_policy_core::test_utils::{expect_err, ExpectedErrorMessageBuilder};
2510 use cool_asserts::assert_matches;
2511 use serde_json::json;
2512
2513 #[track_caller]
2515 fn example_json_schema() -> serde_json::Value {
2516 json!({"": {
2517 "entityTypes": {
2518 "User" : {
2519 "shape" : {
2520 "type" : "Record",
2521 "attributes" : {
2522 "jobLevel" : {
2523 "type" : "Long"
2524 },
2525 }
2526 },
2527 "tags" : {
2528 "type" : "Set",
2529 "element": { "type": "String" }
2530 }
2531 },
2532 "Document" : {
2533 "shape" : {
2534 "type" : "Record",
2535 "attributes" : {
2536 "owner" : {
2537 "type" : "Entity",
2538 "name" : "User"
2539 },
2540 }
2541 },
2542 "tags" : {
2543 "type" : "Set",
2544 "element": { "type": "String" }
2545 }
2546 }
2547 },
2548 "actions": {}
2549 }})
2550 }
2551
2552 #[test]
2553 fn roundtrip() {
2554 let json = example_json_schema();
2555 let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
2556 let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
2557 assert_eq!(json, serialized_json_schema);
2558 }
2559
2560 #[test]
2561 fn basic() {
2562 let json = example_json_schema();
2563 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2564 let user = frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2565 assert_matches!(&user.tags, Some(Type::Type(TypeVariant::Set { element })) => {
2566 assert_matches!(&**element, Type::Type(TypeVariant::String)); });
2568 let doc = frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap();
2569 assert_matches!(&doc.tags, Some(Type::Type(TypeVariant::Set { element })) => {
2570 assert_matches!(&**element, Type::Type(TypeVariant::String)); });
2572 })
2573 }
2574
2575 #[test]
2577 fn tag_type_is_common_type() {
2578 let json = json!({"": {
2579 "commonTypes": {
2580 "T": { "type": "String" },
2581 },
2582 "entityTypes": {
2583 "User" : {
2584 "shape" : {
2585 "type" : "Record",
2586 "attributes" : {
2587 "jobLevel" : {
2588 "type" : "Long"
2589 },
2590 }
2591 },
2592 "tags" : { "type" : "T" },
2593 },
2594 },
2595 "actions": {}
2596 }});
2597 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2598 let user = frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2599 assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name }) => {
2600 assert_eq!(&format!("{type_name}"), "T");
2601 });
2602 })
2603 }
2604
2605 #[test]
2607 fn tag_type_is_entity_type() {
2608 let json = json!({"": {
2609 "entityTypes": {
2610 "User" : {
2611 "shape" : {
2612 "type" : "Record",
2613 "attributes" : {
2614 "jobLevel" : {
2615 "type" : "Long"
2616 },
2617 }
2618 },
2619 "tags" : { "type" : "Entity", "name": "User" },
2620 },
2621 },
2622 "actions": {}
2623 }});
2624 assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
2625 let user = frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap();
2626 assert_matches!(&user.tags, Some(Type::Type(TypeVariant::Entity{ name })) => {
2627 assert_eq!(&format!("{name}"), "User");
2628 });
2629 })
2630 }
2631
2632 #[test]
2634 fn bad_tags() {
2635 let json = json!({"": {
2636 "entityTypes": {
2637 "User": {
2638 "shape": {
2639 "type": "Record",
2640 "attributes": {
2641 "jobLevel": {
2642 "type": "Long"
2643 },
2644 },
2645 "tags": { "type": "String" },
2646 }
2647 },
2648 },
2649 "actions": {}
2650 }});
2651 assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
2652 expect_err(
2653 &json,
2654 &miette::Report::new(e),
2655 &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
2656 .build(),
2657 );
2658 });
2659 }
2660}
2661
2662#[cfg(test)]
2664mod test_json_roundtrip {
2665 use super::*;
2666
2667 #[track_caller] fn roundtrip(schema: Fragment<RawName>) {
2669 let json = serde_json::to_value(schema.clone()).unwrap();
2670 let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
2671 assert_eq!(schema, new_schema);
2672 }
2673
2674 #[test]
2675 fn empty_namespace() {
2676 let fragment = Fragment(BTreeMap::from([(
2677 None,
2678 NamespaceDefinition {
2679 common_types: BTreeMap::new(),
2680 entity_types: BTreeMap::new(),
2681 actions: BTreeMap::new(),
2682 },
2683 )]));
2684 roundtrip(fragment);
2685 }
2686
2687 #[test]
2688 fn nonempty_namespace() {
2689 let fragment = Fragment(BTreeMap::from([(
2690 Some("a".parse().unwrap()),
2691 NamespaceDefinition {
2692 common_types: BTreeMap::new(),
2693 entity_types: BTreeMap::new(),
2694 actions: BTreeMap::new(),
2695 },
2696 )]));
2697 roundtrip(fragment);
2698 }
2699
2700 #[test]
2701 fn nonempty_entity_types() {
2702 let fragment = Fragment(BTreeMap::from([(
2703 None,
2704 NamespaceDefinition {
2705 common_types: BTreeMap::new(),
2706 entity_types: BTreeMap::from([(
2707 "a".parse().unwrap(),
2708 EntityType {
2709 member_of_types: vec!["a".parse().unwrap()],
2710 shape: AttributesOrContext(Type::Type(TypeVariant::Record(RecordType {
2711 attributes: BTreeMap::new(),
2712 additional_attributes: false,
2713 }))),
2714 tags: None,
2715 },
2716 )]),
2717 actions: BTreeMap::from([(
2718 "action".into(),
2719 ActionType {
2720 attributes: None,
2721 applies_to: Some(ApplySpec {
2722 resource_types: vec!["a".parse().unwrap()],
2723 principal_types: vec!["a".parse().unwrap()],
2724 context: AttributesOrContext(Type::Type(TypeVariant::Record(
2725 RecordType {
2726 attributes: BTreeMap::new(),
2727 additional_attributes: false,
2728 },
2729 ))),
2730 }),
2731 member_of: None,
2732 },
2733 )]),
2734 },
2735 )]));
2736 roundtrip(fragment);
2737 }
2738
2739 #[test]
2740 fn multiple_namespaces() {
2741 let fragment = Fragment(BTreeMap::from([
2742 (
2743 Some("foo".parse().unwrap()),
2744 NamespaceDefinition {
2745 common_types: BTreeMap::new(),
2746 entity_types: BTreeMap::from([(
2747 "a".parse().unwrap(),
2748 EntityType {
2749 member_of_types: vec!["a".parse().unwrap()],
2750 shape: AttributesOrContext(Type::Type(TypeVariant::Record(
2751 RecordType {
2752 attributes: BTreeMap::new(),
2753 additional_attributes: false,
2754 },
2755 ))),
2756 tags: None,
2757 },
2758 )]),
2759 actions: BTreeMap::new(),
2760 },
2761 ),
2762 (
2763 None,
2764 NamespaceDefinition {
2765 common_types: BTreeMap::new(),
2766 entity_types: BTreeMap::new(),
2767 actions: BTreeMap::from([(
2768 "action".into(),
2769 ActionType {
2770 attributes: None,
2771 applies_to: Some(ApplySpec {
2772 resource_types: vec!["foo::a".parse().unwrap()],
2773 principal_types: vec!["foo::a".parse().unwrap()],
2774 context: AttributesOrContext(Type::Type(TypeVariant::Record(
2775 RecordType {
2776 attributes: BTreeMap::new(),
2777 additional_attributes: false,
2778 },
2779 ))),
2780 }),
2781 member_of: None,
2782 },
2783 )]),
2784 },
2785 ),
2786 ]));
2787 roundtrip(fragment);
2788 }
2789}
2790
2791#[cfg(test)]
2796mod test_duplicates_error {
2797 use super::*;
2798
2799 #[test]
2800 #[should_panic(expected = "invalid entry: found duplicate key")]
2801 fn namespace() {
2802 let src = r#"{
2803 "Foo": {
2804 "entityTypes" : {},
2805 "actions": {}
2806 },
2807 "Foo": {
2808 "entityTypes" : {},
2809 "actions": {}
2810 }
2811 }"#;
2812 Fragment::from_json_str(src).unwrap();
2813 }
2814
2815 #[test]
2816 #[should_panic(expected = "invalid entry: found duplicate key")]
2817 fn entity_type() {
2818 let src = r#"{
2819 "Foo": {
2820 "entityTypes" : {
2821 "Bar": {},
2822 "Bar": {},
2823 },
2824 "actions": {}
2825 }
2826 }"#;
2827 Fragment::from_json_str(src).unwrap();
2828 }
2829
2830 #[test]
2831 #[should_panic(expected = "invalid entry: found duplicate key")]
2832 fn action() {
2833 let src = r#"{
2834 "Foo": {
2835 "entityTypes" : {},
2836 "actions": {
2837 "Bar": {},
2838 "Bar": {}
2839 }
2840 }
2841 }"#;
2842 Fragment::from_json_str(src).unwrap();
2843 }
2844
2845 #[test]
2846 #[should_panic(expected = "invalid entry: found duplicate key")]
2847 fn common_types() {
2848 let src = r#"{
2849 "Foo": {
2850 "entityTypes" : {},
2851 "actions": { },
2852 "commonTypes": {
2853 "Bar": {"type": "Long"},
2854 "Bar": {"type": "String"}
2855 }
2856 }
2857 }"#;
2858 Fragment::from_json_str(src).unwrap();
2859 }
2860
2861 #[test]
2862 #[should_panic(expected = "invalid entry: found duplicate key")]
2863 fn record_type() {
2864 let src = r#"{
2865 "Foo": {
2866 "entityTypes" : {
2867 "Bar": {
2868 "shape": {
2869 "type": "Record",
2870 "attributes": {
2871 "Baz": {"type": "Long"},
2872 "Baz": {"type": "String"}
2873 }
2874 }
2875 }
2876 },
2877 "actions": { }
2878 }
2879 }"#;
2880 Fragment::from_json_str(src).unwrap();
2881 }
2882
2883 #[test]
2884 #[should_panic(expected = "missing field `resourceTypes`")]
2885 fn missing_resource() {
2886 let src = r#"{
2887 "Foo": {
2888 "entityTypes" : {},
2889 "actions": {
2890 "foo" : {
2891 "appliesTo" : {
2892 "principalTypes" : ["a"]
2893 }
2894 }
2895 }
2896 }
2897 }"#;
2898 Fragment::from_json_str(src).unwrap();
2899 }
2900
2901 #[test]
2902 #[should_panic(expected = "missing field `principalTypes`")]
2903 fn missing_principal() {
2904 let src = r#"{
2905 "Foo": {
2906 "entityTypes" : {},
2907 "actions": {
2908 "foo" : {
2909 "appliesTo" : {
2910 "resourceTypes" : ["a"]
2911 }
2912 }
2913 }
2914 }
2915 }"#;
2916 Fragment::from_json_str(src).unwrap();
2917 }
2918
2919 #[test]
2920 #[should_panic(expected = "missing field `resourceTypes`")]
2921 fn missing_both() {
2922 let src = r#"{
2923 "Foo": {
2924 "entityTypes" : {},
2925 "actions": {
2926 "foo" : {
2927 "appliesTo" : {
2928 }
2929 }
2930 }
2931 }
2932 }"#;
2933 Fragment::from_json_str(src).unwrap();
2934 }
2935}