cedar_policy_validator/
schema_file_format.rs

1/*
2 * Copyright Cedar Contributors
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::{
18    ast::{Id, Name},
19    entities::CedarValueJson,
20    FromNormalizedStr,
21};
22use serde::{
23    de::{MapAccess, Visitor},
24    ser::SerializeMap,
25    Deserialize, Deserializer, Serialize, Serializer,
26};
27use serde_with::serde_as;
28use smol_str::{SmolStr, ToSmolStr};
29use std::collections::{BTreeMap, HashMap, HashSet};
30
31use crate::{
32    human_schema::{
33        self, parser::parse_natural_schema_fragment, SchemaWarning, ToHumanSchemaStrError,
34    },
35    HumanSchemaError, Result,
36};
37
38#[cfg(feature = "wasm")]
39extern crate tsify;
40
41/// A SchemaFragment describe the types for a given instance of Cedar.
42/// SchemaFragments are composed of Entity Types and Action Types. The
43/// schema fragment is split into multiple namespace definitions, eac including
44/// a namespace name which is applied to all entity types (and the implicit
45/// `Action` entity type for all actions) in the schema.
46#[derive(Debug, Clone, PartialEq, Deserialize)]
47#[serde(transparent)]
48#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
49#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
50pub struct SchemaFragment(
51    #[serde(deserialize_with = "deserialize_schema_fragment")]
52    #[cfg_attr(feature = "wasm", tsify(type = "Record<string, NamespaceDefinition>"))]
53    pub HashMap<Option<Name>, NamespaceDefinition>,
54);
55
56/// Custom deserializer to ensure that the empty namespace is mapped to `None`
57fn deserialize_schema_fragment<'de, D>(
58    deserializer: D,
59) -> std::result::Result<HashMap<Option<Name>, NamespaceDefinition>, D::Error>
60where
61    D: Deserializer<'de>,
62{
63    let raw: HashMap<SmolStr, NamespaceDefinition> =
64        serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
65    Ok(HashMap::from_iter(
66        raw.into_iter()
67            .map(|(key, value)| {
68                let key = if key.is_empty() {
69                    None
70                } else {
71                    Some(Name::from_normalized_str(&key).map_err(|err| {
72                        serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
73                    })?)
74                };
75                Ok((key, value))
76            })
77            .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition)>, D::Error>>()?,
78    ))
79}
80
81impl Serialize for SchemaFragment {
82    /// Custom serializer to ensure that `None` is mapped to the empty namespace
83    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
84    where
85        S: Serializer,
86    {
87        let mut map = serializer.serialize_map(Some(self.0.len()))?;
88        for (k, v) in &self.0 {
89            let k: SmolStr = match k {
90                None => "".into(),
91                Some(name) => name.to_smolstr(),
92            };
93            map.serialize_entry(&k, &v)?;
94        }
95        map.end()
96    }
97}
98
99impl SchemaFragment {
100    /// Create a [`SchemaFragment`] from a JSON value (which should be an object
101    /// of the appropriate shape).
102    pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
103        serde_json::from_value(json).map_err(Into::into)
104    }
105
106    /// Create a [`SchemaFragment`] from a string containing JSON (which should
107    /// be an object of the appropriate shape).
108    pub fn from_json_str(json: &str) -> Result<Self> {
109        serde_json::from_str(json).map_err(Into::into)
110    }
111
112    /// Create a [`SchemaFragment`] directly from a file containing a JSON object.
113    pub fn from_file(file: impl std::io::Read) -> Result<Self> {
114        serde_json::from_reader(file).map_err(Into::into)
115    }
116
117    /// Parse the schema (in natural schema syntax) from a string
118    pub fn from_str_natural(
119        src: &str,
120    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
121        let tup = parse_natural_schema_fragment(src)?;
122        Ok(tup)
123    }
124
125    /// Parse the schema (in natural schema syntax) from a reader
126    pub fn from_file_natural(
127        mut file: impl std::io::Read,
128    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
129        let mut src = String::new();
130        file.read_to_string(&mut src)?;
131        Self::from_str_natural(&src)
132    }
133
134    /// Pretty print this [`SchemaFragment`]
135    pub fn as_natural_schema(&self) -> std::result::Result<String, ToHumanSchemaStrError> {
136        let src = human_schema::json_schema_to_custom_schema_str(self)?;
137        Ok(src)
138    }
139}
140
141/// A single namespace definition from a SchemaFragment.
142#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
143#[serde_as]
144#[serde(deny_unknown_fields)]
145#[doc(hidden)]
146#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
147#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
148pub struct NamespaceDefinition {
149    #[serde(default)]
150    #[serde(skip_serializing_if = "HashMap::is_empty")]
151    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
152    #[serde(rename = "commonTypes")]
153    pub common_types: HashMap<Id, SchemaType>,
154    #[serde(rename = "entityTypes")]
155    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
156    pub entity_types: HashMap<Id, EntityType>,
157    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
158    pub actions: HashMap<SmolStr, ActionType>,
159}
160
161impl NamespaceDefinition {
162    pub fn new(
163        entity_types: impl IntoIterator<Item = (Id, EntityType)>,
164        actions: impl IntoIterator<Item = (SmolStr, ActionType)>,
165    ) -> Self {
166        Self {
167            common_types: HashMap::new(),
168            entity_types: entity_types.into_iter().collect(),
169            actions: actions.into_iter().collect(),
170        }
171    }
172}
173
174/// Entity types describe the relationships in the entity store, including what
175/// entities can be members of groups of what types, and what attributes
176/// can/should be included on entities of each type.
177#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
178#[serde(deny_unknown_fields)]
179#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
180#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
181pub struct EntityType {
182    #[serde(default)]
183    #[serde(skip_serializing_if = "Vec::is_empty")]
184    #[serde(rename = "memberOfTypes")]
185    pub member_of_types: Vec<Name>,
186    #[serde(default)]
187    #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
188    pub shape: AttributesOrContext,
189}
190
191#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
192#[serde(transparent)]
193#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
194#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
195pub struct AttributesOrContext(
196    // We use the usual `SchemaType` deserialization, but it will ultimately
197    // need to be a `Record` or type def which resolves to a `Record`.
198    pub SchemaType,
199);
200
201impl AttributesOrContext {
202    pub fn into_inner(self) -> SchemaType {
203        self.0
204    }
205
206    pub fn is_empty_record(&self) -> bool {
207        self.0.is_empty_record()
208    }
209}
210
211impl Default for AttributesOrContext {
212    fn default() -> Self {
213        Self(SchemaType::Type(SchemaTypeVariant::Record {
214            attributes: BTreeMap::new(),
215            additional_attributes: partial_schema_default(),
216        }))
217    }
218}
219
220/// An action type describes a specific action entity.  It also describes what
221/// kinds of entities it can be used on.
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223#[serde(deny_unknown_fields)]
224#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
225#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
226pub struct ActionType {
227    /// This maps attribute names to
228    /// `cedar_policy_core::entities::json::value::CedarValueJson` which is the
229    /// canonical representation of a cedar value as JSON.
230    #[serde(default)]
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
233    #[serde(default)]
234    #[serde(skip_serializing_if = "Option::is_none")]
235    #[serde(rename = "appliesTo")]
236    pub applies_to: Option<ApplySpec>,
237    #[serde(default)]
238    #[serde(skip_serializing_if = "Option::is_none")]
239    #[serde(rename = "memberOf")]
240    pub member_of: Option<Vec<ActionEntityUID>>,
241}
242
243/// The apply spec specifies what principals and resources an action can be used
244/// with.  This specification can either be done through containing to entity
245/// types. The fields of this record are optional so that they can be omitted to
246/// declare that the apply spec for the principal or resource is undefined,
247/// meaning that the action can be applied to any principal or resource. This is
248/// different than providing an empty list because the empty list is interpreted
249/// as specifying that there are no principals or resources that an action
250/// applies to.
251#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
252#[serde(deny_unknown_fields)]
253#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
254#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
255pub struct ApplySpec {
256    #[serde(default)]
257    #[serde(skip_serializing_if = "Option::is_none")]
258    #[serde(rename = "resourceTypes")]
259    pub resource_types: Option<Vec<Name>>,
260    #[serde(default)]
261    #[serde(skip_serializing_if = "Option::is_none")]
262    #[serde(rename = "principalTypes")]
263    pub principal_types: Option<Vec<Name>>,
264    #[serde(default)]
265    #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
266    pub context: AttributesOrContext,
267}
268
269#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
270#[serde(deny_unknown_fields)]
271#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
272#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
273pub struct ActionEntityUID {
274    pub id: SmolStr,
275
276    #[serde(rename = "type")]
277    #[serde(default)]
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub ty: Option<Name>,
280}
281
282impl ActionEntityUID {
283    pub fn default_type(id: SmolStr) -> Self {
284        Self { id, ty: None }
285    }
286}
287
288impl std::fmt::Display for ActionEntityUID {
289    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290        if let Some(ty) = &self.ty {
291            write!(f, "{}::", ty)?
292        } else {
293            write!(f, "Action::")?
294        }
295        write!(f, "\"{}\"", self.id.escape_debug())
296    }
297}
298
299/// A restricted version of the `Type` enum containing only the types which are
300/// exposed to users.
301#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
302// This enum is `untagged` with these variants as a workaround to a serde
303// limitation. It is not possible to have the known variants on one enum, and
304// then, have catch-all variant for any unrecognized tag in the same enum that
305// captures the name of the unrecognized tag.
306#[serde(untagged)]
307#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
308#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
309pub enum SchemaType {
310    Type(SchemaTypeVariant),
311    TypeDef {
312        #[serde(rename = "type")]
313        type_name: Name,
314    },
315}
316
317impl SchemaType {
318    /// Return an iterator of common type references ocurred in the type
319    pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = Name>> {
320        match self {
321            SchemaType::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
322                .iter()
323                .map(|(_, ty)| ty.ty.common_type_references())
324                .fold(Box::new(std::iter::empty()), |it, tys| {
325                    Box::new(it.chain(tys))
326                }),
327            SchemaType::Type(SchemaTypeVariant::Set { element }) => {
328                element.common_type_references()
329            }
330            SchemaType::TypeDef { type_name } => Box::new(std::iter::once(type_name.clone())),
331            _ => Box::new(std::iter::empty()),
332        }
333    }
334
335    /// Prefix unqualified common type references with the namespace they are in
336    pub(crate) fn prefix_common_type_references_with_namespace(
337        self,
338        ns: Option<Name>,
339    ) -> SchemaType {
340        match self {
341            Self::Type(SchemaTypeVariant::Record {
342                attributes,
343                additional_attributes,
344            }) => Self::Type(SchemaTypeVariant::Record {
345                attributes: BTreeMap::from_iter(attributes.into_iter().map(
346                    |(attr, TypeOfAttribute { ty, required })| {
347                        (
348                            attr,
349                            TypeOfAttribute {
350                                ty: ty.prefix_common_type_references_with_namespace(ns.clone()),
351                                required,
352                            },
353                        )
354                    },
355                )),
356                additional_attributes,
357            }),
358            Self::Type(SchemaTypeVariant::Set { element }) => Self::Type(SchemaTypeVariant::Set {
359                element: Box::new(element.prefix_common_type_references_with_namespace(ns)),
360            }),
361            Self::TypeDef { type_name } => Self::TypeDef {
362                type_name: type_name.prefix_namespace_if_unqualified(ns),
363            },
364            _ => self,
365        }
366    }
367}
368
369impl<'de> Deserialize<'de> for SchemaType {
370    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
371    where
372        D: serde::Deserializer<'de>,
373    {
374        deserializer.deserialize_any(SchemaTypeVisitor)
375    }
376}
377
378/// The fields for a `SchemaTypes`. Used for implementing deserialization.
379#[derive(Hash, Eq, PartialEq, Deserialize)]
380#[serde(field_identifier, rename_all = "camelCase")]
381enum TypeFields {
382    Type,
383    Element,
384    Attributes,
385    AdditionalAttributes,
386    Name,
387}
388
389// This macro is used to avoid duplicating the fields names when calling
390// `serde::de::Error::unknown_field`. It wants an `&'static [&'static str]`, and
391// AFAIK, the elements of the static slice must be literals.
392macro_rules! type_field_name {
393    (Type) => {
394        "type"
395    };
396    (Element) => {
397        "element"
398    };
399    (Attributes) => {
400        "attributes"
401    };
402    (AdditionalAttributes) => {
403        "additionalAttributes"
404    };
405    (Name) => {
406        "name"
407    };
408}
409
410impl TypeFields {
411    fn as_str(&self) -> &'static str {
412        match self {
413            TypeFields::Type => type_field_name!(Type),
414            TypeFields::Element => type_field_name!(Element),
415            TypeFields::Attributes => type_field_name!(Attributes),
416            TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
417            TypeFields::Name => type_field_name!(Name),
418        }
419    }
420}
421
422/// Used during deserialization to deserialize the attributes type map while
423/// reporting an error if there are any duplicate keys in the map. I could not
424/// find a way to do the `serde_with` conversion inline without introducing this
425/// struct.
426#[derive(Deserialize)]
427struct AttributesTypeMap(
428    #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
429    BTreeMap<SmolStr, TypeOfAttribute>,
430);
431
432struct SchemaTypeVisitor;
433
434impl<'de> Visitor<'de> for SchemaTypeVisitor {
435    type Value = SchemaType;
436
437    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
438        formatter.write_str("builtin type or reference to type defined in commonTypes")
439    }
440
441    fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
442    where
443        M: MapAccess<'de>,
444    {
445        use TypeFields::*;
446
447        // We keep field values wrapped in a `Result` initially so that we do
448        // not report errors due the contents of a field when the field is not
449        // expected for a particular type variant. We instead report that the
450        // field so not exist at all, so that the schema author can delete the
451        // field without wasting time fixing errors in the value.
452        let mut type_name: Option<std::result::Result<SmolStr, M::Error>> = None;
453        let mut element: Option<std::result::Result<SchemaType, M::Error>> = None;
454        let mut attributes: Option<std::result::Result<AttributesTypeMap, M::Error>> = None;
455        let mut additional_attributes: Option<std::result::Result<bool, M::Error>> = None;
456        let mut name: Option<std::result::Result<SmolStr, M::Error>> = None;
457
458        // Gather all the fields in the object. Any fields that are not one of
459        // the possible fields for some schema type will have been reported by
460        // serde already.
461        while let Some(key) = map.next_key()? {
462            match key {
463                Type => {
464                    if type_name.is_some() {
465                        return Err(serde::de::Error::duplicate_field(Type.as_str()));
466                    }
467                    type_name = Some(map.next_value());
468                }
469                Element => {
470                    if element.is_some() {
471                        return Err(serde::de::Error::duplicate_field(Element.as_str()));
472                    }
473                    element = Some(map.next_value());
474                }
475                Attributes => {
476                    if attributes.is_some() {
477                        return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
478                    }
479                    attributes = Some(map.next_value());
480                }
481                AdditionalAttributes => {
482                    if additional_attributes.is_some() {
483                        return Err(serde::de::Error::duplicate_field(
484                            AdditionalAttributes.as_str(),
485                        ));
486                    }
487                    additional_attributes = Some(map.next_value());
488                }
489                Name => {
490                    if name.is_some() {
491                        return Err(serde::de::Error::duplicate_field(Name.as_str()));
492                    }
493                    name = Some(map.next_value());
494                }
495            }
496        }
497
498        Self::build_schema_type::<M>(type_name, element, attributes, additional_attributes, name)
499    }
500}
501
502impl SchemaTypeVisitor {
503    /// Construct a schema type given the name of the type and its fields.
504    /// Fields which were not present are `None`. It is an error for a field
505    /// which is not used for a particular type to be `Some` when building that
506    /// type.
507    fn build_schema_type<'de, M>(
508        type_name: Option<std::result::Result<SmolStr, M::Error>>,
509        element: Option<std::result::Result<SchemaType, M::Error>>,
510        attributes: Option<std::result::Result<AttributesTypeMap, M::Error>>,
511        additional_attributes: Option<std::result::Result<bool, M::Error>>,
512        name: Option<std::result::Result<SmolStr, M::Error>>,
513    ) -> std::result::Result<SchemaType, M::Error>
514    where
515        M: MapAccess<'de>,
516    {
517        use TypeFields::*;
518        let present_fields = [
519            (Type, type_name.is_some()),
520            (Element, element.is_some()),
521            (Attributes, attributes.is_some()),
522            (AdditionalAttributes, additional_attributes.is_some()),
523            (Name, name.is_some()),
524        ]
525        .into_iter()
526        .filter(|(_, present)| *present)
527        .map(|(field, _)| field)
528        .collect::<HashSet<_>>();
529        // Used to generate the appropriate serde error if a field is present
530        // when it is not expected.
531        let error_if_fields = |fs: &[TypeFields],
532                               expected: &'static [&'static str]|
533         -> std::result::Result<(), M::Error> {
534            for f in fs {
535                if present_fields.contains(f) {
536                    return Err(serde::de::Error::unknown_field(f.as_str(), expected));
537                }
538            }
539            Ok(())
540        };
541        let error_if_any_fields = || -> std::result::Result<(), M::Error> {
542            error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
543        };
544
545        match type_name.transpose()?.as_ref().map(|s| s.as_str()) {
546            Some("String") => {
547                error_if_any_fields()?;
548                Ok(SchemaType::Type(SchemaTypeVariant::String))
549            }
550            Some("Long") => {
551                error_if_any_fields()?;
552                Ok(SchemaType::Type(SchemaTypeVariant::Long))
553            }
554            Some("Boolean") => {
555                error_if_any_fields()?;
556                Ok(SchemaType::Type(SchemaTypeVariant::Boolean))
557            }
558            Some("Set") => {
559                error_if_fields(
560                    &[Attributes, AdditionalAttributes, Name],
561                    &[type_field_name!(Element)],
562                )?;
563
564                if let Some(element) = element {
565                    Ok(SchemaType::Type(SchemaTypeVariant::Set {
566                        element: Box::new(element?),
567                    }))
568                } else {
569                    Err(serde::de::Error::missing_field(Element.as_str()))
570                }
571            }
572            Some("Record") => {
573                error_if_fields(
574                    &[Element, Name],
575                    &[
576                        type_field_name!(Attributes),
577                        type_field_name!(AdditionalAttributes),
578                    ],
579                )?;
580
581                if let Some(attributes) = attributes {
582                    let additional_attributes =
583                        additional_attributes.unwrap_or(Ok(partial_schema_default()));
584                    Ok(SchemaType::Type(SchemaTypeVariant::Record {
585                        attributes: attributes?.0,
586                        additional_attributes: additional_attributes?,
587                    }))
588                } else {
589                    Err(serde::de::Error::missing_field(Attributes.as_str()))
590                }
591            }
592            Some("Entity") => {
593                error_if_fields(
594                    &[Element, Attributes, AdditionalAttributes],
595                    &[type_field_name!(Name)],
596                )?;
597
598                if let Some(name) = name {
599                    let name = name?;
600                    Ok(SchemaType::Type(SchemaTypeVariant::Entity {
601                        name: cedar_policy_core::ast::Name::from_normalized_str(&name).map_err(
602                            |err| {
603                                serde::de::Error::custom(format!(
604                                    "invalid entity type `{name}`: {err}"
605                                ))
606                            },
607                        )?,
608                    }))
609                } else {
610                    Err(serde::de::Error::missing_field(Name.as_str()))
611                }
612            }
613            Some("Extension") => {
614                error_if_fields(
615                    &[Element, Attributes, AdditionalAttributes],
616                    &[type_field_name!(Name)],
617                )?;
618
619                if let Some(name) = name {
620                    let name = name?;
621                    Ok(SchemaType::Type(SchemaTypeVariant::Extension {
622                        name: Id::from_normalized_str(&name).map_err(|err| {
623                            serde::de::Error::custom(format!(
624                                "invalid extension type `{name}`: {err}"
625                            ))
626                        })?,
627                    }))
628                } else {
629                    Err(serde::de::Error::missing_field(Name.as_str()))
630                }
631            }
632            Some(type_name) => {
633                error_if_any_fields()?;
634                Ok(SchemaType::TypeDef {
635                    type_name: cedar_policy_core::ast::Name::from_normalized_str(type_name)
636                        .map_err(|err| {
637                            serde::de::Error::custom(format!(
638                                "invalid common type `{type_name}`: {err}"
639                            ))
640                        })?,
641                })
642            }
643            None => Err(serde::de::Error::missing_field(Type.as_str())),
644        }
645    }
646}
647
648impl From<SchemaTypeVariant> for SchemaType {
649    fn from(variant: SchemaTypeVariant) -> Self {
650        Self::Type(variant)
651    }
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
655#[serde(tag = "type")]
656#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
657#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
658pub enum SchemaTypeVariant {
659    String,
660    Long,
661    Boolean,
662    Set {
663        element: Box<SchemaType>,
664    },
665    Record {
666        attributes: BTreeMap<SmolStr, TypeOfAttribute>,
667        #[serde(rename = "additionalAttributes")]
668        #[serde(skip_serializing_if = "is_partial_schema_default")]
669        additional_attributes: bool,
670    },
671    Entity {
672        name: Name,
673    },
674    Extension {
675        name: Id,
676    },
677}
678
679// Only used for serialization
680fn is_partial_schema_default(b: &bool) -> bool {
681    *b == partial_schema_default()
682}
683
684// The possible tags for a SchemaType as written in a schema JSON document. Used
685// to forbid declaring a custom typedef with the same name as a builtin type.
686// This must be kept up to date with the variants for `SchemaTypeVariant` and
687// their actual serialization by serde. There is crate that looks like it could
688// do this automatically, but it returns an empty slice for the variants names
689// of `SchemaTypeVariant`.
690// https://docs.rs/serde-aux/latest/serde_aux/serde_introspection/fn.serde_introspect.html
691pub(crate) static SCHEMA_TYPE_VARIANT_TAGS: &[&str] = &[
692    "String",
693    "Long",
694    "Boolean",
695    "Set",
696    "Record",
697    "Entity",
698    "Extension",
699];
700
701impl SchemaType {
702    /// Is this `SchemaType` an extension type, or does it contain one
703    /// (recursively)? Returns `None` if this is a `TypeDef` because we can't
704    /// easily properly check the type of a typedef, accounting for namespaces,
705    /// without first converting to a `Type`.
706    pub fn is_extension(&self) -> Option<bool> {
707        match self {
708            Self::Type(SchemaTypeVariant::Extension { .. }) => Some(true),
709            Self::Type(SchemaTypeVariant::Set { element }) => element.is_extension(),
710            Self::Type(SchemaTypeVariant::Record { attributes, .. }) => attributes
711                .values()
712                .try_fold(false, |a, e| match e.ty.is_extension() {
713                    Some(true) => Some(true),
714                    Some(false) => Some(a),
715                    None => None,
716                }),
717            Self::Type(_) => Some(false),
718            Self::TypeDef { .. } => None,
719        }
720    }
721
722    /// Is this `SchemaType` an empty record? This function is used by the `Display`
723    /// implementation to avoid printing unnecessary entity/action data.
724    pub fn is_empty_record(&self) -> bool {
725        match self {
726            Self::Type(SchemaTypeVariant::Record {
727                attributes,
728                additional_attributes,
729            }) => *additional_attributes == partial_schema_default() && attributes.is_empty(),
730            _ => false,
731        }
732    }
733}
734
735#[cfg(feature = "arbitrary")]
736// PANIC SAFETY property testing code
737#[allow(clippy::panic)]
738impl<'a> arbitrary::Arbitrary<'a> for SchemaType {
739    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<SchemaType> {
740        use std::collections::BTreeSet;
741
742        Ok(SchemaType::Type(match u.int_in_range::<u8>(1..=8)? {
743            1 => SchemaTypeVariant::String,
744            2 => SchemaTypeVariant::Long,
745            3 => SchemaTypeVariant::Boolean,
746            4 => SchemaTypeVariant::Set {
747                element: Box::new(u.arbitrary()?),
748            },
749            5 => {
750                let attributes = {
751                    let attr_names: BTreeSet<String> = u.arbitrary()?;
752                    attr_names
753                        .into_iter()
754                        .map(|attr_name| Ok((attr_name.into(), u.arbitrary()?)))
755                        .collect::<arbitrary::Result<_>>()?
756                };
757                SchemaTypeVariant::Record {
758                    attributes,
759                    additional_attributes: u.arbitrary()?,
760                }
761            }
762            6 => {
763                let name: Name = u.arbitrary()?;
764                SchemaTypeVariant::Entity { name }
765            }
766            7 => SchemaTypeVariant::Extension {
767                // PANIC SAFETY: `ipaddr` is a valid `Id`
768                #[allow(clippy::unwrap_used)]
769                name: "ipaddr".parse().unwrap(),
770            },
771            8 => SchemaTypeVariant::Extension {
772                // PANIC SAFETY: `decimal` is a valid `Id`
773                #[allow(clippy::unwrap_used)]
774                name: "decimal".parse().unwrap(),
775            },
776            n => panic!("bad index: {n}"),
777        }))
778    }
779    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
780        (1, None) // Unfortunately, we probably can't be more precise than this
781    }
782}
783
784/// Used to describe the type of a record or entity attribute. It contains a the
785/// type of the attribute and whether the attribute is required. The type is
786/// flattened for serialization, so, in JSON format, this appears as a regular
787/// type with one extra property `required`.
788///
789/// Note that we can't add #[serde(deny_unknown_fields)] here because we are
790/// using #[serde(tag = "type")] in ty:SchemaType which is flattened here.
791/// The way serde(flatten) is implemented means it may be possible to access
792/// fields incorrectly if a struct contains two structs that are flattened
793/// (`<https://github.com/serde-rs/serde/issues/1547>`). This shouldn't apply to
794/// us as we're using flatten only once
795/// (`<https://github.com/serde-rs/serde/issues/1600>`). This should be ok because
796/// unknown fields for TypeOfAttribute should be passed to SchemaType where
797/// they will be denied (`<https://github.com/serde-rs/serde/issues/1600>`).
798#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Eq, PartialOrd, Ord)]
799#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
800pub struct TypeOfAttribute {
801    #[serde(flatten)]
802    pub ty: SchemaType,
803    #[serde(default = "record_attribute_required_default")]
804    #[serde(skip_serializing_if = "is_record_attribute_required_default")]
805    pub required: bool,
806}
807
808// Only used for serialization
809fn is_record_attribute_required_default(b: &bool) -> bool {
810    *b == record_attribute_required_default()
811}
812
813/// By default schema properties which enable parts of partial schema validation
814/// should be `false`.  Defines the default value for `additionalAttributes`.
815fn partial_schema_default() -> bool {
816    false
817}
818
819/// Defines the default value for `required` on record and entity attributes.
820fn record_attribute_required_default() -> bool {
821    true
822}
823
824#[cfg(test)]
825mod test {
826    use super::*;
827
828    #[test]
829    fn test_entity_type_parser1() {
830        let user = r#"
831        {
832            "memberOfTypes" : ["UserGroup"]
833        }
834        "#;
835        let et = serde_json::from_str::<EntityType>(user).expect("Parse Error");
836        assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
837        assert_eq!(
838            et.shape.into_inner(),
839            SchemaType::Type(SchemaTypeVariant::Record {
840                attributes: BTreeMap::new(),
841                additional_attributes: false
842            })
843        );
844    }
845
846    #[test]
847    fn test_entity_type_parser2() {
848        let src = r#"
849              { }
850        "#;
851        let et = serde_json::from_str::<EntityType>(src).expect("Parse Error");
852        assert_eq!(et.member_of_types.len(), 0);
853        assert_eq!(
854            et.shape.into_inner(),
855            SchemaType::Type(SchemaTypeVariant::Record {
856                attributes: BTreeMap::new(),
857                additional_attributes: false
858            })
859        );
860    }
861
862    #[test]
863    fn test_action_type_parser1() {
864        let src = r#"
865              {
866                "appliesTo" : {
867                  "resourceTypes": ["Album"],
868                  "principalTypes": ["User"]
869                },
870                "memberOf": [{"id": "readWrite"}]
871              }
872        "#;
873        let at: ActionType = serde_json::from_str(src).expect("Parse Error");
874        let spec = ApplySpec {
875            resource_types: Some(vec!["Album".parse().unwrap()]),
876            principal_types: Some(vec!["User".parse().unwrap()]),
877            context: AttributesOrContext::default(),
878        };
879        assert_eq!(at.applies_to, Some(spec));
880        assert_eq!(
881            at.member_of,
882            Some(vec![ActionEntityUID {
883                ty: None,
884                id: "readWrite".into()
885            }])
886        );
887    }
888
889    #[test]
890    fn test_action_type_parser2() {
891        let src = r#"
892              { }
893        "#;
894        let at: ActionType = serde_json::from_str(src).expect("Parse Error");
895        assert_eq!(at.applies_to, None);
896        assert!(at.member_of.is_none());
897    }
898
899    #[test]
900    fn test_schema_file_parser() {
901        let src = serde_json::json!(
902        {
903            "entityTypes": {
904
905              "User": {
906                "memberOfTypes": ["UserGroup"]
907              },
908              "Photo": {
909                "memberOfTypes": ["Album", "Account"]
910              },
911
912              "Album": {
913                "memberOfTypes": ["Album", "Account"]
914              },
915              "Account": { },
916              "UserGroup": { }
917           },
918
919           "actions": {
920              "readOnly": { },
921              "readWrite": { },
922              "createAlbum": {
923                "appliesTo" : {
924                  "resourceTypes": ["Account", "Album"],
925                  "principalTypes": ["User"]
926                },
927                "memberOf": [{"id": "readWrite"}]
928              },
929              "addPhotoToAlbum": {
930                "appliesTo" : {
931                  "resourceTypes": ["Album"],
932                  "principalTypes": ["User"]
933                },
934                "memberOf": [{"id": "readWrite"}]
935              },
936              "viewPhoto": {
937                "appliesTo" : {
938                  "resourceTypes": ["Photo"],
939                  "principalTypes": ["User"]
940                },
941                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
942              },
943              "viewComments": {
944                "appliesTo" : {
945                  "resourceTypes": ["Photo"],
946                  "principalTypes": ["User"]
947                },
948                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
949              }
950            }
951          });
952        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
953
954        assert_eq!(schema_file.entity_types.len(), 5);
955        assert_eq!(schema_file.actions.len(), 6);
956    }
957
958    #[test]
959    fn test_parse_namespaces() {
960        let src = r#"
961        {
962            "foo::foo::bar::baz": {
963                "entityTypes": {},
964                "actions": {}
965            }
966        }"#;
967        let schema: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
968        let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
969        assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
970    }
971
972    #[test]
973    #[should_panic(expected = "unknown field `requiredddddd`")]
974    fn test_schema_file_with_misspelled_required() {
975        let src = serde_json::json!(
976        {
977            "entityTypes": {
978                "User": {
979                    "shape": {
980                        "type": "Record",
981                        "attributes": {
982                            "favorite": {
983                                "type": "Entity",
984                                "name": "Photo",
985                                "requiredddddd": false
986                            }
987                        }
988                    }
989                }
990            },
991            "actions": {}
992        });
993        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
994        println!("{:#?}", schema);
995    }
996
997    #[test]
998    #[should_panic(expected = "unknown field `nameeeeee`")]
999    fn test_schema_file_with_misspelled_field() {
1000        let src = serde_json::json!(
1001        {
1002            "entityTypes": {
1003                "User": {
1004                    "shape": {
1005                        "type": "Record",
1006                        "attributes": {
1007                            "favorite": {
1008                                "type": "Entity",
1009                                "nameeeeee": "Photo",
1010                            }
1011                        }
1012                    }
1013                }
1014            },
1015            "actions": {}
1016        });
1017        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1018        println!("{:#?}", schema);
1019    }
1020
1021    #[test]
1022    #[should_panic(expected = "unknown field `extra`")]
1023    fn test_schema_file_with_extra_field() {
1024        let src = serde_json::json!(
1025        {
1026            "entityTypes": {
1027                "User": {
1028                    "shape": {
1029                        "type": "Record",
1030                        "attributes": {
1031                            "favorite": {
1032                                "type": "Entity",
1033                                "name": "Photo",
1034                                "extra": "Should not exist"
1035                            }
1036                        }
1037                    }
1038                }
1039            },
1040            "actions": {}
1041        });
1042        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1043        println!("{:#?}", schema);
1044    }
1045
1046    #[test]
1047    #[should_panic(expected = "unknown field `memberOfTypes`")]
1048    fn test_schema_file_with_misplaced_field() {
1049        let src = serde_json::json!(
1050        {
1051            "entityTypes": {
1052                "User": {
1053                    "shape": {
1054                        "memberOfTypes": [],
1055                        "type": "Record",
1056                        "attributes": {
1057                            "favorite": {
1058                                "type": "Entity",
1059                                "name": "Photo",
1060                            }
1061                        }
1062                    }
1063                }
1064            },
1065            "actions": {}
1066        });
1067        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1068        println!("{:#?}", schema);
1069    }
1070
1071    #[test]
1072    #[should_panic(expected = "missing field `name`")]
1073    fn schema_file_with_missing_field() {
1074        let src = serde_json::json!(
1075        {
1076            "entityTypes": {
1077                "User": {
1078                    "shape": {
1079                        "type": "Record",
1080                        "attributes": {
1081                            "favorite": {
1082                                "type": "Entity",
1083                            }
1084                        }
1085                    }
1086                }
1087            },
1088            "actions": {}
1089        });
1090        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1091        println!("{:#?}", schema);
1092    }
1093
1094    #[test]
1095    #[should_panic(expected = "missing field `type`")]
1096    fn schema_file_with_missing_type() {
1097        let src = serde_json::json!(
1098        {
1099            "entityTypes": {
1100                "User": {
1101                    "shape": { }
1102                }
1103            },
1104            "actions": {}
1105        });
1106        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1107        println!("{:#?}", schema);
1108    }
1109
1110    #[test]
1111    #[should_panic(expected = "unknown field `attributes`")]
1112    fn schema_file_unexpected_malformed_attribute() {
1113        let src = serde_json::json!(
1114        {
1115            "entityTypes": {
1116                "User": {
1117                    "shape": {
1118                        "type": "Record",
1119                        "attributes": {
1120                            "a": {
1121                                "type": "Long",
1122                                "attributes": {
1123                                    "b": {"foo": "bar"}
1124                                }
1125                            }
1126                        }
1127                    }
1128                }
1129            },
1130            "actions": {}
1131        });
1132        let schema: NamespaceDefinition = serde_json::from_value(src).unwrap();
1133        println!("{:#?}", schema);
1134    }
1135}
1136
1137/// Tests related to PR #749
1138#[cfg(test)]
1139mod strengthened_types {
1140    use cool_asserts::assert_matches;
1141
1142    use crate::{
1143        ActionEntityUID, ApplySpec, EntityType, NamespaceDefinition, SchemaFragment, SchemaType,
1144    };
1145
1146    /// Assert that `result` is an `Err`, and the error message matches `msg`
1147    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
1148    fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
1149        assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
1150    }
1151
1152    #[test]
1153    fn invalid_namespace() {
1154        let src = serde_json::json!(
1155        {
1156           "\n" : {
1157            "entityTypes": {},
1158            "actions": {}
1159           }
1160        });
1161        let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1162        assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
1163
1164        let src = serde_json::json!(
1165        {
1166           "1" : {
1167            "entityTypes": {},
1168            "actions": {}
1169           }
1170        });
1171        let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1172        assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
1173
1174        let src = serde_json::json!(
1175        {
1176           "*1" : {
1177            "entityTypes": {},
1178            "actions": {}
1179           }
1180        });
1181        let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1182        assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
1183
1184        let src = serde_json::json!(
1185        {
1186           "::" : {
1187            "entityTypes": {},
1188            "actions": {}
1189           }
1190        });
1191        let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1192        assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
1193
1194        let src = serde_json::json!(
1195        {
1196           "A::" : {
1197            "entityTypes": {},
1198            "actions": {}
1199           }
1200        });
1201        let schema: Result<SchemaFragment, _> = serde_json::from_value(src);
1202        assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
1203    }
1204
1205    #[test]
1206    fn invalid_common_type() {
1207        let src = serde_json::json!(
1208        {
1209            "entityTypes": {},
1210            "actions": {},
1211            "commonTypes": {
1212                "" : {
1213                    "type": "String"
1214                }
1215            }
1216        });
1217        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1218        assert_error_matches(schema, "invalid id ``: unexpected end of input");
1219
1220        let src = serde_json::json!(
1221        {
1222            "entityTypes": {},
1223            "actions": {},
1224            "commonTypes": {
1225                "~" : {
1226                    "type": "String"
1227                }
1228            }
1229        });
1230        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1231        assert_error_matches(schema, "invalid id `~`: invalid token");
1232
1233        let src = serde_json::json!(
1234        {
1235            "entityTypes": {},
1236            "actions": {},
1237            "commonTypes": {
1238                "A::B" : {
1239                    "type": "String"
1240                }
1241            }
1242        });
1243        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1244        assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1245    }
1246
1247    #[test]
1248    fn invalid_entity_type() {
1249        let src = serde_json::json!(
1250        {
1251            "entityTypes": {
1252                "": {}
1253            },
1254            "actions": {}
1255        });
1256        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1257        assert_error_matches(schema, "invalid id ``: unexpected end of input");
1258
1259        let src = serde_json::json!(
1260        {
1261            "entityTypes": {
1262                "*": {}
1263            },
1264            "actions": {}
1265        });
1266        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1267        assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
1268
1269        let src = serde_json::json!(
1270        {
1271            "entityTypes": {
1272                "A::B": {}
1273            },
1274            "actions": {}
1275        });
1276        let schema: Result<NamespaceDefinition, _> = serde_json::from_value(src);
1277        assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
1278    }
1279
1280    #[test]
1281    fn invalid_member_of_types() {
1282        let src = serde_json::json!(
1283        {
1284           "memberOfTypes": [""]
1285        });
1286        let schema: Result<EntityType, _> = serde_json::from_value(src);
1287        assert_error_matches(schema, "invalid name ``: unexpected end of input");
1288
1289        let src = serde_json::json!(
1290        {
1291           "memberOfTypes": ["*"]
1292        });
1293        let schema: Result<EntityType, _> = serde_json::from_value(src);
1294        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1295
1296        let src = serde_json::json!(
1297        {
1298           "memberOfTypes": ["A::"]
1299        });
1300        let schema: Result<EntityType, _> = serde_json::from_value(src);
1301        assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1302
1303        let src = serde_json::json!(
1304        {
1305           "memberOfTypes": ["::A"]
1306        });
1307        let schema: Result<EntityType, _> = serde_json::from_value(src);
1308        assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1309    }
1310
1311    #[test]
1312    fn invalid_apply_spec() {
1313        let src = serde_json::json!(
1314        {
1315           "resourceTypes": [""]
1316        });
1317        let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1318        assert_error_matches(schema, "invalid name ``: unexpected end of input");
1319
1320        let src = serde_json::json!(
1321        {
1322           "resourceTypes": ["*"]
1323        });
1324        let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1325        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1326
1327        let src = serde_json::json!(
1328        {
1329           "resourceTypes": ["A::"]
1330        });
1331        let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1332        assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
1333
1334        let src = serde_json::json!(
1335        {
1336           "resourceTypes": ["::A"]
1337        });
1338        let schema: Result<ApplySpec, _> = serde_json::from_value(src);
1339        assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
1340    }
1341
1342    #[test]
1343    fn invalid_schema_entity_types() {
1344        let src = serde_json::json!(
1345        {
1346           "type": "Entity",
1347            "name": ""
1348        });
1349        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1350        assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
1351
1352        let src = serde_json::json!(
1353        {
1354           "type": "Entity",
1355            "name": "*"
1356        });
1357        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1358        assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
1359
1360        let src = serde_json::json!(
1361        {
1362           "type": "Entity",
1363            "name": "::A"
1364        });
1365        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1366        assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
1367
1368        let src = serde_json::json!(
1369        {
1370           "type": "Entity",
1371            "name": "A::"
1372        });
1373        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1374        assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
1375    }
1376
1377    #[test]
1378    fn invalid_action_euid() {
1379        let src = serde_json::json!(
1380        {
1381           "id": "action",
1382            "type": ""
1383        });
1384        let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1385        assert_error_matches(schema, "invalid name ``: unexpected end of input");
1386
1387        let src = serde_json::json!(
1388        {
1389           "id": "action",
1390            "type": "*"
1391        });
1392        let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1393        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
1394
1395        let src = serde_json::json!(
1396        {
1397           "id": "action",
1398            "type": "Action::"
1399        });
1400        let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1401        assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
1402
1403        let src = serde_json::json!(
1404        {
1405           "id": "action",
1406            "type": "::Action"
1407        });
1408        let schema: Result<ActionEntityUID, _> = serde_json::from_value(src);
1409        assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
1410    }
1411
1412    #[test]
1413    fn invalid_schema_common_types() {
1414        let src = serde_json::json!(
1415        {
1416           "type": ""
1417        });
1418        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1419        assert_error_matches(schema, "invalid common type ``: unexpected end of input");
1420
1421        let src = serde_json::json!(
1422        {
1423           "type": "*"
1424        });
1425        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1426        assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
1427
1428        let src = serde_json::json!(
1429        {
1430           "type": "::A"
1431        });
1432        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1433        assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
1434
1435        let src = serde_json::json!(
1436        {
1437           "type": "A::"
1438        });
1439        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1440        assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
1441    }
1442
1443    #[test]
1444    fn invalid_schema_extension_types() {
1445        let src = serde_json::json!(
1446        {
1447           "type": "Extension",
1448           "name": ""
1449        });
1450        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1451        assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
1452
1453        let src = serde_json::json!(
1454        {
1455            "type": "Extension",
1456           "name": "*"
1457        });
1458        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1459        assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
1460
1461        let src = serde_json::json!(
1462        {
1463            "type": "Extension",
1464           "name": "__cedar::decimal"
1465        });
1466        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1467        assert_error_matches(
1468            schema,
1469            "invalid extension type `__cedar::decimal`: unexpected token `::`",
1470        );
1471
1472        let src = serde_json::json!(
1473        {
1474            "type": "Extension",
1475           "name": "__cedar::"
1476        });
1477        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1478        assert_error_matches(
1479            schema,
1480            "invalid extension type `__cedar::`: unexpected token `::`",
1481        );
1482
1483        let src = serde_json::json!(
1484        {
1485            "type": "Extension",
1486           "name": "::__cedar"
1487        });
1488        let schema: Result<SchemaType, _> = serde_json::from_value(src);
1489        assert_error_matches(
1490            schema,
1491            "invalid extension type `::__cedar`: unexpected token `::`",
1492        );
1493    }
1494}
1495
1496/// Check that (de)serialization works as expected.
1497#[cfg(test)]
1498mod test_json_roundtrip {
1499    use super::*;
1500
1501    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
1502    fn roundtrip(schema: SchemaFragment) {
1503        let json = serde_json::to_value(schema.clone()).unwrap();
1504        let new_schema: SchemaFragment = serde_json::from_value(json).unwrap();
1505        assert_eq!(schema, new_schema);
1506    }
1507
1508    #[test]
1509    fn empty_namespace() {
1510        let fragment = SchemaFragment(HashMap::from([(
1511            None,
1512            NamespaceDefinition {
1513                common_types: HashMap::new(),
1514                entity_types: HashMap::new(),
1515                actions: HashMap::new(),
1516            },
1517        )]));
1518        roundtrip(fragment);
1519    }
1520
1521    #[test]
1522    fn nonempty_namespace() {
1523        let fragment = SchemaFragment(HashMap::from([(
1524            Some("a".parse().unwrap()),
1525            NamespaceDefinition {
1526                common_types: HashMap::new(),
1527                entity_types: HashMap::new(),
1528                actions: HashMap::new(),
1529            },
1530        )]));
1531        roundtrip(fragment);
1532    }
1533
1534    #[test]
1535    fn nonempty_entity_types() {
1536        let fragment = SchemaFragment(HashMap::from([(
1537            None,
1538            NamespaceDefinition {
1539                common_types: HashMap::new(),
1540                entity_types: HashMap::from([(
1541                    "a".parse().unwrap(),
1542                    EntityType {
1543                        member_of_types: vec!["a".parse().unwrap()],
1544                        shape: AttributesOrContext(SchemaType::Type(SchemaTypeVariant::Record {
1545                            attributes: BTreeMap::new(),
1546                            additional_attributes: false,
1547                        })),
1548                    },
1549                )]),
1550                actions: HashMap::from([(
1551                    "action".into(),
1552                    ActionType {
1553                        attributes: None,
1554                        applies_to: Some(ApplySpec {
1555                            resource_types: Some(vec!["a".parse().unwrap()]),
1556                            principal_types: Some(vec!["a".parse().unwrap()]),
1557                            context: AttributesOrContext(SchemaType::Type(
1558                                SchemaTypeVariant::Record {
1559                                    attributes: BTreeMap::new(),
1560                                    additional_attributes: false,
1561                                },
1562                            )),
1563                        }),
1564                        member_of: None,
1565                    },
1566                )]),
1567            },
1568        )]));
1569        roundtrip(fragment);
1570    }
1571
1572    #[test]
1573    fn multiple_namespaces() {
1574        let fragment = SchemaFragment(HashMap::from([
1575            (
1576                Some("foo".parse().unwrap()),
1577                NamespaceDefinition {
1578                    common_types: HashMap::new(),
1579                    entity_types: HashMap::from([(
1580                        "a".parse().unwrap(),
1581                        EntityType {
1582                            member_of_types: vec!["a".parse().unwrap()],
1583                            shape: AttributesOrContext(SchemaType::Type(
1584                                SchemaTypeVariant::Record {
1585                                    attributes: BTreeMap::new(),
1586                                    additional_attributes: false,
1587                                },
1588                            )),
1589                        },
1590                    )]),
1591                    actions: HashMap::new(),
1592                },
1593            ),
1594            (
1595                None,
1596                NamespaceDefinition {
1597                    common_types: HashMap::new(),
1598                    entity_types: HashMap::new(),
1599                    actions: HashMap::from([(
1600                        "action".into(),
1601                        ActionType {
1602                            attributes: None,
1603                            applies_to: Some(ApplySpec {
1604                                resource_types: Some(vec!["foo::a".parse().unwrap()]),
1605                                principal_types: Some(vec!["foo::a".parse().unwrap()]),
1606                                context: AttributesOrContext(SchemaType::Type(
1607                                    SchemaTypeVariant::Record {
1608                                        attributes: BTreeMap::new(),
1609                                        additional_attributes: false,
1610                                    },
1611                                )),
1612                            }),
1613                            member_of: None,
1614                        },
1615                    )]),
1616                },
1617            ),
1618        ]));
1619        roundtrip(fragment);
1620    }
1621}
1622
1623/// Tests in this module check the behavior of schema parsing given duplicate
1624/// map keys. The `json!` macro silently drops duplicate keys before they reach
1625/// our parser, so these tests must be written with `serde_json::from_str`
1626/// instead.
1627#[cfg(test)]
1628mod test_duplicates_error {
1629    use super::*;
1630
1631    #[test]
1632    #[should_panic(expected = "invalid entry: found duplicate key")]
1633    fn namespace() {
1634        let src = r#"{
1635            "Foo": {
1636              "entityTypes" : {},
1637              "actions": {}
1638            },
1639            "Foo": {
1640              "entityTypes" : {},
1641              "actions": {}
1642            }
1643        }"#;
1644        serde_json::from_str::<SchemaFragment>(src).unwrap();
1645    }
1646
1647    #[test]
1648    #[should_panic(expected = "invalid entry: found duplicate key")]
1649    fn entity_type() {
1650        let src = r#"{
1651            "Foo": {
1652              "entityTypes" : {
1653                "Bar": {},
1654                "Bar": {},
1655              },
1656              "actions": {}
1657            }
1658        }"#;
1659        serde_json::from_str::<SchemaFragment>(src).unwrap();
1660    }
1661
1662    #[test]
1663    #[should_panic(expected = "invalid entry: found duplicate key")]
1664    fn action() {
1665        let src = r#"{
1666            "Foo": {
1667              "entityTypes" : {},
1668              "actions": {
1669                "Bar": {},
1670                "Bar": {}
1671              }
1672            }
1673        }"#;
1674        serde_json::from_str::<SchemaFragment>(src).unwrap();
1675    }
1676
1677    #[test]
1678    #[should_panic(expected = "invalid entry: found duplicate key")]
1679    fn common_types() {
1680        let src = r#"{
1681            "Foo": {
1682              "entityTypes" : {},
1683              "actions": { },
1684              "commonTypes": {
1685                "Bar": {"type": "Long"},
1686                "Bar": {"type": "String"}
1687              }
1688            }
1689        }"#;
1690        serde_json::from_str::<SchemaFragment>(src).unwrap();
1691    }
1692
1693    #[test]
1694    #[should_panic(expected = "invalid entry: found duplicate key")]
1695    fn record_type() {
1696        let src = r#"{
1697            "Foo": {
1698              "entityTypes" : {
1699                "Bar": {
1700                    "shape": {
1701                        "type": "Record",
1702                        "attributes": {
1703                            "Baz": {"type": "Long"},
1704                            "Baz": {"type": "String"}
1705                        }
1706                    }
1707                }
1708              },
1709              "actions": { }
1710            }
1711        }"#;
1712        serde_json::from_str::<SchemaFragment>(src).unwrap();
1713    }
1714}