cedar_policy_validator/
schema_file_format.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use cedar_policy_core::entities::JSONValue;
18use serde::{
19    de::{MapAccess, Visitor},
20    Deserialize, Serialize,
21};
22use serde_with::serde_as;
23use smol_str::SmolStr;
24use std::collections::{BTreeMap, HashMap};
25
26use crate::Result;
27
28/// A SchemaFragment describe the types for a given instance of Cedar.
29/// SchemaFragments are composed of Entity Types and Action Types. The
30/// schema fragment is split into multiple namespace definitions, eac including
31/// a namespace name which is applied to all entity types (and the implicit
32/// `Action` entity type for all actions) in the schema.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(transparent)]
35pub struct SchemaFragment(
36    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
37    pub  HashMap<SmolStr, NamespaceDefinition>,
38);
39
40impl SchemaFragment {
41    /// Create a `SchemaFragment` from a JSON value (which should be an object
42    /// of the appropriate shape).
43    pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
44        serde_json::from_value(json).map_err(Into::into)
45    }
46
47    /// Create a `SchemaFragment` directly from a file.
48    pub fn from_file(file: impl std::io::Read) -> Result<Self> {
49        serde_json::from_reader(file).map_err(Into::into)
50    }
51}
52
53/// A single namespace definition from a SchemaFragment.
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55#[serde_as]
56#[serde(deny_unknown_fields)]
57#[doc(hidden)]
58pub struct NamespaceDefinition {
59    #[serde(default)]
60    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
61    #[serde(rename = "commonTypes")]
62    pub common_types: HashMap<SmolStr, SchemaType>,
63    #[serde(rename = "entityTypes")]
64    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
65    pub entity_types: HashMap<SmolStr, EntityType>,
66    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
67    pub actions: HashMap<SmolStr, ActionType>,
68}
69
70impl NamespaceDefinition {
71    pub fn new(
72        entity_types: impl IntoIterator<Item = (SmolStr, EntityType)>,
73        actions: impl IntoIterator<Item = (SmolStr, ActionType)>,
74    ) -> Self {
75        Self {
76            common_types: HashMap::new(),
77            entity_types: entity_types.into_iter().collect(),
78            actions: actions.into_iter().collect(),
79        }
80    }
81}
82
83impl std::fmt::Display for NamespaceDefinition {
84    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
85        f.write_str(
86            &serde_json::to_string_pretty(&self).expect("failed to serialize NamespaceContents"),
87        )
88    }
89}
90
91/// Entity types describe the relationships in the entity store, including what
92/// entities can be members of groups of what types, and what attributes
93/// can/should be included on entities of each type.
94#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[serde(deny_unknown_fields)]
96pub struct EntityType {
97    #[serde(default)]
98    #[serde(rename = "memberOfTypes")]
99    pub member_of_types: Vec<SmolStr>,
100    #[serde(default)]
101    pub shape: AttributesOrContext,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(transparent)]
106pub struct AttributesOrContext(
107    // We use the usual `SchemaType` deserialization, but it will ultimately
108    // need to be a `Record` or type def which resolves to a `Record`.
109    pub SchemaType,
110);
111
112impl AttributesOrContext {
113    pub fn into_inner(self) -> SchemaType {
114        self.0
115    }
116}
117
118impl Default for AttributesOrContext {
119    fn default() -> Self {
120        Self(SchemaType::Type(SchemaTypeVariant::Record {
121            attributes: BTreeMap::new(),
122            additional_attributes: false,
123        }))
124    }
125}
126
127/// An action type describes a specific action entity.  It also describes what
128/// kinds of entities it can be used on.
129#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
130#[serde(deny_unknown_fields)]
131pub struct ActionType {
132    /// This maps attribute names to
133    /// `cedar_policy_core::entities::json::jsonvalue::JSONValue` which is the
134    /// canonical representation of a cedar value as JSON.
135    #[serde(default)]
136    pub attributes: Option<HashMap<SmolStr, JSONValue>>,
137    #[serde(default)]
138    #[serde(rename = "appliesTo")]
139    pub applies_to: Option<ApplySpec>,
140    #[serde(default)]
141    #[serde(rename = "memberOf")]
142    pub member_of: Option<Vec<ActionEntityUID>>,
143}
144
145/// The apply spec specifies what principals and resources an action can be used
146/// with.  This specification can either be done through containing to entity
147/// types. The fields of this record are optional so that they can be omitted to
148/// declare that the apply spec for the principal or resource is undefined,
149/// meaning that the action can be applied to any principal or resource. This is
150/// different than providing an empty list because the empty list is interpreted
151/// as specifying that there are no principals or resources that an action
152/// applies to.
153#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154#[serde(deny_unknown_fields)]
155pub struct ApplySpec {
156    #[serde(default)]
157    #[serde(rename = "resourceTypes")]
158    pub resource_types: Option<Vec<SmolStr>>,
159    #[serde(default)]
160    #[serde(rename = "principalTypes")]
161    pub principal_types: Option<Vec<SmolStr>>,
162    #[serde(default)]
163    pub context: AttributesOrContext,
164}
165
166#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
167#[serde(deny_unknown_fields)]
168pub struct ActionEntityUID {
169    pub id: SmolStr,
170
171    #[serde(rename = "type")]
172    #[serde(default)]
173    pub ty: Option<SmolStr>,
174}
175
176impl ActionEntityUID {
177    pub fn default_type(id: SmolStr) -> Self {
178        Self { id, ty: None }
179    }
180}
181
182impl std::fmt::Display for ActionEntityUID {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        if let Some(ty) = &self.ty {
185            write!(f, "{}::", ty)?
186        } else {
187            write!(f, "Action::")?
188        }
189        write!(f, "\"{}\"", self.id)
190    }
191}
192
193/// A restricted version of the `Type` enum containing only the types which are
194/// exposed to users.
195#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
196// This enum is `untagged` with these variants as a workaround to a serde
197// limitation. It is not possible to have the known variants on one enum, and
198// then, have catch-all variant for any unrecognized tag in the same enum that
199// captures the name of the unrecognized tag.
200#[serde(untagged)]
201pub enum SchemaType {
202    Type(SchemaTypeVariant),
203    TypeDef {
204        #[serde(rename = "type")]
205        type_name: SmolStr,
206    },
207}
208
209impl<'de> Deserialize<'de> for SchemaType {
210    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
211    where
212        D: serde::Deserializer<'de>,
213    {
214        deserializer.deserialize_any(SchemaTypeVisitor)
215    }
216}
217
218/// The fields for a `SchemaTypes`. Used for implementing deserialization.
219#[derive(Hash, Eq, PartialEq, Deserialize)]
220#[serde(field_identifier, rename_all = "camelCase")]
221enum TypeFields {
222    Type,
223    Element,
224    Attributes,
225    AdditionalAttributes,
226    Name,
227}
228
229// This macro is used to avoid duplicating the fields names when calling
230// `serde::de::Error::unknown_field`. It wants an `&'static [&'static str]`, and
231// AFAIK, the elements of the static slice must be literals.
232macro_rules! type_field_name {
233    (Type) => {
234        "type"
235    };
236    (Element) => {
237        "element"
238    };
239    (Attributes) => {
240        "attributes"
241    };
242    (AdditionalAttributes) => {
243        "additionalAttributes"
244    };
245    (Name) => {
246        "name"
247    };
248}
249
250impl TypeFields {
251    fn as_str(&self) -> &'static str {
252        match self {
253            TypeFields::Type => type_field_name!(Type),
254            TypeFields::Element => type_field_name!(Element),
255            TypeFields::Attributes => type_field_name!(Attributes),
256            TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
257            TypeFields::Name => type_field_name!(Name),
258        }
259    }
260}
261
262/// Used during deserialization to deserialize the attributes type map while
263/// reporting an error if there are any duplicate keys in the map. I could not
264/// find a way to do the `serde_with` conversion inline without introducing this
265/// struct.
266#[derive(Deserialize)]
267struct AttributesTypeMap(
268    #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
269    BTreeMap<SmolStr, TypeOfAttribute>,
270);
271
272struct SchemaTypeVisitor;
273
274impl<'de> Visitor<'de> for SchemaTypeVisitor {
275    type Value = SchemaType;
276
277    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
278        formatter.write_str("builtin type or reference to type defined in commonTypes")
279    }
280
281    fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
282    where
283        M: MapAccess<'de>,
284    {
285        use TypeFields::*;
286
287        // We keep field values wrapped in a `Result` initially so that we do
288        // not report errors due the contents of a field when the field is not
289        // expected for a particular type variant. We instead report that the
290        // field so not exist at all, so that the schema author can delete the
291        // field without wasting time fixing errors in the value.
292        let mut type_name: Option<std::result::Result<SmolStr, M::Error>> = None;
293        let mut element: Option<std::result::Result<SchemaType, M::Error>> = None;
294        let mut attributes: Option<std::result::Result<AttributesTypeMap, M::Error>> = None;
295        let mut additional_attributes: Option<std::result::Result<bool, M::Error>> = None;
296        let mut name: Option<std::result::Result<SmolStr, M::Error>> = None;
297
298        // Gather all the fields in the object. Any fields that are not one of
299        // the possible fields for some schema type will have been reported by
300        // serde already.
301        while let Some(key) = map.next_key()? {
302            match key {
303                Type => {
304                    if type_name.is_some() {
305                        return Err(serde::de::Error::duplicate_field(Type.as_str()));
306                    }
307                    type_name = Some(map.next_value());
308                }
309                Element => {
310                    if element.is_some() {
311                        return Err(serde::de::Error::duplicate_field(Element.as_str()));
312                    }
313                    element = Some(map.next_value());
314                }
315                Attributes => {
316                    if attributes.is_some() {
317                        return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
318                    }
319                    attributes = Some(map.next_value());
320                }
321                AdditionalAttributes => {
322                    if additional_attributes.is_some() {
323                        return Err(serde::de::Error::duplicate_field(
324                            AdditionalAttributes.as_str(),
325                        ));
326                    }
327                    additional_attributes = Some(map.next_value());
328                }
329                Name => {
330                    if name.is_some() {
331                        return Err(serde::de::Error::duplicate_field(Name.as_str()));
332                    }
333                    name = Some(map.next_value());
334                }
335            }
336        }
337
338        Self::build_schema_type::<M>(type_name, element, attributes, additional_attributes, name)
339    }
340}
341
342impl SchemaTypeVisitor {
343    /// Construct a schema type given the name of the type and its fields.
344    /// Fields which were not present are `None`. It is an error for a field
345    /// which is not used for a particular type to be `Some` when building that
346    /// type.
347    fn build_schema_type<'de, M>(
348        type_name: Option<std::result::Result<SmolStr, M::Error>>,
349        element: Option<std::result::Result<SchemaType, M::Error>>,
350        attributes: Option<std::result::Result<AttributesTypeMap, M::Error>>,
351        additional_attributes: Option<std::result::Result<bool, M::Error>>,
352        name: Option<std::result::Result<SmolStr, M::Error>>,
353    ) -> std::result::Result<SchemaType, M::Error>
354    where
355        M: MapAccess<'de>,
356    {
357        use TypeFields::*;
358
359        match type_name.transpose()?.as_ref().map(|s| s.as_str()) {
360            Some("String") => Ok(SchemaType::Type(SchemaTypeVariant::String)),
361            Some("Long") => Ok(SchemaType::Type(SchemaTypeVariant::Long)),
362            Some("Boolean") => Ok(SchemaType::Type(SchemaTypeVariant::Boolean)),
363            Some("Set") => {
364                if let Some(element) = element {
365                    Ok(SchemaType::Type(SchemaTypeVariant::Set {
366                        element: Box::new(element?),
367                    }))
368                } else {
369                    Err(serde::de::Error::missing_field(Element.as_str()))
370                }
371            }
372            Some("Record") => {
373                if let Some(attributes) = attributes {
374                    let additional_attributes =
375                        additional_attributes.unwrap_or(Ok(additional_attributes_default()));
376                    Ok(SchemaType::Type(SchemaTypeVariant::Record {
377                        attributes: attributes?.0,
378                        additional_attributes: additional_attributes?,
379                    }))
380                } else {
381                    Err(serde::de::Error::missing_field(Attributes.as_str()))
382                }
383            }
384            Some("Entity") => {
385                if let Some(name) = name {
386                    Ok(SchemaType::Type(SchemaTypeVariant::Entity { name: name? }))
387                } else {
388                    Err(serde::de::Error::missing_field(Name.as_str()))
389                }
390            }
391            Some("Extension") => {
392                if let Some(name) = name {
393                    Ok(SchemaType::Type(SchemaTypeVariant::Extension {
394                        name: name?,
395                    }))
396                } else {
397                    Err(serde::de::Error::missing_field(Name.as_str()))
398                }
399            }
400            Some(type_name) => Ok(SchemaType::TypeDef {
401                type_name: type_name.into(),
402            }),
403            None => Err(serde::de::Error::missing_field(Type.as_str())),
404        }
405    }
406}
407
408impl From<SchemaTypeVariant> for SchemaType {
409    fn from(variant: SchemaTypeVariant) -> Self {
410        Self::Type(variant)
411    }
412}
413
414#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
415#[serde(tag = "type")]
416pub enum SchemaTypeVariant {
417    String,
418    Long,
419    Boolean,
420    Set {
421        element: Box<SchemaType>,
422    },
423    Record {
424        attributes: BTreeMap<SmolStr, TypeOfAttribute>,
425        #[serde(rename = "additionalAttributes")]
426        additional_attributes: bool,
427    },
428    Entity {
429        name: SmolStr,
430    },
431    Extension {
432        name: SmolStr,
433    },
434}
435
436// The possible tags for a SchemaType as written in a schema JSON document. Used
437// to forbid declaring a custom typedef with the same name as a builtin type.
438// This must be kept up to date with the variants for `SchemaTypeVariant` and
439// their actual serialization by serde. There is crate that looks like it could
440// do this automatically, but it returns an empty slice for the variants names
441// of `SchemaTypeVariant`.
442// https://docs.rs/serde-aux/latest/serde_aux/serde_introspection/fn.serde_introspect.html
443pub(crate) static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
444    "String",
445    "Long",
446    "Boolean",
447    "Set",
448    "Record",
449    "Entity",
450    "Extension",
451];
452
453impl SchemaType {
454    /// Is this `SchemaType` an extension type, or does it contain one
455    /// (recursively)? Returns `None` if this is a `TypeDef` because we can't
456    /// easily properly check the type of a typedef, accounting for namespaces,
457    /// without first converting to a `Type`.
458    pub fn is_extension(&self) -> Option<bool> {
459        match self {
460            Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
461            Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
462            Self::Type(SchemaTypeVariant::Record { attributes, .. }) => {
463                attributes
464                    .values()
465                    .fold(Some(false), |a, e| match e.ty.is_extension() {
466                        Some(true) => Some(true),
467                        Some(false) => a,
468                        None => None,
469                    })
470            }
471            Self::Type(_) => Some(false),
472            Self::TypeDef { .. } => None,
473        }
474    }
475}
476
477#[cfg(feature = "arbitrary")]
478impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
479    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
480        use cedar_policy_core::ast::Name;
481        use std::collections::BTreeSet;
482
483        Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
484            1 => SchemaTypeVariant::String,
485            2 => SchemaTypeVariant::Long,
486            3 => SchemaTypeVariant::Boolean,
487            4 => SchemaTypeVariant::Set {
488                element: Box::new(u.arbitrary()?),
489            },
490            5 => {
491                let attributes = {
492                    let attr_names: BTreeSet<String> = u.arbitrary()?;
493                    attr_names
494                        .into_iter()
495                        .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
496                        .collect::<arbitrary::Result<_>>()?
497                };
498                SchemaTypeVariant::Record {
499                    attributes,
500                    additional_attributes: u.arbitrary()?,
501                }
502            }
503            6 => {
504                let name: Name = u.arbitrary()?;
505                SchemaTypeVariant::Entity {
506                    name: name.to_string().into(),
507                }
508            }
509            7 => SchemaTypeVariant::Extension {
510                name: "ipaddr".into(),
511            },
512            8 => SchemaTypeVariant::Extension {
513                name: "decimal".into(),
514            },
515            n => panic!("bad index: {n}"),
516        }))
517    }
518    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
519        (1, None) // Unfortunately, we probably can't be more precise than this
520    }
521}
522
523/// Used to describe the type of a record or entity attribute. It contains a the
524/// type of the attribute and whether the attribute is required. The type is
525/// flattened for serialization, so, in JSON format, this appears as a regular
526/// type with one extra property `required`.
527///
528/// Note that we can't add #[serde(deny_unknown_fields)] here because we are
529/// using #[serde(tag = "type")] in ty:SchemaType which is flattened here.
530/// The way serde(flatten) is implemented means it may be possible to access
531/// fields incorrectly if a struct contains two structs that are flattened
532/// (`<https://github.com/serde-rs/serde/issues/1547>`). This shouldn't apply to
533/// us as we're using flatten only once
534/// (`<https://github.com/serde-rs/serde/issues/1600>`). This should be ok because
535/// unknown fields for TypeOfAttribute should be passed to SchemaType where
536/// they will be denied (`<https://github.com/serde-rs/serde/issues/1600>`).
537#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
538#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
539pub struct TypeOfAttribute {
540    #[serde(flatten)]
541    pub ty: SchemaType,
542    #[serde(default = "record_attribute_required_default")]
543    pub required: bool,
544}
545
546/// Defines the default value for `additionalAttributes` on records and
547/// entities
548fn additional_attributes_default() -> bool {
549    false
550}
551
552/// Defines the default value for `required` on record and entity attributes.
553fn record_attribute_required_default() -> bool {
554    true
555}
556
557#[cfg(test)]
558mod test {
559    use super::*;
560
561    #[test]
562    fn test_entity_type_parser1() {
563        let user = r#"
564        {
565            "memberOfTypes" : ["UserGroup"]
566        }
567        "#;
568        let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
569        assert_eq!(et.member_of_types, vec!["UserGroup"]);
570        assert_eq!(
571            et.shape.into_inner(),
572            SchemaType::Type(SchemaTypeVariant::Record {
573                attributes: BTreeMap::new(),
574                additional_attributes: false
575            })
576        );
577    }
578
579    #[test]
580    fn test_entity_type_parser2() {
581        let src = r#"
582              { }
583        "#;
584        let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
585        assert_eq!(et.member_of_types.len(), 0);
586        assert_eq!(
587            et.shape.into_inner(),
588            SchemaType::Type(SchemaTypeVariant::Record {
589                attributes: BTreeMap::new(),
590                additional_attributes: false
591            })
592        );
593    }
594
595    #[test]
596    fn test_action_type_parser1() {
597        let src = r#"
598              {
599                "appliesTo" : {
600                  "resourceTypes": ["Album"],
601                  "principalTypes": ["User"]
602                },
603                "memberOf": [{"id": "readWrite"}]
604              }
605        "#;
606        let at: ActionType = serde_json::from_str(src).expect("Parse Error");
607        let spec = ApplySpec {
608            resource_types: Some(vec!["Album".into()]),
609            principal_types: Some(vec!["User".into()]),
610            context: AttributesOrContext::default(),
611        };
612        assert_eq!(at.applies_to, Some(spec));
613        assert_eq!(
614            at.member_of,
615            Some(vec![ActionEntityUID {
616                ty: None,
617                id: "readWrite".into()
618            }])
619        );
620    }
621
622    #[test]
623    fn test_action_type_parser2() {
624        let src = r#"
625              { }
626        "#;
627        let at: ActionType = serde_json::from_str(src).expect("Parse Error");
628        assert_eq!(at.applies_to, None);
629        assert!(at.member_of.is_none());
630    }
631
632    #[test]
633    fn test_schema_file_parser() {
634        let src = serde_json::json!(
635        {
636            "entityTypes": {
637
638              "User": {
639                "memberOfTypes": ["UserGroup"]
640              },
641              "Photo": {
642                "memberOfTypes": ["Album", "Account"]
643              },
644
645              "Album": {
646                "memberOfTypes": ["Album", "Account"]
647              },
648              "Account": { },
649              "UserGroup": { }
650           },
651
652           "actions": {
653              "readOnly": { },
654              "readWrite": { },
655              "createAlbum": {
656                "appliesTo" : {
657                  "resourceTypes": ["Account", "Album"],
658                  "principalTypes": ["User"]
659                },
660                "memberOf": [{"id": "readWrite"}]
661              },
662              "addPhotoToAlbum": {
663                "appliesTo" : {
664                  "resourceTypes": ["Album"],
665                  "principalTypes": ["User"]
666                },
667                "memberOf": [{"id": "readWrite"}]
668              },
669              "viewPhoto": {
670                "appliesTo" : {
671                  "resourceTypes": ["Photo"],
672                  "principalTypes": ["User"]
673                },
674                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
675              },
676              "viewComments": {
677                "appliesTo" : {
678                  "resourceTypes": ["Photo"],
679                  "principalTypes": ["User"]
680                },
681                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
682              }
683            }
684          });
685        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
686
687        assert_eq!(schema_file.entity_types.len(), 5);
688        assert_eq!(schema_file.actions.len(), 6);
689    }
690
691    #[test]
692    fn test_parse_namespaces() {
693        let src = r#"
694        {
695            "foo::foo::bar::baz": {
696                "entityTypes": {},
697                "actions": {}
698            }
699        }"#;
700        let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
701        let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
702        assert_eq!(namespace, "foo::foo::bar::baz".to_string());
703    }
704
705    #[test]
706    #[should_panic(expected = "unknown field `requiredddddd`")]
707    fn test_schema_file_with_misspelled_required() {
708        let src = serde_json::json!(
709        {
710            "entityTypes": {
711                "User": {
712                    "shape": {
713                        "type": "Record",
714                        "attributes": {
715                            "favorite": {
716                                "type": "Entity",
717                                "name": "Photo",
718                                "requiredddddd": false
719                            }
720                        }
721                    }
722                }
723            },
724            "actions": {}
725        });
726        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
727        println!("{:#?}", schema);
728    }
729
730    #[test]
731    #[should_panic(expected = "unknown field `nameeeeee`")]
732    fn test_schema_file_with_misspelled_field() {
733        let src = serde_json::json!(
734        {
735            "entityTypes": {
736                "User": {
737                    "shape": {
738                        "type": "Record",
739                        "attributes": {
740                            "favorite": {
741                                "type": "Entity",
742                                "nameeeeee": "Photo",
743                            }
744                        }
745                    }
746                }
747            },
748            "actions": {}
749        });
750        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
751        println!("{:#?}", schema);
752    }
753
754    #[test]
755    #[should_panic(expected = "unknown field `extra`")]
756    fn test_schema_file_with_extra_field() {
757        let src = serde_json::json!(
758        {
759            "entityTypes": {
760                "User": {
761                    "shape": {
762                        "type": "Record",
763                        "attributes": {
764                            "favorite": {
765                                "type": "Entity",
766                                "name": "Photo",
767                                "extra": "Should not exist"
768                            }
769                        }
770                    }
771                }
772            },
773            "actions": {}
774        });
775        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
776        println!("{:#?}", schema);
777    }
778
779    #[test]
780    #[should_panic(expected = "unknown field `memberOfTypes`")]
781    fn test_schema_file_with_misplaced_field() {
782        let src = serde_json::json!(
783        {
784            "entityTypes": {
785                "User": {
786                    "shape": {
787                        "memberOfTypes": [],
788                        "type": "Record",
789                        "attributes": {
790                            "favorite": {
791                                "type": "Entity",
792                                "name": "Photo",
793                            }
794                        }
795                    }
796                }
797            },
798            "actions": {}
799        });
800        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
801        println!("{:#?}", schema);
802    }
803
804    #[test]
805    #[should_panic(expected = "missing field `name`")]
806    fn schema_file_with_missing_field() {
807        let src = serde_json::json!(
808        {
809            "entityTypes": {
810                "User": {
811                    "shape": {
812                        "type": "Record",
813                        "attributes": {
814                            "favorite": {
815                                "type": "Entity",
816                            }
817                        }
818                    }
819                }
820            },
821            "actions": {}
822        });
823        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
824        println!("{:#?}", schema);
825    }
826
827    #[test]
828    #[should_panic(expected = "missing field `type`")]
829    fn schema_file_with_missing_type() {
830        let src = serde_json::json!(
831        {
832            "entityTypes": {
833                "User": {
834                    "shape": { }
835                }
836            },
837            "actions": {}
838        });
839        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
840        println!("{:#?}", schema);
841    }
842
843    #[test]
844    fn test_schema_file_with_field_from_other_type() {
845        let src = serde_json::json!(
846        {
847            "entityTypes": {
848                "User": {
849                    "shape": {
850                        "type": "Record",
851                        "attributes": {
852                            "favorite": {
853                                "type": "String",
854                                // These fields shouldn't exist for a String,
855                                // and we could detect this error, but we allow
856                                // it to maintain backwards compatibility.
857                                "name": "Photo",
858                                "attributes": {},
859                                "element": "",
860                            }
861                        }
862                    }
863                }
864            },
865            "actions": {}
866        });
867        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
868        println!("{:#?}", schema);
869    }
870
871    #[test]
872    fn schema_file_unexpected_malformed_attribute() {
873        let src = serde_json::json!(
874        {
875            "entityTypes": {
876                "User": {
877                    "shape": {
878                        "type": "Record",
879                        "attributes": {
880                            "a": {
881                                "type": "Long",
882                                // Similar to above, `attributes` shouldn't
883                                // exist, and `"foo": "bar"` is an invalid
884                                // attribute when it should exist. We allow this
885                                // for backwards compatibility.
886                                "attributes": {
887                                    "b": {"foo": "bar"}
888                                }
889                            }
890                        }
891                    }
892                }
893            },
894            "actions": {}
895        });
896        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
897        println!("{:#?}", schema);
898    }
899}
900
901/// Tests in this module check the behavior of schema parsing given duplicate
902/// map keys. The `json!` macro silently drops duplicate keys before they reach
903/// our parser, so these tests must be written with `serde_json::from_str`
904/// instead.
905#[cfg(test)]
906mod test_duplicates_error {
907    use super::*;
908
909    #[test]
910    #[should_panic(expected = "invalid entry: found duplicate key")]
911    fn namespace() {
912        let src = r#"{
913            "Foo": {
914              "entityTypes" : {},
915              "actions": {}
916            },
917            "Foo": {
918              "entityTypes" : {},
919              "actions": {}
920            }
921        }"#;
922        serde_json::from_str::<SchemaFragment>(src).unwrap();
923    }
924
925    #[test]
926    #[should_panic(expected = "invalid entry: found duplicate key")]
927    fn entity_type() {
928        let src = r#"{
929            "Foo": {
930              "entityTypes" : {
931                "Bar": {},
932                "Bar": {},
933              },
934              "actions": {}
935            }
936        }"#;
937        serde_json::from_str::<SchemaFragment>(src).unwrap();
938    }
939
940    #[test]
941    #[should_panic(expected = "invalid entry: found duplicate key")]
942    fn action() {
943        let src = r#"{
944            "Foo": {
945              "entityTypes" : {},
946              "actions": {
947                "Bar": {},
948                "Bar": {}
949              }
950            }
951        }"#;
952        serde_json::from_str::<SchemaFragment>(src).unwrap();
953    }
954
955    #[test]
956    #[should_panic(expected = "invalid entry: found duplicate key")]
957    fn common_types() {
958        let src = r#"{
959            "Foo": {
960              "entityTypes" : {},
961              "actions": { },
962              "commonTypes": {
963                "Bar": {"type": "Long"},
964                "Bar": {"type": "String"}
965              }
966            }
967        }"#;
968        serde_json::from_str::<SchemaFragment>(src).unwrap();
969    }
970
971    #[test]
972    #[should_panic(expected = "invalid entry: found duplicate key")]
973    fn record_type() {
974        let src = r#"{
975            "Foo": {
976              "entityTypes" : {
977                "Bar": {
978                    "shape": {
979                        "type": "Record",
980                        "attributes": {
981                            "Baz": {"type": "Long"},
982                            "Baz": {"type": "String"}
983                        }
984                    }
985                }
986              },
987              "actions": { }
988            }
989        }"#;
990        serde_json::from_str::<SchemaFragment>(src).unwrap();
991    }
992}