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