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
684pub(crate) static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
692 "String",
693 "Long",
694 "Boolean",
695 "Set",
696 "Record",
697 "Entity",
698 "Extension",
699];
700
701impl SchemaType {
702 pub fn is_extension(&self) -> Option<bool> {
707 match self {
708 Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
709 Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
710 Self::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
711 .values()
712 .try_fold(false, |a, e| match e.ty.is_extension() {
713 Some(true) => Some(true),
714 Some(false) => Some(a),
715 None => None,
716 }),
717 Self::Type(_) => Some(false),
718 Self::TypeDef { .. } => None,
719 }
720 }
721
722 pub fn is_empty_record(&self) -> bool {
725 match self {
726 Self::Type(SchemaTypeVariant::Record {
727 attributes,
728 additional_attributes,
729 }) => *additional_attributes == partial_schema_default() && attributes.is_empty(),
730 _ => false,
731 }
732 }
733}
734
735#[cfg(feature = "arbitrary")]
736#[allow(clippy::panic)]
738impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
739 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
740 use std::collections::BTreeSet;
741
742 Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
743 1 => SchemaTypeVariant::String,
744 2 => SchemaTypeVariant::Long,
745 3 => SchemaTypeVariant::Boolean,
746 4 => SchemaTypeVariant::Set {
747 element: Box::new(u.arbitrary()?),
748 },
749 5 => {
750 let attributes = {
751 let attr_names: BTreeSet<String> = u.arbitrary()?;
752 attr_names
753 .into_iter()
754 .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
755 .collect::<arbitrary::Result<_>>()?
756 };
757 SchemaTypeVariant::Record {
758 attributes,
759 additional_attributes: u.arbitrary()?,
760 }
761 }
762 6 => {
763 let name: Name = u.arbitrary()?;
764 SchemaTypeVariant::Entity { name }
765 }
766 7 => SchemaTypeVariant::Extension {
767 #[allow(clippy::unwrap_used)]
769 name: "ipaddr".parse().unwrap(),
770 },
771 8 => SchemaTypeVariant::Extension {
772 #[allow(clippy::unwrap_used)]
774 name: "decimal".parse().unwrap(),
775 },
776 n => panic!("bad index: {n}"),
777 }))
778 }
779 fn size_hint(_depth: usize) -> (usize, Option<usize>) {
780 (1, None) }
782}
783
784#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
799#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
800pub struct TypeOfAttribute {
801 #[serde(flatten)]
802 pub ty: SchemaType,
803 #[serde(default = "record_attribute_required_default")]
804 #[serde(skip_serializing_if = "is_record_attribute_required_default")]
805 pub required: bool,
806}
807
808fn is_record_attribute_required_default(b: &bool) -> bool {
810 *b == record_attribute_required_default()
811}
812
813fn partial_schema_default() -> bool {
816 false
817}
818
819fn record_attribute_required_default() -> bool {
821 true
822}
823
824#[cfg(test)]
825mod test {
826 use super::*;
827
828 #[test]
829 fn test_entity_type_parser1() {
830 let user = r#"
831 {
832 "memberOfTypes" : ["UserGroup"]
833 }
834 "#;
835 let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
836 assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
837 assert_eq!(
838 et.shape.into_inner(),
839 SchemaType::Type(SchemaTypeVariant::Record {
840 attributes: BTreeMap::new(),
841 additional_attributes: false
842 })
843 );
844 }
845
846 #[test]
847 fn test_entity_type_parser2() {
848 let src = r#"
849 { }
850 "#;
851 let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
852 assert_eq!(et.member_of_types.len(), 0);
853 assert_eq!(
854 et.shape.into_inner(),
855 SchemaType::Type(SchemaTypeVariant::Record {
856 attributes: BTreeMap::new(),
857 additional_attributes: false
858 })
859 );
860 }
861
862 #[test]
863 fn test_action_type_parser1() {
864 let src = r#"
865 {
866 "appliesTo" : {
867 "resourceTypes": ["Album"],
868 "principalTypes": ["User"]
869 },
870 "memberOf": [{"id": "readWrite"}]
871 }
872 "#;
873 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
874 let spec = ApplySpec {
875 resource_types: Some(vec!["Album".parse().unwrap()]),
876 principal_types: Some(vec!["User".parse().unwrap()]),
877 context: AttributesOrContext::default(),
878 };
879 assert_eq!(at.applies_to, Some(spec));
880 assert_eq!(
881 at.member_of,
882 Some(vec![ActionEntityUID {
883 ty: None,
884 id: "readWrite".into()
885 }])
886 );
887 }
888
889 #[test]
890 fn test_action_type_parser2() {
891 let src = r#"
892 { }
893 "#;
894 let at: ActionType = serde_json::from_str(src).expect("Parse Error");
895 assert_eq!(at.applies_to, None);
896 assert!(at.member_of.is_none());
897 }
898
899 #[test]
900 fn test_schema_file_parser() {
901 let src = serde_json::json!(
902 {
903 "entityTypes": {
904
905 "User": {
906 "memberOfTypes": ["UserGroup"]
907 },
908 "Photo": {
909 "memberOfTypes": ["Album", "Account"]
910 },
911
912 "Album": {
913 "memberOfTypes": ["Album", "Account"]
914 },
915 "Account": { },
916 "UserGroup": { }
917 },
918
919 "actions": {
920 "readOnly": { },
921 "readWrite": { },
922 "createAlbum": {
923 "appliesTo" : {
924 "resourceTypes": ["Account", "Album"],
925 "principalTypes": ["User"]
926 },
927 "memberOf": [{"id": "readWrite"}]
928 },
929 "addPhotoToAlbum": {
930 "appliesTo" : {
931 "resourceTypes": ["Album"],
932 "principalTypes": ["User"]
933 },
934 "memberOf": [{"id": "readWrite"}]
935 },
936 "viewPhoto": {
937 "appliesTo" : {
938 "resourceTypes": ["Photo"],
939 "principalTypes": ["User"]
940 },
941 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
942 },
943 "viewComments": {
944 "appliesTo" : {
945 "resourceTypes": ["Photo"],
946 "principalTypes": ["User"]
947 },
948 "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
949 }
950 }
951 });
952 let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
953
954 assert_eq!(schema_file.entity_types.len(), 5);
955 assert_eq!(schema_file.actions.len(), 6);
956 }
957
958 #[test]
959 fn test_parse_namespaces() {
960 let src = r#"
961 {
962 "foo::foo::bar::baz": {
963 "entityTypes": {},
964 "actions": {}
965 }
966 }"#;
967 let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
968 let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
969 assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
970 }
971
972 #[test]
973 #[should_panic(expected = "unknown field `requiredddddd`")]
974 fn test_schema_file_with_misspelled_required() {
975 let src = serde_json::json!(
976 {
977 "entityTypes": {
978 "User": {
979 "shape": {
980 "type": "Record",
981 "attributes": {
982 "favorite": {
983 "type": "Entity",
984 "name": "Photo",
985 "requiredddddd": false
986 }
987 }
988 }
989 }
990 },
991 "actions": {}
992 });
993 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
994 println!("{:#?}", schema);
995 }
996
997 #[test]
998 #[should_panic(expected = "unknown field `nameeeeee`")]
999 fn test_schema_file_with_misspelled_field() {
1000 let src = serde_json::json!(
1001 {
1002 "entityTypes": {
1003 "User": {
1004 "shape": {
1005 "type": "Record",
1006 "attributes": {
1007 "favorite": {
1008 "type": "Entity",
1009 "nameeeeee": "Photo",
1010 }
1011 }
1012 }
1013 }
1014 },
1015 "actions": {}
1016 });
1017 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1018 println!("{:#?}", schema);
1019 }
1020
1021 #[test]
1022 #[should_panic(expected = "unknown field `extra`")]
1023 fn test_schema_file_with_extra_field() {
1024 let src = serde_json::json!(
1025 {
1026 "entityTypes": {
1027 "User": {
1028 "shape": {
1029 "type": "Record",
1030 "attributes": {
1031 "favorite": {
1032 "type": "Entity",
1033 "name": "Photo",
1034 "extra": "Should not exist"
1035 }
1036 }
1037 }
1038 }
1039 },
1040 "actions": {}
1041 });
1042 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1043 println!("{:#?}", schema);
1044 }
1045
1046 #[test]
1047 #[should_panic(expected = "unknown field `memberOfTypes`")]
1048 fn test_schema_file_with_misplaced_field() {
1049 let src = serde_json::json!(
1050 {
1051 "entityTypes": {
1052 "User": {
1053 "shape": {
1054 "memberOfTypes": [],
1055 "type": "Record",
1056 "attributes": {
1057 "favorite": {
1058 "type": "Entity",
1059 "name": "Photo",
1060 }
1061 }
1062 }
1063 }
1064 },
1065 "actions": {}
1066 });
1067 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1068 println!("{:#?}", schema);
1069 }
1070
1071 #[test]
1072 #[should_panic(expected = "missing field `name`")]
1073 fn schema_file_with_missing_field() {
1074 let src = serde_json::json!(
1075 {
1076 "entityTypes": {
1077 "User": {
1078 "shape": {
1079 "type": "Record",
1080 "attributes": {
1081 "favorite": {
1082 "type": "Entity",
1083 }
1084 }
1085 }
1086 }
1087 },
1088 "actions": {}
1089 });
1090 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1091 println!("{:#?}", schema);
1092 }
1093
1094 #[test]
1095 #[should_panic(expected = "missing field `type`")]
1096 fn schema_file_with_missing_type() {
1097 let src = serde_json::json!(
1098 {
1099 "entityTypes": {
1100 "User": {
1101 "shape": { }
1102 }
1103 },
1104 "actions": {}
1105 });
1106 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1107 println!("{:#?}", schema);
1108 }
1109
1110 #[test]
1111 #[should_panic(expected = "unknown field `attributes`")]
1112 fn schema_file_unexpected_malformed_attribute() {
1113 let src = serde_json::json!(
1114 {
1115 "entityTypes": {
1116 "User": {
1117 "shape": {
1118 "type": "Record",
1119 "attributes": {
1120 "a": {
1121 "type": "Long",
1122 "attributes": {
1123 "b": {"foo": "bar"}
1124 }
1125 }
1126 }
1127 }
1128 }
1129 },
1130 "actions": {}
1131 });
1132 let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1133 println!("{:#?}", schema);
1134 }
1135}
1136
1137#[cfg(test)]
1139mod strengthened_types {
1140 use cool_asserts::assert_matches;
1141
1142 use crate::{
1143 ActionEntityUID, ApplySpec, EntityType, NamespaceDefinition, SchemaFragment, SchemaType,
1144 };
1145
1146 #[track_caller] fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
1149 assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
1150 }
1151
1152 #[test]
1153 fn invalid_namespace() {
1154 let src = serde_json::json!(
1155 {
1156 "\n" : {
1157 "entityTypes": {},
1158 "actions": {}
1159 }
1160 });
1161 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1162 assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
1163
1164 let src = serde_json::json!(
1165 {
1166 "1" : {
1167 "entityTypes": {},
1168 "actions": {}
1169 }
1170 });
1171 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1172 assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
1173
1174 let src = serde_json::json!(
1175 {
1176 "*1" : {
1177 "entityTypes": {},
1178 "actions": {}
1179 }
1180 });
1181 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1182 assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
1183
1184 let src = serde_json::json!(
1185 {
1186 "::" : {
1187 "entityTypes": {},
1188 "actions": {}
1189 }
1190 });
1191 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1192 assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
1193
1194 let src = serde_json::json!(
1195 {
1196 "A::" : {
1197 "entityTypes": {},
1198 "actions": {}
1199 }
1200 });
1201 let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1202 assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
1203 }
1204
1205 #[test]
1206 fn invalid_common_type() {
1207 let src = serde_json::json!(
1208 {
1209 "entityTypes": {},
1210 "actions": {},
1211 "commonTypes": {
1212 "" : {
1213 "type": "String"
1214 }
1215 }
1216 });
1217 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1218 assert_error_matches(schema, "invalid id ``: unexpected end of input");
1219
1220 let src = serde_json::json!(
1221 {
1222 "entityTypes": {},
1223 "actions": {},
1224 "commonTypes": {
1225 "~" : {
1226 "type": "String"
1227 }
1228 }
1229 });
1230 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1231 assert_error_matches(schema, "invalid id `~`: invalid token");
1232
1233 let src = serde_json::json!(
1234 {
1235 "entityTypes": {},
1236 "actions": {},
1237 "commonTypes": {
1238 "A::B" : {
1239 "type": "String"
1240 }
1241 }
1242 });
1243 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1244 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1245 }
1246
1247 #[test]
1248 fn invalid_entity_type() {
1249 let src = serde_json::json!(
1250 {
1251 "entityTypes": {
1252 "": {}
1253 },
1254 "actions": {}
1255 });
1256 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1257 assert_error_matches(schema, "invalid id ``: unexpected end of input");
1258
1259 let src = serde_json::json!(
1260 {
1261 "entityTypes": {
1262 "*": {}
1263 },
1264 "actions": {}
1265 });
1266 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1267 assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
1268
1269 let src = serde_json::json!(
1270 {
1271 "entityTypes": {
1272 "A::B": {}
1273 },
1274 "actions": {}
1275 });
1276 let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1277 assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1278 }
1279
1280 #[test]
1281 fn invalid_member_of_types() {
1282 let src = serde_json::json!(
1283 {
1284 "memberOfTypes": [""]
1285 });
1286 let schema: Result<EntityType, _> = serde_json::from_value(src);
1287 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1288
1289 let src = serde_json::json!(
1290 {
1291 "memberOfTypes": ["*"]
1292 });
1293 let schema: Result<EntityType, _> = serde_json::from_value(src);
1294 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1295
1296 let src = serde_json::json!(
1297 {
1298 "memberOfTypes": ["A::"]
1299 });
1300 let schema: Result<EntityType, _> = serde_json::from_value(src);
1301 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1302
1303 let src = serde_json::json!(
1304 {
1305 "memberOfTypes": ["::A"]
1306 });
1307 let schema: Result<EntityType, _> = serde_json::from_value(src);
1308 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1309 }
1310
1311 #[test]
1312 fn invalid_apply_spec() {
1313 let src = serde_json::json!(
1314 {
1315 "resourceTypes": [""]
1316 });
1317 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1318 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1319
1320 let src = serde_json::json!(
1321 {
1322 "resourceTypes": ["*"]
1323 });
1324 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1325 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1326
1327 let src = serde_json::json!(
1328 {
1329 "resourceTypes": ["A::"]
1330 });
1331 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1332 assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1333
1334 let src = serde_json::json!(
1335 {
1336 "resourceTypes": ["::A"]
1337 });
1338 let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1339 assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1340 }
1341
1342 #[test]
1343 fn invalid_schema_entity_types() {
1344 let src = serde_json::json!(
1345 {
1346 "type": "Entity",
1347 "name": ""
1348 });
1349 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1350 assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
1351
1352 let src = serde_json::json!(
1353 {
1354 "type": "Entity",
1355 "name": "*"
1356 });
1357 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1358 assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
1359
1360 let src = serde_json::json!(
1361 {
1362 "type": "Entity",
1363 "name": "::A"
1364 });
1365 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1366 assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
1367
1368 let src = serde_json::json!(
1369 {
1370 "type": "Entity",
1371 "name": "A::"
1372 });
1373 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1374 assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
1375 }
1376
1377 #[test]
1378 fn invalid_action_euid() {
1379 let src = serde_json::json!(
1380 {
1381 "id": "action",
1382 "type": ""
1383 });
1384 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1385 assert_error_matches(schema, "invalid name ``: unexpected end of input");
1386
1387 let src = serde_json::json!(
1388 {
1389 "id": "action",
1390 "type": "*"
1391 });
1392 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1393 assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1394
1395 let src = serde_json::json!(
1396 {
1397 "id": "action",
1398 "type": "Action::"
1399 });
1400 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1401 assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
1402
1403 let src = serde_json::json!(
1404 {
1405 "id": "action",
1406 "type": "::Action"
1407 });
1408 let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1409 assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
1410 }
1411
1412 #[test]
1413 fn invalid_schema_common_types() {
1414 let src = serde_json::json!(
1415 {
1416 "type": ""
1417 });
1418 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1419 assert_error_matches(schema, "invalid common type ``: unexpected end of input");
1420
1421 let src = serde_json::json!(
1422 {
1423 "type": "*"
1424 });
1425 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1426 assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
1427
1428 let src = serde_json::json!(
1429 {
1430 "type": "::A"
1431 });
1432 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1433 assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
1434
1435 let src = serde_json::json!(
1436 {
1437 "type": "A::"
1438 });
1439 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1440 assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
1441 }
1442
1443 #[test]
1444 fn invalid_schema_extension_types() {
1445 let src = serde_json::json!(
1446 {
1447 "type": "Extension",
1448 "name": ""
1449 });
1450 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1451 assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
1452
1453 let src = serde_json::json!(
1454 {
1455 "type": "Extension",
1456 "name": "*"
1457 });
1458 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1459 assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
1460
1461 let src = serde_json::json!(
1462 {
1463 "type": "Extension",
1464 "name": "__cedar::decimal"
1465 });
1466 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1467 assert_error_matches(
1468 schema,
1469 "invalid extension type `__cedar::decimal`: unexpected token `::`",
1470 );
1471
1472 let src = serde_json::json!(
1473 {
1474 "type": "Extension",
1475 "name": "__cedar::"
1476 });
1477 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1478 assert_error_matches(
1479 schema,
1480 "invalid extension type `__cedar::`: unexpected token `::`",
1481 );
1482
1483 let src = serde_json::json!(
1484 {
1485 "type": "Extension",
1486 "name": "::__cedar"
1487 });
1488 let schema: Result<SchemaType, _> = serde_json::from_value(src);
1489 assert_error_matches(
1490 schema,
1491 "invalid extension type `::__cedar`: unexpected token `::`",
1492 );
1493 }
1494}
1495
1496#[cfg(test)]
1498mod test_json_roundtrip {
1499 use super::*;
1500
1501 #[track_caller] fn roundtrip(schema: SchemaFragment) {
1503 let json = serde_json::to_value(schema.clone()).unwrap();
1504 let new_schema: SchemaFragment = serde_json::from_value(json).unwrap();
1505 assert_eq!(schema, new_schema);
1506 }
1507
1508 #[test]
1509 fn empty_namespace() {
1510 let fragment = SchemaFragment(HashMap::from([(
1511 None,
1512 NamespaceDefinition {
1513 common_types: HashMap::new(),
1514 entity_types: HashMap::new(),
1515 actions: HashMap::new(),
1516 },
1517 )]));
1518 roundtrip(fragment);
1519 }
1520
1521 #[test]
1522 fn nonempty_namespace() {
1523 let fragment = SchemaFragment(HashMap::from([(
1524 Some("a".parse().unwrap()),
1525 NamespaceDefinition {
1526 common_types: HashMap::new(),
1527 entity_types: HashMap::new(),
1528 actions: HashMap::new(),
1529 },
1530 )]));
1531 roundtrip(fragment);
1532 }
1533
1534 #[test]
1535 fn nonempty_entity_types() {
1536 let fragment = SchemaFragment(HashMap::from([(
1537 None,
1538 NamespaceDefinition {
1539 common_types: HashMap::new(),
1540 entity_types: HashMap::from([(
1541 "a".parse().unwrap(),
1542 EntityType {
1543 member_of_types: vec!["a".parse().unwrap()],
1544 shape: AttributesOrContext(SchemaType::Type(SchemaTypeVariant::Record {
1545 attributes: BTreeMap::new(),
1546 additional_attributes: false,
1547 })),
1548 },
1549 )]),
1550 actions: HashMap::from([(
1551 "action".into(),
1552 ActionType {
1553 attributes: None,
1554 applies_to: Some(ApplySpec {
1555 resource_types: Some(vec!["a".parse().unwrap()]),
1556 principal_types: Some(vec!["a".parse().unwrap()]),
1557 context: AttributesOrContext(SchemaType::Type(
1558 SchemaTypeVariant::Record {
1559 attributes: BTreeMap::new(),
1560 additional_attributes: false,
1561 },
1562 )),
1563 }),
1564 member_of: None,
1565 },
1566 )]),
1567 },
1568 )]));
1569 roundtrip(fragment);
1570 }
1571
1572 #[test]
1573 fn multiple_namespaces() {
1574 let fragment = SchemaFragment(HashMap::from([
1575 (
1576 Some("foo".parse().unwrap()),
1577 NamespaceDefinition {
1578 common_types: HashMap::new(),
1579 entity_types: HashMap::from([(
1580 "a".parse().unwrap(),
1581 EntityType {
1582 member_of_types: vec!["a".parse().unwrap()],
1583 shape: AttributesOrContext(SchemaType::Type(
1584 SchemaTypeVariant::Record {
1585 attributes: BTreeMap::new(),
1586 additional_attributes: false,
1587 },
1588 )),
1589 },
1590 )]),
1591 actions: HashMap::new(),
1592 },
1593 ),
1594 (
1595 None,
1596 NamespaceDefinition {
1597 common_types: HashMap::new(),
1598 entity_types: HashMap::new(),
1599 actions: HashMap::from([(
1600 "action".into(),
1601 ActionType {
1602 attributes: None,
1603 applies_to: Some(ApplySpec {
1604 resource_types: Some(vec!["foo::a".parse().unwrap()]),
1605 principal_types: Some(vec!["foo::a".parse().unwrap()]),
1606 context: AttributesOrContext(SchemaType::Type(
1607 SchemaTypeVariant::Record {
1608 attributes: BTreeMap::new(),
1609 additional_attributes: false,
1610 },
1611 )),
1612 }),
1613 member_of: None,
1614 },
1615 )]),
1616 },
1617 ),
1618 ]));
1619 roundtrip(fragment);
1620 }
1621}
1622
1623#[cfg(test)]
1628mod test_duplicates_error {
1629 use super::*;
1630
1631 #[test]
1632 #[should_panic(expected = "invalid entry: found duplicate key")]
1633 fn namespace() {
1634 let src = r#"{
1635 "Foo": {
1636 "entityTypes" : {},
1637 "actions": {}
1638 },
1639 "Foo": {
1640 "entityTypes" : {},
1641 "actions": {}
1642 }
1643 }"#;
1644 serde_json::from_str::<SchemaFragment>(src).unwrap();
1645 }
1646
1647 #[test]
1648 #[should_panic(expected = "invalid entry: found duplicate key")]
1649 fn entity_type() {
1650 let src = r#"{
1651 "Foo": {
1652 "entityTypes" : {
1653 "Bar": {},
1654 "Bar": {},
1655 },
1656 "actions": {}
1657 }
1658 }"#;
1659 serde_json::from_str::<SchemaFragment>(src).unwrap();
1660 }
1661
1662 #[test]
1663 #[should_panic(expected = "invalid entry: found duplicate key")]
1664 fn action() {
1665 let src = r#"{
1666 "Foo": {
1667 "entityTypes" : {},
1668 "actions": {
1669 "Bar": {},
1670 "Bar": {}
1671 }
1672 }
1673 }"#;
1674 serde_json::from_str::<SchemaFragment>(src).unwrap();
1675 }
1676
1677 #[test]
1678 #[should_panic(expected = "invalid entry: found duplicate key")]
1679 fn common_types() {
1680 let src = r#"{
1681 "Foo": {
1682 "entityTypes" : {},
1683 "actions": { },
1684 "commonTypes": {
1685 "Bar": {"type": "Long"},
1686 "Bar": {"type": "String"}
1687 }
1688 }
1689 }"#;
1690 serde_json::from_str::<SchemaFragment>(src).unwrap();
1691 }
1692
1693 #[test]
1694 #[should_panic(expected = "invalid entry: found duplicate key")]
1695 fn record_type() {
1696 let src = r#"{
1697 "Foo": {
1698 "entityTypes" : {
1699 "Bar": {
1700 "shape": {
1701 "type": "Record",
1702 "attributes": {
1703 "Baz": {"type": "Long"},
1704 "Baz": {"type": "String"}
1705 }
1706 }
1707 }
1708 },
1709 "actions": { }
1710 }
1711 }"#;
1712 serde_json::from_str::<SchemaFragment>(src).unwrap();
1713 }
1714}