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