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