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