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