1use cedar_policy_core::{
18 ast::{Id, Name},
19 entities::CedarValueJson,
20 FromNormalizedStr,
21};
22use serde::{
23 de::{MapAccess, Visitor},
24 ser::SerializeMap,
25 Deserialize, Deserializer, Serialize, Serializer,
26};
27use serde_with::serde_as;
28use smol_str::{SmolStr, ToSmolStr};
29use std::collections::{BTreeMap, HashMap, HashSet};
30
31use crate::{
32 human_schema::{
33 self, parser::parse_natural_schema_fragment, SchemaWarning, ToHumanSchemaStrError,
34 },
35 HumanSchemaError, Result,
36};
37
38#[cfg(feature = "wasm")]
39extern crate tsify;
40
41#[derive(Debug, Clone, PartialEq, Deserialize)]
47#[serde(transparent)]
48#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
49#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
50pub struct SchemaFragment(
51 #[serde(deserialize_with = "deserialize_schema_fragment")]
52 #[cfg_attr(feature = "wasm", tsify(type = "Record<string, NamespaceDefinition>"))]
53 pub HashMap<Option<Name>, NamespaceDefinition>,
54);
55
56fn deserialize_schema_fragment<'de, D>(
58 deserializer: D,
59) -> std::result::Result<HashMap<Option<Name>, NamespaceDefinition>, D::Error>
60where
61 D: Deserializer<'de>,
62{
63 let raw: HashMap<SmolStr, NamespaceDefinition> =
64 serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
65 Ok(HashMap::from_iter(
66 raw.into_iter()
67 .map(|(key, value)| {
68 let key = if key.is_empty() {
69 None
70 } else {
71 Some(Name::from_normalized_str(&key).map_err(|err| {
72 serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
73 })?)
74 };
75 Ok((key, value))
76 })
77 .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition)>, D::Error>>()?,
78 ))
79}
80
81impl Serialize for SchemaFragment {
82 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
84 where
85 S: Serializer,
86 {
87 let mut map = serializer.serialize_map(Some(self.0.len()))?;
88 for (k, v) in &self.0 {
89 let k: SmolStr = match k {
90 None => "".into(),
91 Some(name) => name.to_smolstr(),
92 };
93 map.serialize_entry(&k, &v)?;
94 }
95 map.end()
96 }
97}
98
99impl SchemaFragment {
100 pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
103 serde_json::from_value(json).map_err(Into::into)
104 }
105
106 pub fn from_json_str(json: &str) -> Result<Self> {
109 serde_json::from_str(json).map_err(Into::into)
110 }
111
112 pub fn from_file(file: impl std::io::Read) -> Result<Self> {
114 serde_json::from_reader(file).map_err(Into::into)
115 }
116
117 pub fn from_str_natural(
119 src: &str,
120 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
121 let tup = parse_natural_schema_fragment(src)?;
122 Ok(tup)
123 }
124
125 pub fn from_file_natural(
127 mut file: impl std::io::Read,
128 ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
129 let mut src = String::new();
130 file.read_to_string(&mut src)?;
131 Self::from_str_natural(&src)
132 }
133
134 pub fn as_natural_schema(&self) -> std::result::Result<String, ToHumanSchemaStrError> {
136 let src = human_schema::json_schema_to_custom_schema_str(self)?;
137 Ok(src)
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde_as]
144#[serde(deny_unknown_fields)]
145#[doc(hidden)]
146#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
147#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
148pub struct NamespaceDefinition {
149 #[serde(default)]
150 #[serde(skip_serializing_if = "HashMap::is_empty")]
151 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
152 #[serde(rename = "commonTypes")]
153 pub common_types: HashMap<Id, SchemaType>,
154 #[serde(rename = "entityTypes")]
155 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
156 pub entity_types: HashMap<Id, EntityType>,
157 #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
158 pub actions: HashMap<SmolStr, ActionType>,
159}
160
161impl NamespaceDefinition {
162 pub fn new(
163 entity_types: impl IntoIterator<Item = (Id, EntityType)>,
164 actions: impl IntoIterator<Item = (SmolStr, ActionType)>,
165 ) -> Self {
166 Self {
167 common_types: HashMap::new(),
168 entity_types: entity_types.into_iter().collect(),
169 actions: actions.into_iter().collect(),
170 }
171 }
172}
173
174#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
178#[serde(deny_unknown_fields)]
179#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
180#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
181pub struct EntityType {
182 #[serde(default)]
183 #[serde(skip_serializing_if = "Vec::is_empty")]
184 #[serde(rename = "memberOfTypes")]
185 pub member_of_types: Vec<Name>,
186 #[serde(default)]
187 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
188 pub shape: AttributesOrContext,
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192#[serde(transparent)]
193#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
194#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
195pub struct AttributesOrContext(
196 pub SchemaType,
199);
200
201impl AttributesOrContext {
202 pub fn into_inner(self) -> SchemaType {
203 self.0
204 }
205
206 pub fn is_empty_record(&self) -> bool {
207 self.0.is_empty_record()
208 }
209}
210
211impl Default for AttributesOrContext {
212 fn default() -> Self {
213 Self(SchemaType::Type(SchemaTypeVariant::Record {
214 attributes: BTreeMap::new(),
215 additional_attributes: partial_schema_default(),
216 }))
217 }
218}
219
220#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[serde(deny_unknown_fields)]
224#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
225#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
226pub struct ActionType {
227 #[serde(default)]
231 #[serde(skip_serializing_if = "Option::is_none")]
232 pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
233 #[serde(default)]
234 #[serde(skip_serializing_if = "Option::is_none")]
235 #[serde(rename = "appliesTo")]
236 pub applies_to: Option<ApplySpec>,
237 #[serde(default)]
238 #[serde(skip_serializing_if = "Option::is_none")]
239 #[serde(rename = "memberOf")]
240 pub member_of: Option<Vec<ActionEntityUID>>,
241}
242
243#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252#[serde(deny_unknown_fields)]
253#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
254#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
255pub struct ApplySpec {
256 #[serde(default)]
257 #[serde(skip_serializing_if = "Option::is_none")]
258 #[serde(rename = "resourceTypes")]
259 pub resource_types: Option<Vec<Name>>,
260 #[serde(default)]
261 #[serde(skip_serializing_if = "Option::is_none")]
262 #[serde(rename = "principalTypes")]
263 pub principal_types: Option<Vec<Name>>,
264 #[serde(default)]
265 #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
266 pub context: AttributesOrContext,
267}
268
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270#[serde(deny_unknown_fields)]
271#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
272#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
273pub struct ActionEntityUID {
274 pub id: SmolStr,
275
276 #[serde(rename = "type")]
277 #[serde(default)]
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub ty: Option<Name>,
280}
281
282impl ActionEntityUID {
283 pub fn default_type(id: SmolStr) -> Self {
284 Self { id, ty: None }
285 }
286}
287
288impl std::fmt::Display for ActionEntityUID {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 if let Some(ty) = &self.ty {
291 write!(f, "{}::", ty)?
292 } else {
293 write!(f, "Action::")?
294 }
295 write!(f, "\"{}\"", self.id.escape_debug())
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
302#[serde(untagged)]
307#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
308#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
309pub enum SchemaType {
310 Type(SchemaTypeVariant),
311 TypeDef {
312 #[serde(rename = "type")]
313 type_name: Name,
314 },
315}
316
317impl SchemaType {
318 pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = Name>> {
320 match self {
321 SchemaType::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
322 .iter()
323 .map(|(_, ty)| ty.ty.common_type_references())
324 .fold(Box::new(std::iter::empty()), |it, tys| {
325 Box::new(it.chain(tys))
326 }),
327 SchemaType::Type(SchemaTypeVariant::Set { element }) => {
328 element.common_type_references()
329 }
330 SchemaType::TypeDef { type_name } => Box::new(std::iter::once(type_name.clone())),
331 _ => Box::new(std::iter::empty()),
332 }
333 }
334
335 pub(crate) fn prefix_common_type_references_with_namespace(
337 self,
338 ns: Option<Name>,
339 ) -> SchemaType {
340 match self {
341 Self::Type(SchemaTypeVariant::Record {
342 attributes,
343 additional_attributes,
344 }) => Self::Type(SchemaTypeVariant::Record {
345 attributes: BTreeMap::from_iter(attributes.into_iter().map(
346 |(attr, TypeOfAttribute { ty, required })| {
347 (
348 attr,
349 TypeOfAttribute {
350 ty: ty.prefix_common_type_references_with_namespace(ns.clone()),
351 required,
352 },
353 )
354 },
355 )),
356 additional_attributes,
357 }),
358 Self::Type(SchemaTypeVariant::Set { element }) => Self::Type(SchemaTypeVariant::Set {
359 element: Box::new(element.prefix_common_type_references_with_namespace(ns)),
360 }),
361 Self::TypeDef { type_name } => Self::TypeDef {
362 type_name: type_name.prefix_namespace_if_unqualified(ns),
363 },
364 _ => self,
365 }
366 }
367}
368
369impl<'de> Deserialize<'de> for SchemaType {
370 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
371 where
372 D: serde::Deserializer<'de>,
373 {
374 deserializer.deserialize_any(SchemaTypeVisitor)
375 }
376}
377
378#[derive(Hash, Eq, PartialEq, Deserialize)]
380#[serde(field_identifier, rename_all = "camelCase")]
381enum TypeFields {
382 Type,
383 Element,
384 Attributes,
385 AdditionalAttributes,
386 Name,
387}
388
389macro_rules! type_field_name {
393 (Type) => {
394 "type"
395 };
396 (Element) => {
397 "element"
398 };
399 (Attributes) => {
400 "attributes"
401 };
402 (AdditionalAttributes) => {
403 "additionalAttributes"
404 };
405 (Name) => {
406 "name"
407 };
408}
409
410impl TypeFields {
411 fn as_str(&self) -> &'static str {
412 match self {
413 TypeFields::Type => type_field_name!(Type),
414 TypeFields::Element => type_field_name!(Element),
415 TypeFields::Attributes => type_field_name!(Attributes),
416 TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
417 TypeFields::Name => type_field_name!(Name),
418 }
419 }
420}
421
422#[derive(Deserialize)]
427struct AttributesTypeMap(
428 #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
429 BTreeMap<SmolStr, TypeOfAttribute>,
430);
431
432struct SchemaTypeVisitor;
433
434impl<'de> Visitor<'de> for SchemaTypeVisitor {
435 type Value = SchemaType;
436
437 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
438 formatter.write_str("builtin type or reference to type defined in commonTypes")
439 }
440
441 fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
442 where
443 M: MapAccess<'de>,
444 {
445 use TypeFields::*;
446
447 let mut type_name: Option<std::result::Result<SmolStr, M::Error>> = None;
453 let mut element: Option<std::result::Result<SchemaType, M::Error>> = None;
454 let mut attributes: Option<std::result::Result<AttributesTypeMap, M::Error>> = None;
455 let mut additional_attributes: Option<std::result::Result<bool, M::Error>> = None;
456 let mut name: Option<std::result::Result<SmolStr, M::Error>> = None;
457
458 while let Some(key) = map.next_key()? {
462 match key {
463 Type => {
464 if type_name.is_some() {
465 return Err(serde::de::Error::duplicate_field(Type.as_str()));
466 }
467 type_name = Some(map.next_value());
468 }
469 Element => {
470 if element.is_some() {
471 return Err(serde::de::Error::duplicate_field(Element.as_str()));
472 }
473 element = Some(map.next_value());
474 }
475 Attributes => {
476 if attributes.is_some() {
477 return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
478 }
479 attributes = Some(map.next_value());
480 }
481 AdditionalAttributes => {
482 if additional_attributes.is_some() {
483 return Err(serde::de::Error::duplicate_field(
484 AdditionalAttributes.as_str(),
485 ));
486 }
487 additional_attributes = Some(map.next_value());
488 }
489 Name => {
490 if name.is_some() {
491 return Err(serde::de::Error::duplicate_field(Name.as_str()));
492 }
493 name = Some(map.next_value());
494 }
495 }
496 }
497
498 Self::build_schema_type::<M>(type_name, element, attributes, additional_attributes, name)
499 }
500}
501
502impl SchemaTypeVisitor {
503 fn build_schema_type<'de, M>(
508 type_name: Option<std::result::Result<SmolStr, M::Error>>,
509 element: Option<std::result::Result<SchemaType, M::Error>>,
510 attributes: Option<std::result::Result<AttributesTypeMap, M::Error>>,
511 additional_attributes: Option<std::result::Result<bool, M::Error>>,
512 name: Option<std::result::Result<SmolStr, M::Error>>,
513 ) -> std::result::Result<SchemaType, M::Error>
514 where
515 M: MapAccess<'de>,
516 {
517 use TypeFields::*;
518 let present_fields = [
519 (Type, type_name.is_some()),
520 (Element, element.is_some()),
521 (Attributes, attributes.is_some()),
522 (AdditionalAttributes, additional_attributes.is_some()),
523 (Name, name.is_some()),
524 ]
525 .into_iter()
526 .filter(|(_, present)| *present)
527 .map(|(field, _)| field)
528 .collect::<HashSet<_>>();
529 let error_if_fields = |fs: &[TypeFields],
532 expected: &'static [&'static str]|
533 -> std::result::Result<(), M::Error> {
534 for f in fs {
535 if present_fields.contains(f) {
536 return Err(serde::de::Error::unknown_field(f.as_str(), expected));
537 }
538 }
539 Ok(())
540 };
541 let error_if_any_fields = || -> std::result::Result<(), M::Error> {
542 error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
543 };
544
545 match type_name.transpose()?.as_ref().map(|s| s.as_str()) {
546 Some("String") => {
547 error_if_any_fields()?;
548 Ok(SchemaType::Type(SchemaTypeVariant::String))
549 }
550 Some("Long") => {
551 error_if_any_fields()?;
552 Ok(SchemaType::Type(SchemaTypeVariant::Long))
553 }
554 Some("Boolean") => {
555 error_if_any_fields()?;
556 Ok(SchemaType::Type(SchemaTypeVariant::Boolean))
557 }
558 Some("Set") => {
559 error_if_fields(
560 &[Attributes, AdditionalAttributes, Name],
561 &[type_field_name!(Element)],
562 )?;
563
564 if let Some(element) = element {
565 Ok(SchemaType::Type(SchemaTypeVariant::Set {
566 element: Box::new(element?),
567 }))
568 } else {
569 Err(serde::de::Error::missing_field(Element.as_str()))
570 }
571 }
572 Some("Record") => {
573 error_if_fields(
574 &[Element, Name],
575 &[
576 type_field_name!(Attributes),
577 type_field_name!(AdditionalAttributes),
578 ],
579 )?;
580
581 if let Some(attributes) = attributes {
582 let additional_attributes =
583 additional_attributes.unwrap_or(Ok(partial_schema_default()));
584 Ok(SchemaType::Type(SchemaTypeVariant::Record {
585 attributes: attributes?.0,
586 additional_attributes: additional_attributes?,
587 }))
588 } else {
589 Err(serde::de::Error::missing_field(Attributes.as_str()))
590 }
591 }
592 Some("Entity") => {
593 error_if_fields(
594 &[Element, Attributes, AdditionalAttributes],
595 &[type_field_name!(Name)],
596 )?;
597
598 if let Some(name) = name {
599 let name = name?;
600 Ok(SchemaType::Type(SchemaTypeVariant::Entity {
601 name: cedar_policy_core::ast::Name::from_normalized_str(&name).map_err(
602 |err| {
603 serde::de::Error::custom(format!(
604 "invalid entity type `{name}`: {err}"
605 ))
606 },
607 )?,
608 }))
609 } else {
610 Err(serde::de::Error::missing_field(Name.as_str()))
611 }
612 }
613 Some("Extension") => {
614 error_if_fields(
615 &[Element, Attributes, AdditionalAttributes],
616 &[type_field_name!(Name)],
617 )?;
618
619 if let Some(name) = name {
620 let name = name?;
621 Ok(SchemaType::Type(SchemaTypeVariant::Extension {
622 name: Id::from_normalized_str(&name).map_err(|err| {
623 serde::de::Error::custom(format!(
624 "invalid extension type `{name}`: {err}"
625 ))
626 })?,
627 }))
628 } else {
629 Err(serde::de::Error::missing_field(Name.as_str()))
630 }
631 }
632 Some(type_name) => {
633 error_if_any_fields()?;
634 Ok(SchemaType::TypeDef {
635 type_name: cedar_policy_core::ast::Name::from_normalized_str(type_name)
636 .map_err(|err| {
637 serde::de::Error::custom(format!(
638 "invalid common type `{type_name}`: {err}"
639 ))
640 })?,
641 })
642 }
643 None => Err(serde::de::Error::missing_field(Type.as_str())),
644 }
645 }
646}
647
648impl From<SchemaTypeVariant> for SchemaType {
649 fn from(variant: SchemaTypeVariant) -> Self {
650 Self::Type(variant)
651 }
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
655#[serde(tag = "type")]
656#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
657#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
658pub enum SchemaTypeVariant {
659 String,
660 Long,
661 Boolean,
662 Set {
663 element: Box<SchemaType>,
664 },
665 Record {
666 attributes: BTreeMap<SmolStr, TypeOfAttribute>,
667 #[serde(rename = "additionalAttributes")]
668 #[serde(skip_serializing_if = "is_partial_schema_default")]
669 additional_attributes: bool,
670 },
671 Entity {
672 name: Name,
673 },
674 Extension {
675 name: Id,
676 },
677}
678
679fn is_partial_schema_default(b: &bool) -> bool {
681 *b == partial_schema_default()
682}
683
684static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
692 "String",
693 "Long",
694 "Boolean",
695 "Set",
696 "Record",
697 "Entity",
698 "Extension",
699];
700
701pub fn is_builtin_type_name(name: &str) -> bool {
702 SCHEMA_TYPE_VARIANT_TAGS.contains(&name)
703}
704
705impl SchemaType {
706 pub fn is_extension(&self) -> Option<bool> {
711 match self {
712 Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
713 Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
714 Self::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
715 .values()
716 .try_fold(false, |a, e| match e.ty.is_extension() {
717 Some(true) => Some(true),
718 Some(false) => Some(a),
719 None => None,
720 }),
721 Self::Type(_) => Some(false),
722 Self::TypeDef { .. } => None,
723 }
724 }
725
726 pub fn is_empty_record(&self) -> bool {
729 match self {
730 Self::Type(SchemaTypeVariant::Record {
731 attributes,
732 additional_attributes,
733 }) => *additional_attributes == partial_schema_default() && attributes.is_empty(),
734 _ => false,
735 }
736 }
737}
738
739#[cfg(feature = "arbitrary")]
740#[allow(clippy::panic)]
742impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
743 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
744 use std::collections::BTreeSet;
745
746 Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
747 1 => SchemaTypeVariant::String,
748 2 => SchemaTypeVariant::Long,
749 3 => SchemaTypeVariant::Boolean,
750 4 => SchemaTypeVariant::Set {
751 element: Box::new(u.arbitrary()?),
752 },
753 5 => {
754 let attributes = {
755 let attr_names: BTreeSet<String> = u.arbitrary()?;
756 attr_names
757 .into_iter()
758 .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
759 .collect::<arbitrary::Result<_>>()?
760 };
761 SchemaTypeVariant::Record {
762 attributes,
763 additional_attributes: u.arbitrary()?,
764 }
765 }
766 6 => {
767 let name: Name = u.arbitrary()?;
768 SchemaTypeVariant::Entity { name }
769 }
770 7 => SchemaTypeVariant::Extension {
771 #[allow(clippy::unwrap_used)]
773 name: "ipaddr".parse().unwrap(),
774 },
775 8 => SchemaTypeVariant::Extension {
776 #[allow(clippy::unwrap_used)]
778 name: "decimal".parse().unwrap(),
779 },
780 n => panic!("bad index: {n}"),
781 }))
782 }
783 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
784 (1, None) }
786}
787
788#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
803#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
804pub struct TypeOfAttribute {
805 #[serde(flatten)]
806 pub ty: SchemaType,
807 #[serde(default = "record_attribute_required_default")]
808 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
809 pub required: bool,
810}
811
812fn is_record_attribute_required_default(b: &bool) -> bool {
814 *b == record_attribute_required_default()
815}
816
817fn partial_schema_default() -> bool {
820 false
821}
822
823fn record_attribute_required_default() -> bool {
825 true
826}
827
828#[cfg(test)]
829mod test {
830 use super::*;
831
832 #[test]
833 fn test_entity_type_parser1() {
834 let user = r#"
835 {
836 "memberOfTypes" : ["UserGroup"]
837 }
838 "#;
839 let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
840 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
841 assert_eq!(
842 et.shape.into_inner(),
843 SchemaType::Type(SchemaTypeVariant::Record {
844 attributes: BTreeMap::new(),
845 additional_attributes: false
846 })
847 );
848 }
849
850 #[test]
851 fn test_entity_type_parser2() {
852 let src = r#"
853 { }
854 "#;
855 let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
856 assert_eq!(et.member_of_types.len(), 0);
857 assert_eq!(
858 et.shape.into_inner(),
859 SchemaType::Type(SchemaTypeVariant::Record {
860 attributes: BTreeMap::new(),
861 additional_attributes: false
862 })
863 );
864 }
865
866 #[test]
867 fn test_action_type_parser1() {
868 let src = r#"
869 {
870 "appliesTo" : {
871 "resourceTypes": ["Album"],
872 "principalTypes": ["User"]
873 },
874 "memberOf": [{"id": "readWrite"}]
875 }
876 "#;
877 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
878 let spec = ApplySpec {
879 resource_types: Some(vec!["Album".parse().unwrap()]),
880 principal_types: Some(vec!["User".parse().unwrap()]),
881 context: AttributesOrContext::default(),
882 };
883 assert_eq!(at.applies_to, Some(spec));
884 assert_eq!(
885 at.member_of,
886 Some(vec![ActionEntityUID {
887 ty: None,
888 id: "readWrite".into()
889 }])
890 );
891 }
892
893 #[test]
894 fn test_action_type_parser2() {
895 let src = r#"
896 { }
897 "#;
898 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
899 assert_eq!(at.applies_to, None);
900 assert!(at.member_of.is_none());
901 }
902
903 #[test]
904 fn test_schema_file_parser() {
905 let src = serde_json::json!(
906 {
907 "entityTypes": {
908
909 "User": {
910 "memberOfTypes": ["UserGroup"]
911 },
912 "Photo": {
913 "memberOfTypes": ["Album", "Account"]
914 },
915
916 "Album": {
917 "memberOfTypes": ["Album", "Account"]
918 },
919 "Account": { },
920 "UserGroup": { }
921 },
922
923 "actions": {
924 "readOnly": { },
925 "readWrite": { },
926 "createAlbum": {
927 "appliesTo" : {
928 "resourceTypes": ["Account", "Album"],
929 "principalTypes": ["User"]
930 },
931 "memberOf": [{"id": "readWrite"}]
932 },
933 "addPhotoToAlbum": {
934 "appliesTo" : {
935 "resourceTypes": ["Album"],
936 "principalTypes": ["User"]
937 },
938 "memberOf": [{"id": "readWrite"}]
939 },
940 "viewPhoto": {
941 "appliesTo" : {
942 "resourceTypes": ["Photo"],
943 "principalTypes": ["User"]
944 },
945 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
946 },
947 "viewComments": {
948 "appliesTo" : {
949 "resourceTypes": ["Photo"],
950 "principalTypes": ["User"]
951 },
952 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
953 }
954 }
955 });
956 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
957
958 assert_eq!(schema_file.entity_types.len(), 5);
959 assert_eq!(schema_file.actions.len(), 6);
960 }
961
962 #[test]
963 fn test_parse_namespaces() {
964 let src = r#"
965 {
966 "foo::foo::bar::baz": {
967 "entityTypes": {},
968 "actions": {}
969 }
970 }"#;
971 let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
972 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
973 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
974 }
975
976 #[test]
977 #[should_panic(expected = "unknown field `requiredddddd`")]
978 fn test_schema_file_with_misspelled_required() {
979 let src = serde_json::json!(
980 {
981 "entityTypes": {
982 "User": {
983 "shape": {
984 "type": "Record",
985 "attributes": {
986 "favorite": {
987 "type": "Entity",
988 "name": "Photo",
989 "requiredddddd": false
990 }
991 }
992 }
993 }
994 },
995 "actions": {}
996 });
997 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
998 println!("{:#?}", schema);
999 }
1000
1001 #[test]
1002 #[should_panic(expected = "unknown field `nameeeeee`")]
1003 fn test_schema_file_with_misspelled_field() {
1004 let src = serde_json::json!(
1005 {
1006 "entityTypes": {
1007 "User": {
1008 "shape": {
1009 "type": "Record",
1010 "attributes": {
1011 "favorite": {
1012 "type": "Entity",
1013 "nameeeeee": "Photo",
1014 }
1015 }
1016 }
1017 }
1018 },
1019 "actions": {}
1020 });
1021 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1022 println!("{:#?}", schema);
1023 }
1024
1025 #[test]
1026 #[should_panic(expected = "unknown field `extra`")]
1027 fn test_schema_file_with_extra_field() {
1028 let src = serde_json::json!(
1029 {
1030 "entityTypes": {
1031 "User": {
1032 "shape": {
1033 "type": "Record",
1034 "attributes": {
1035 "favorite": {
1036 "type": "Entity",
1037 "name": "Photo",
1038 "extra": "Should not exist"
1039 }
1040 }
1041 }
1042 }
1043 },
1044 "actions": {}
1045 });
1046 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1047 println!("{:#?}", schema);
1048 }
1049
1050 #[test]
1051 #[should_panic(expected = "unknown field `memberOfTypes`")]
1052 fn test_schema_file_with_misplaced_field() {
1053 let src = serde_json::json!(
1054 {
1055 "entityTypes": {
1056 "User": {
1057 "shape": {
1058 "memberOfTypes": [],
1059 "type": "Record",
1060 "attributes": {
1061 "favorite": {
1062 "type": "Entity",
1063 "name": "Photo",
1064 }
1065 }
1066 }
1067 }
1068 },
1069 "actions": {}
1070 });
1071 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1072 println!("{:#?}", schema);
1073 }
1074
1075 #[test]
1076 #[should_panic(expected = "missing field `name`")]
1077 fn schema_file_with_missing_field() {
1078 let src = serde_json::json!(
1079 {
1080 "entityTypes": {
1081 "User": {
1082 "shape": {
1083 "type": "Record",
1084 "attributes": {
1085 "favorite": {
1086 "type": "Entity",
1087 }
1088 }
1089 }
1090 }
1091 },
1092 "actions": {}
1093 });
1094 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1095 println!("{:#?}", schema);
1096 }
1097
1098 #[test]
1099 #[should_panic(expected = "missing field `type`")]
1100 fn schema_file_with_missing_type() {
1101 let src = serde_json::json!(
1102 {
1103 "entityTypes": {
1104 "User": {
1105 "shape": { }
1106 }
1107 },
1108 "actions": {}
1109 });
1110 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1111 println!("{:#?}", schema);
1112 }
1113
1114 #[test]
1115 #[should_panic(expected = "unknown field `attributes`")]
1116 fn schema_file_unexpected_malformed_attribute() {
1117 let src = serde_json::json!(
1118 {
1119 "entityTypes": {
1120 "User": {
1121 "shape": {
1122 "type": "Record",
1123 "attributes": {
1124 "a": {
1125 "type": "Long",
1126 "attributes": {
1127 "b": {"foo": "bar"}
1128 }
1129 }
1130 }
1131 }
1132 }
1133 },
1134 "actions": {}
1135 });
1136 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1137 println!("{:#?}", schema);
1138 }
1139}
1140
1141#[cfg(test)]
1143mod strengthened_types {
1144 use cool_asserts::assert_matches;
1145
1146 use crate::{
1147 ActionEntityUID, ApplySpec, EntityType, NamespaceDefinition, SchemaFragment, SchemaType,
1148 };
1149
1150 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
1153 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
1154 }
1155
1156 #[test]
1157 fn invalid_namespace() {
1158 let src = serde_json::json!(
1159 {
1160 "\n" : {
1161 "entityTypes": {},
1162 "actions": {}
1163 }
1164 });
1165 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1166 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
1167
1168 let src = serde_json::json!(
1169 {
1170 "1" : {
1171 "entityTypes": {},
1172 "actions": {}
1173 }
1174 });
1175 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1176 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
1177
1178 let src = serde_json::json!(
1179 {
1180 "*1" : {
1181 "entityTypes": {},
1182 "actions": {}
1183 }
1184 });
1185 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1186 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
1187
1188 let src = serde_json::json!(
1189 {
1190 "::" : {
1191 "entityTypes": {},
1192 "actions": {}
1193 }
1194 });
1195 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1196 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
1197
1198 let src = serde_json::json!(
1199 {
1200 "A::" : {
1201 "entityTypes": {},
1202 "actions": {}
1203 }
1204 });
1205 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1206 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
1207 }
1208
1209 #[test]
1210 fn invalid_common_type() {
1211 let src = serde_json::json!(
1212 {
1213 "entityTypes": {},
1214 "actions": {},
1215 "commonTypes": {
1216 "" : {
1217 "type": "String"
1218 }
1219 }
1220 });
1221 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1222 assert_error_matches(schema, "invalid id ``: unexpected end of input");
1223
1224 let src = serde_json::json!(
1225 {
1226 "entityTypes": {},
1227 "actions": {},
1228 "commonTypes": {
1229 "~" : {
1230 "type": "String"
1231 }
1232 }
1233 });
1234 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1235 assert_error_matches(schema, "invalid id `~`: invalid token");
1236
1237 let src = serde_json::json!(
1238 {
1239 "entityTypes": {},
1240 "actions": {},
1241 "commonTypes": {
1242 "A::B" : {
1243 "type": "String"
1244 }
1245 }
1246 });
1247 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1248 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1249 }
1250
1251 #[test]
1252 fn invalid_entity_type() {
1253 let src = serde_json::json!(
1254 {
1255 "entityTypes": {
1256 "": {}
1257 },
1258 "actions": {}
1259 });
1260 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1261 assert_error_matches(schema, "invalid id ``: unexpected end of input");
1262
1263 let src = serde_json::json!(
1264 {
1265 "entityTypes": {
1266 "*": {}
1267 },
1268 "actions": {}
1269 });
1270 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1271 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
1272
1273 let src = serde_json::json!(
1274 {
1275 "entityTypes": {
1276 "A::B": {}
1277 },
1278 "actions": {}
1279 });
1280 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1281 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1282 }
1283
1284 #[test]
1285 fn invalid_member_of_types() {
1286 let src = serde_json::json!(
1287 {
1288 "memberOfTypes": [""]
1289 });
1290 let schema: Result<EntityType, _> = serde_json::from_value(src);
1291 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1292
1293 let src = serde_json::json!(
1294 {
1295 "memberOfTypes": ["*"]
1296 });
1297 let schema: Result<EntityType, _> = serde_json::from_value(src);
1298 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1299
1300 let src = serde_json::json!(
1301 {
1302 "memberOfTypes": ["A::"]
1303 });
1304 let schema: Result<EntityType, _> = serde_json::from_value(src);
1305 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1306
1307 let src = serde_json::json!(
1308 {
1309 "memberOfTypes": ["::A"]
1310 });
1311 let schema: Result<EntityType, _> = serde_json::from_value(src);
1312 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1313 }
1314
1315 #[test]
1316 fn invalid_apply_spec() {
1317 let src = serde_json::json!(
1318 {
1319 "resourceTypes": [""]
1320 });
1321 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1322 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1323
1324 let src = serde_json::json!(
1325 {
1326 "resourceTypes": ["*"]
1327 });
1328 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1329 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1330
1331 let src = serde_json::json!(
1332 {
1333 "resourceTypes": ["A::"]
1334 });
1335 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1336 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1337
1338 let src = serde_json::json!(
1339 {
1340 "resourceTypes": ["::A"]
1341 });
1342 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1343 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1344 }
1345
1346 #[test]
1347 fn invalid_schema_entity_types() {
1348 let src = serde_json::json!(
1349 {
1350 "type": "Entity",
1351 "name": ""
1352 });
1353 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1354 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
1355
1356 let src = serde_json::json!(
1357 {
1358 "type": "Entity",
1359 "name": "*"
1360 });
1361 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1362 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
1363
1364 let src = serde_json::json!(
1365 {
1366 "type": "Entity",
1367 "name": "::A"
1368 });
1369 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1370 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
1371
1372 let src = serde_json::json!(
1373 {
1374 "type": "Entity",
1375 "name": "A::"
1376 });
1377 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1378 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
1379 }
1380
1381 #[test]
1382 fn invalid_action_euid() {
1383 let src = serde_json::json!(
1384 {
1385 "id": "action",
1386 "type": ""
1387 });
1388 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1389 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1390
1391 let src = serde_json::json!(
1392 {
1393 "id": "action",
1394 "type": "*"
1395 });
1396 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1397 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1398
1399 let src = serde_json::json!(
1400 {
1401 "id": "action",
1402 "type": "Action::"
1403 });
1404 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1405 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
1406
1407 let src = serde_json::json!(
1408 {
1409 "id": "action",
1410 "type": "::Action"
1411 });
1412 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1413 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
1414 }
1415
1416 #[test]
1417 fn invalid_schema_common_types() {
1418 let src = serde_json::json!(
1419 {
1420 "type": ""
1421 });
1422 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1423 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
1424
1425 let src = serde_json::json!(
1426 {
1427 "type": "*"
1428 });
1429 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1430 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
1431
1432 let src = serde_json::json!(
1433 {
1434 "type": "::A"
1435 });
1436 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1437 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
1438
1439 let src = serde_json::json!(
1440 {
1441 "type": "A::"
1442 });
1443 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1444 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
1445 }
1446
1447 #[test]
1448 fn invalid_schema_extension_types() {
1449 let src = serde_json::json!(
1450 {
1451 "type": "Extension",
1452 "name": ""
1453 });
1454 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1455 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
1456
1457 let src = serde_json::json!(
1458 {
1459 "type": "Extension",
1460 "name": "*"
1461 });
1462 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1463 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
1464
1465 let src = serde_json::json!(
1466 {
1467 "type": "Extension",
1468 "name": "__cedar::decimal"
1469 });
1470 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1471 assert_error_matches(
1472 schema,
1473 "invalid extension type `__cedar::decimal`: unexpected token `::`",
1474 );
1475
1476 let src = serde_json::json!(
1477 {
1478 "type": "Extension",
1479 "name": "__cedar::"
1480 });
1481 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1482 assert_error_matches(
1483 schema,
1484 "invalid extension type `__cedar::`: unexpected token `::`",
1485 );
1486
1487 let src = serde_json::json!(
1488 {
1489 "type": "Extension",
1490 "name": "::__cedar"
1491 });
1492 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1493 assert_error_matches(
1494 schema,
1495 "invalid extension type `::__cedar`: unexpected token `::`",
1496 );
1497 }
1498}
1499
1500#[cfg(test)]
1502mod test_json_roundtrip {
1503 use super::*;
1504
1505 #[track_caller] fn roundtrip(schema: SchemaFragment) {
1507 let json = serde_json::to_value(schema.clone()).unwrap();
1508 let new_schema: SchemaFragment = serde_json::from_value(json).unwrap();
1509 assert_eq!(schema, new_schema);
1510 }
1511
1512 #[test]
1513 fn empty_namespace() {
1514 let fragment = SchemaFragment(HashMap::from([(
1515 None,
1516 NamespaceDefinition {
1517 common_types: HashMap::new(),
1518 entity_types: HashMap::new(),
1519 actions: HashMap::new(),
1520 },
1521 )]));
1522 roundtrip(fragment);
1523 }
1524
1525 #[test]
1526 fn nonempty_namespace() {
1527 let fragment = SchemaFragment(HashMap::from([(
1528 Some("a".parse().unwrap()),
1529 NamespaceDefinition {
1530 common_types: HashMap::new(),
1531 entity_types: HashMap::new(),
1532 actions: HashMap::new(),
1533 },
1534 )]));
1535 roundtrip(fragment);
1536 }
1537
1538 #[test]
1539 fn nonempty_entity_types() {
1540 let fragment = SchemaFragment(HashMap::from([(
1541 None,
1542 NamespaceDefinition {
1543 common_types: HashMap::new(),
1544 entity_types: HashMap::from([(
1545 "a".parse().unwrap(),
1546 EntityType {
1547 member_of_types: vec!["a".parse().unwrap()],
1548 shape: AttributesOrContext(SchemaType::Type(SchemaTypeVariant::Record {
1549 attributes: BTreeMap::new(),
1550 additional_attributes: false,
1551 })),
1552 },
1553 )]),
1554 actions: HashMap::from([(
1555 "action".into(),
1556 ActionType {
1557 attributes: None,
1558 applies_to: Some(ApplySpec {
1559 resource_types: Some(vec!["a".parse().unwrap()]),
1560 principal_types: Some(vec!["a".parse().unwrap()]),
1561 context: AttributesOrContext(SchemaType::Type(
1562 SchemaTypeVariant::Record {
1563 attributes: BTreeMap::new(),
1564 additional_attributes: false,
1565 },
1566 )),
1567 }),
1568 member_of: None,
1569 },
1570 )]),
1571 },
1572 )]));
1573 roundtrip(fragment);
1574 }
1575
1576 #[test]
1577 fn multiple_namespaces() {
1578 let fragment = SchemaFragment(HashMap::from([
1579 (
1580 Some("foo".parse().unwrap()),
1581 NamespaceDefinition {
1582 common_types: HashMap::new(),
1583 entity_types: HashMap::from([(
1584 "a".parse().unwrap(),
1585 EntityType {
1586 member_of_types: vec!["a".parse().unwrap()],
1587 shape: AttributesOrContext(SchemaType::Type(
1588 SchemaTypeVariant::Record {
1589 attributes: BTreeMap::new(),
1590 additional_attributes: false,
1591 },
1592 )),
1593 },
1594 )]),
1595 actions: HashMap::new(),
1596 },
1597 ),
1598 (
1599 None,
1600 NamespaceDefinition {
1601 common_types: HashMap::new(),
1602 entity_types: HashMap::new(),
1603 actions: HashMap::from([(
1604 "action".into(),
1605 ActionType {
1606 attributes: None,
1607 applies_to: Some(ApplySpec {
1608 resource_types: Some(vec!["foo::a".parse().unwrap()]),
1609 principal_types: Some(vec!["foo::a".parse().unwrap()]),
1610 context: AttributesOrContext(SchemaType::Type(
1611 SchemaTypeVariant::Record {
1612 attributes: BTreeMap::new(),
1613 additional_attributes: false,
1614 },
1615 )),
1616 }),
1617 member_of: None,
1618 },
1619 )]),
1620 },
1621 ),
1622 ]));
1623 roundtrip(fragment);
1624 }
1625}
1626
1627#[cfg(test)]
1632mod test_duplicates_error {
1633 use super::*;
1634
1635 #[test]
1636 #[should_panic(expected = "invalid entry: found duplicate key")]
1637 fn namespace() {
1638 let src = r#"{
1639 "Foo": {
1640 "entityTypes" : {},
1641 "actions": {}
1642 },
1643 "Foo": {
1644 "entityTypes" : {},
1645 "actions": {}
1646 }
1647 }"#;
1648 serde_json::from_str::<SchemaFragment>(src).unwrap();
1649 }
1650
1651 #[test]
1652 #[should_panic(expected = "invalid entry: found duplicate key")]
1653 fn entity_type() {
1654 let src = r#"{
1655 "Foo": {
1656 "entityTypes" : {
1657 "Bar": {},
1658 "Bar": {},
1659 },
1660 "actions": {}
1661 }
1662 }"#;
1663 serde_json::from_str::<SchemaFragment>(src).unwrap();
1664 }
1665
1666 #[test]
1667 #[should_panic(expected = "invalid entry: found duplicate key")]
1668 fn action() {
1669 let src = r#"{
1670 "Foo": {
1671 "entityTypes" : {},
1672 "actions": {
1673 "Bar": {},
1674 "Bar": {}
1675 }
1676 }
1677 }"#;
1678 serde_json::from_str::<SchemaFragment>(src).unwrap();
1679 }
1680
1681 #[test]
1682 #[should_panic(expected = "invalid entry: found duplicate key")]
1683 fn common_types() {
1684 let src = r#"{
1685 "Foo": {
1686 "entityTypes" : {},
1687 "actions": { },
1688 "commonTypes": {
1689 "Bar": {"type": "Long"},
1690 "Bar": {"type": "String"}
1691 }
1692 }
1693 }"#;
1694 serde_json::from_str::<SchemaFragment>(src).unwrap();
1695 }
1696
1697 #[test]
1698 #[should_panic(expected = "invalid entry: found duplicate key")]
1699 fn record_type() {
1700 let src = r#"{
1701 "Foo": {
1702 "entityTypes" : {
1703 "Bar": {
1704 "shape": {
1705 "type": "Record",
1706 "attributes": {
1707 "Baz": {"type": "Long"},
1708 "Baz": {"type": "String"}
1709 }
1710 }
1711 }
1712 },
1713 "actions": { }
1714 }
1715 }"#;
1716 serde_json::from_str::<SchemaFragment>(src).unwrap();
1717 }
1718}