cedar_policy_core/validator/
json_schema.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
17//! Structures defining the JSON syntax for Cedar schemas
18
19use crate::{
20    ast::{Eid, EntityUID, InternalName, Name, UnreservedId},
21    entities::CedarValueJson,
22    est::Annotations,
23    extensions::Extensions,
24    parser::Loc,
25    FromNormalizedStr,
26};
27use educe::Educe;
28use itertools::Itertools;
29use nonempty::{nonempty, NonEmpty};
30use serde::{
31    de::{MapAccess, Visitor},
32    ser::SerializeMap,
33    Deserialize, Deserializer, Serialize, Serializer,
34};
35use serde_with::serde_as;
36use smol_str::{SmolStr, ToSmolStr};
37use std::hash::Hash;
38use std::{
39    collections::{BTreeMap, HashMap, HashSet},
40    fmt::Display,
41    marker::PhantomData,
42    str::FromStr,
43};
44use thiserror::Error;
45
46use crate::validator::{
47    cedar_schema::{
48        self, fmt::ToCedarSchemaSyntaxError, parser::parse_cedar_schema_fragment, SchemaWarning,
49    },
50    err::{schema_errors::*, Result},
51    AllDefs, CedarSchemaError, CedarSchemaParseError, ConditionalName, RawName, ReferenceType,
52};
53
54/// Represents the definition of a common type in the schema.
55#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
56#[educe(PartialEq, Eq)]
57#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
58pub struct CommonType<N> {
59    /// The referred type
60    #[serde(flatten)]
61    pub ty: Type<N>,
62    /// Annotations
63    #[serde(default)]
64    #[serde(skip_serializing_if = "Annotations::is_empty")]
65    pub annotations: Annotations,
66    /// Source location
67    ///
68    /// (As of this writing, this is not populated when parsing from JSON.
69    /// It is only populated if constructing this structure from the
70    /// corresponding Cedar-syntax structure.)
71    #[serde(skip)]
72    #[educe(PartialEq(ignore))]
73    pub loc: Option<Loc>,
74}
75
76/// A [`Fragment`] is split into multiple namespace definitions, and is just a
77/// map from namespace name to namespace definition (i.e., definitions of common
78/// types, entity types, and actions in that namespace).
79/// The namespace name is implicitly applied to all definitions in the
80/// corresponding [`NamespaceDefinition`].
81/// See [`NamespaceDefinition`].
82///
83/// The parameter `N` is the type of entity type names and common type names in
84/// attributes/parents fields in this [`Fragment`], including recursively. (It
85/// doesn't affect the type of common and entity type names _that are being
86/// declared here_, which is always an [`UnreservedId`] and unambiguously refers
87/// to the [`InternalName`] with the appropriate implicit namespace prepended.
88/// It only affects the type of common and entity type _references_.)
89/// For example:
90/// - `N` = [`RawName`]: This is the schema JSON format exposed to users
91/// - `N` = [`ConditionalName`]: a [`Fragment`] which has been partially
92///   processed, by converting [`RawName`]s into [`ConditionalName`]s
93/// - `N` = [`InternalName`]: a [`Fragment`] in which all names have been
94///   resolved into fully-qualified [`InternalName`]s
95#[derive(Educe, Debug, Clone, Deserialize)]
96#[educe(PartialEq, Eq)]
97#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
98#[serde(transparent)]
99#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
100#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
101#[cfg_attr(feature = "wasm", serde(rename = "SchemaJson"))]
102pub struct Fragment<N>(
103    #[serde(deserialize_with = "deserialize_schema_fragment")]
104    #[cfg_attr(
105        feature = "wasm",
106        tsify(type = "Record<string, NamespaceDefinition<N>>")
107    )]
108    pub BTreeMap<Option<Name>, NamespaceDefinition<N>>,
109);
110
111/// Custom deserializer to ensure that the empty namespace is mapped to `None`
112fn deserialize_schema_fragment<'de, D, N: Deserialize<'de> + From<RawName>>(
113    deserializer: D,
114) -> std::result::Result<BTreeMap<Option<Name>, NamespaceDefinition<N>>, D::Error>
115where
116    D: Deserializer<'de>,
117{
118    let raw: BTreeMap<SmolStr, NamespaceDefinition<N>> =
119        serde_with::rust::maps_duplicate_key_is_error::deserialize(deserializer)?;
120    Ok(BTreeMap::from_iter(
121        raw.into_iter()
122            .map(|(key, value)| {
123                let key = if key.is_empty() {
124                    if !value.annotations.is_empty() {
125                        Err(serde::de::Error::custom(
126                            "annotations are not allowed on the empty namespace".to_string(),
127                        ))?
128                    }
129                    None
130                } else {
131                    Some(Name::from_normalized_str(&key).map_err(|err| {
132                        serde::de::Error::custom(format!("invalid namespace `{key}`: {err}"))
133                    })?)
134                };
135                Ok((key, value))
136            })
137            .collect::<std::result::Result<Vec<(Option<Name>, NamespaceDefinition<N>)>, D::Error>>(
138            )?,
139    ))
140}
141
142impl<N: Serialize> Serialize for Fragment<N> {
143    /// Custom serializer to ensure that `None` is mapped to the empty namespace
144    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
145    where
146        S: Serializer,
147    {
148        let mut map = serializer.serialize_map(Some(self.0.len()))?;
149        for (k, v) in &self.0 {
150            let k: SmolStr = match k {
151                None => "".into(),
152                Some(name) => name.to_smolstr(),
153            };
154            map.serialize_entry(&k, &v)?;
155        }
156        map.end()
157    }
158}
159
160impl Fragment<RawName> {
161    /// Create a [`Fragment`] from a string containing JSON (which should
162    /// be an object of the appropriate shape).
163    pub fn from_json_str(json: &str) -> Result<Self> {
164        serde_json::from_str(json).map_err(|e| JsonDeserializationError::new(e, Some(json)).into())
165    }
166
167    /// Create a [`Fragment`] from a JSON value (which should be an object
168    /// of the appropriate shape).
169    pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
170        serde_json::from_value(json).map_err(|e| JsonDeserializationError::new(e, None).into())
171    }
172
173    /// Create a [`Fragment`] directly from a file containing a JSON object.
174    pub fn from_json_file(file: impl std::io::Read) -> Result<Self> {
175        serde_json::from_reader(file).map_err(|e| JsonDeserializationError::new(e, None).into())
176    }
177
178    /// Parse the schema (in the Cedar schema syntax) from a string
179    pub fn from_cedarschema_str<'a>(
180        src: &str,
181        extensions: &Extensions<'a>,
182    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
183    {
184        parse_cedar_schema_fragment(src, extensions)
185            .map_err(|e| CedarSchemaParseError::new(e, src).into())
186    }
187
188    /// Parse the schema (in the Cedar schema syntax) from a reader
189    pub fn from_cedarschema_file<'a>(
190        mut file: impl std::io::Read,
191        extensions: &'a Extensions<'_>,
192    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
193    {
194        let mut src = String::new();
195        file.read_to_string(&mut src)?;
196        Self::from_cedarschema_str(&src, extensions)
197    }
198}
199
200impl<N: Display> Fragment<N> {
201    /// Pretty print this [`Fragment`]
202    pub fn to_cedarschema(&self) -> std::result::Result<String, ToCedarSchemaSyntaxError> {
203        let src = cedar_schema::fmt::json_schema_to_cedar_schema_str(self)?;
204        Ok(src)
205    }
206}
207
208/// An [`UnreservedId`] that cannot be reserved JSON schema keywords
209/// like `Set`, `Long`, and etc.
210#[derive(Educe, Debug, Clone, Serialize)]
211#[educe(PartialEq, Eq, PartialOrd, Ord, Hash)]
212#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
213#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
214pub struct CommonTypeId(#[cfg_attr(feature = "wasm", tsify(type = "string"))] UnreservedId);
215
216impl From<CommonTypeId> for UnreservedId {
217    fn from(value: CommonTypeId) -> Self {
218        value.0
219    }
220}
221
222impl AsRef<UnreservedId> for CommonTypeId {
223    fn as_ref(&self) -> &UnreservedId {
224        &self.0
225    }
226}
227
228impl CommonTypeId {
229    /// Create a [`CommonTypeId`] from an [`UnreservedId`], failing if it is a reserved basename
230    pub fn new(id: UnreservedId) -> std::result::Result<Self, ReservedCommonTypeBasenameError> {
231        if Self::is_reserved_schema_keyword(&id) {
232            Err(ReservedCommonTypeBasenameError { id })
233        } else {
234            Ok(Self(id))
235        }
236    }
237
238    /// Create a [`CommonTypeId`] based on an [`UnreservedId`] but do not check
239    /// if the latter is valid or not
240    pub fn unchecked(id: UnreservedId) -> Self {
241        Self(id)
242    }
243
244    // Test if this id is a reserved JSON schema keyword.
245    // Issues:
246    // https://github.com/cedar-policy/cedar/issues/1070
247    // https://github.com/cedar-policy/cedar/issues/1139
248    fn is_reserved_schema_keyword(id: &UnreservedId) -> bool {
249        matches!(
250            id.as_ref(),
251            "Bool" | "Boolean" | "Entity" | "Extension" | "Long" | "Record" | "Set" | "String"
252        )
253    }
254
255    /// Make a valid [`CommonTypeId`] from this [`UnreservedId`], modifying the
256    /// id if needed to avoid reserved basenames
257    #[cfg(feature = "arbitrary")]
258    fn make_into_valid_common_type_id(id: &UnreservedId) -> Self {
259        Self::new(id.clone()).unwrap_or_else(|_| {
260            // PANIC SAFETY: `_Bool`, `_Record`, and etc are valid unreserved names.
261            #[allow(clippy::unwrap_used)]
262            let new_id = format!("_{id}").parse().unwrap();
263            // PANIC SAFETY: `_Bool`, `_Record`, and etc are valid common type basenames.
264            #[allow(clippy::unwrap_used)]
265            Self::new(new_id).unwrap()
266        })
267    }
268}
269
270impl Display for CommonTypeId {
271    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272        self.0.fmt(f)
273    }
274}
275
276#[cfg(feature = "arbitrary")]
277impl<'a> arbitrary::Arbitrary<'a> for CommonTypeId {
278    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
279        let id: UnreservedId = u.arbitrary()?;
280        Ok(CommonTypeId::make_into_valid_common_type_id(&id))
281    }
282
283    fn size_hint(depth: usize) -> (usize, Option<usize>) {
284        <UnreservedId as arbitrary::Arbitrary>::size_hint(depth)
285    }
286}
287
288/// Deserialize a [`CommonTypeId`]
289impl<'de> Deserialize<'de> for CommonTypeId {
290    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
291    where
292        D: Deserializer<'de>,
293    {
294        UnreservedId::deserialize(deserializer).and_then(|id| {
295            CommonTypeId::new(id).map_err(|e| serde::de::Error::custom(format!("{e}")))
296        })
297    }
298}
299
300/// Error when a common-type basename is reserved
301#[derive(Debug, Error, PartialEq, Eq, Clone)]
302#[error("this is reserved and cannot be the basename of a common-type declaration: {id}")]
303pub struct ReservedCommonTypeBasenameError {
304    /// `id` that is a reserved common-type basename
305    pub(crate) id: UnreservedId,
306}
307
308/// A single namespace definition from a Fragment.
309/// This is composed of common types, entity types, and action definitions.
310///
311/// The parameter `N` is the type of entity type names and common type names in
312/// attributes/parents fields in this [`NamespaceDefinition`], including
313/// recursively. (It doesn't affect the type of common and entity type names
314/// _that are being declared here_, which is always an `UnreservedId` and unambiguously
315/// refers to the [`InternalName`] with the implicit current/active namespace prepended.)
316/// See notes on [`Fragment`].
317#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
318#[educe(PartialEq, Eq)]
319#[serde_as]
320#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
321#[serde(bound(serialize = "N: Serialize"))]
322#[serde(deny_unknown_fields)]
323#[serde(rename_all = "camelCase")]
324#[doc(hidden)]
325#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
326#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
327pub struct NamespaceDefinition<N> {
328    #[serde(default)]
329    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
330    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
331    pub common_types: BTreeMap<CommonTypeId, CommonType<N>>,
332    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
333    pub entity_types: BTreeMap<UnreservedId, EntityType<N>>,
334    #[serde(with = "::serde_with::rust::maps_duplicate_key_is_error")]
335    pub actions: BTreeMap<SmolStr, ActionType<N>>,
336    /// Annotations
337    #[serde(default)]
338    #[serde(skip_serializing_if = "Annotations::is_empty")]
339    pub annotations: Annotations,
340
341    #[cfg(feature = "extended-schema")]
342    #[serde(skip)]
343    #[educe(Eq(ignore))]
344    pub loc: Option<Loc>,
345}
346
347#[cfg(test)]
348impl<N> NamespaceDefinition<N> {
349    /// Create a new [`NamespaceDefinition`] with specified entity types and
350    /// actions, and no common types or annotations
351    pub fn new(
352        entity_types: impl IntoIterator<Item = (UnreservedId, EntityType<N>)>,
353        actions: impl IntoIterator<Item = (SmolStr, ActionType<N>)>,
354    ) -> Self {
355        Self {
356            common_types: BTreeMap::new(),
357            entity_types: entity_types.into_iter().collect(),
358            actions: actions.into_iter().collect(),
359            annotations: Annotations::new(),
360            #[cfg(feature = "extended-schema")]
361            loc: None,
362        }
363    }
364}
365
366impl NamespaceDefinition<RawName> {
367    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
368    pub fn conditionally_qualify_type_references(
369        self,
370        ns: Option<&InternalName>,
371    ) -> NamespaceDefinition<ConditionalName> {
372        NamespaceDefinition {
373            common_types: self
374                .common_types
375                .into_iter()
376                .map(|(k, v)| {
377                    (
378                        k,
379                        CommonType {
380                            ty: v.ty.conditionally_qualify_type_references(ns),
381                            annotations: v.annotations,
382                            loc: v.loc,
383                        },
384                    )
385                })
386                .collect(),
387            entity_types: self
388                .entity_types
389                .into_iter()
390                .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
391                .collect(),
392            actions: self
393                .actions
394                .into_iter()
395                .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
396                .collect(),
397            annotations: self.annotations,
398            #[cfg(feature = "extended-schema")]
399            loc: self.loc,
400        }
401    }
402}
403
404impl NamespaceDefinition<ConditionalName> {
405    /// Convert this [`NamespaceDefinition<ConditionalName>`] into a
406    /// [`NamespaceDefinition<InternalName>`] by fully-qualifying all typenames
407    /// that appear anywhere in any definitions.
408    ///
409    /// `all_defs` needs to contain the full set of all fully-qualified typenames
410    /// and actions that are defined in the schema (in all schema fragments).
411    pub fn fully_qualify_type_references(
412        self,
413        all_defs: &AllDefs,
414    ) -> Result<NamespaceDefinition<InternalName>> {
415        Ok(NamespaceDefinition {
416            common_types: self
417                .common_types
418                .into_iter()
419                .map(|(k, v)| {
420                    Ok((
421                        k,
422                        CommonType {
423                            ty: v.ty.fully_qualify_type_references(all_defs)?,
424                            annotations: v.annotations,
425                            loc: v.loc,
426                        },
427                    ))
428                })
429                .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
430            entity_types: self
431                .entity_types
432                .into_iter()
433                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
434                .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
435            actions: self
436                .actions
437                .into_iter()
438                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
439                .collect::<Result<_>>()?,
440            annotations: self.annotations,
441            #[cfg(feature = "extended-schema")]
442            loc: self.loc,
443        })
444    }
445}
446
447/// The kind of entity type. There are currently two kinds: The standard entity
448/// type specified by [`StandardEntityType`] and the enumerated entity type
449/// proposed by RFC 53
450#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
451#[serde(untagged)]
452#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
453#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
454pub enum EntityTypeKind<N> {
455    /// The standard entity type specified by [`StandardEntityType`]
456    Standard(StandardEntityType<N>),
457    /// The enumerated entity type: An entity type that can only have a
458    /// nonempty set of possible EIDs
459    Enum {
460        #[serde(rename = "enum")]
461        /// The nonempty set of possible EIDs
462        choices: NonEmpty<SmolStr>,
463    },
464}
465
466/// Represents the full definition of an entity type in the schema.
467/// Entity types describe the relationships in the entity store, including what
468/// entities can be members of groups of what types, and what attributes
469/// can/should be included on entities of each type.
470///
471/// The parameter `N` is the type of entity type names and common type names in
472/// this [`EntityType`], including recursively.
473/// See notes on [`Fragment`].
474#[derive(Educe, Debug, Clone, Serialize)]
475#[educe(PartialEq, Eq)]
476#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
477pub struct EntityType<N> {
478    /// The referred type
479    #[serde(flatten)]
480    pub kind: EntityTypeKind<N>,
481    /// Annotations
482    #[serde(default)]
483    #[serde(skip_serializing_if = "Annotations::is_empty")]
484    pub annotations: Annotations,
485    /// Source location
486    ///
487    /// (As of this writing, this is not populated when parsing from JSON.
488    /// It is only populated if constructing this structure from the
489    /// corresponding Cedar-syntax structure.)
490    #[serde(skip)]
491    #[educe(PartialEq(ignore))]
492    pub loc: Option<Loc>,
493}
494
495impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for EntityType<N> {
496    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
497    where
498        D: serde::Deserializer<'de>,
499    {
500        // A "real" option that does not accept `null` during deserialization
501        enum RealOption<T> {
502            Some(T),
503            None,
504        }
505        impl<'de, T: Deserialize<'de>> Deserialize<'de> for RealOption<T> {
506            fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
507            where
508                D: Deserializer<'de>,
509            {
510                T::deserialize(deserializer).map(Self::Some)
511            }
512        }
513        impl<T> Default for RealOption<T> {
514            fn default() -> Self {
515                Self::None
516            }
517        }
518
519        impl<T> From<RealOption<T>> for Option<T> {
520            fn from(value: RealOption<T>) -> Self {
521                match value {
522                    RealOption::Some(v) => Self::Some(v),
523                    RealOption::None => None,
524                }
525            }
526        }
527
528        // A struct that contains all possible fields of entity type
529        // I tried to apply the same idea to `EntityTypeKind` but serde allows
530        // unknown fields
531        #[derive(Deserialize)]
532        #[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
533        #[serde(deny_unknown_fields)]
534        #[serde(rename_all = "camelCase")]
535        struct Everything<N> {
536            #[serde(default)]
537            member_of_types: RealOption<Vec<N>>,
538            #[serde(default)]
539            shape: RealOption<AttributesOrContext<N>>,
540            #[serde(default)]
541            tags: RealOption<Type<N>>,
542            #[serde(default)]
543            #[serde(rename = "enum")]
544            choices: RealOption<NonEmpty<SmolStr>>,
545            #[serde(default)]
546            annotations: Annotations,
547        }
548
549        let value: Everything<N> = Everything::deserialize(deserializer)?;
550        // We favor the "enum" key here. That is, when we observe this key, we
551        // assume the entity type is an enumerated one and hence reports fields
552        // of standard entity types as invalid.
553        if let Some(choices) = value.choices.into() {
554            let mut unexpected_fields: Vec<&str> = vec![];
555            if Option::<Vec<N>>::from(value.member_of_types).is_some() {
556                unexpected_fields.push("memberOfTypes");
557            }
558            if Option::<AttributesOrContext<N>>::from(value.shape).is_some() {
559                unexpected_fields.push("shape");
560            }
561            if Option::<Type<N>>::from(value.tags).is_some() {
562                unexpected_fields.push("tags");
563            }
564            if !unexpected_fields.is_empty() {
565                return Err(serde::de::Error::custom(format!(
566                    "unexpected field: {}",
567                    unexpected_fields.into_iter().join(", ")
568                )));
569            }
570            Ok(EntityType {
571                kind: EntityTypeKind::Enum { choices },
572                annotations: value.annotations,
573                loc: None,
574            })
575        } else {
576            Ok(EntityType {
577                kind: EntityTypeKind::Standard(StandardEntityType {
578                    member_of_types: Option::from(value.member_of_types).unwrap_or_default(),
579                    shape: Option::from(value.shape).unwrap_or_default(),
580                    tags: Option::from(value.tags),
581                }),
582                annotations: value.annotations,
583                loc: None,
584            })
585        }
586    }
587}
588
589/// The "standard" entity type. That is, an entity type defined by parent
590/// entity types, shape, and tags.
591#[derive(Debug, Clone, Serialize, Deserialize, Educe)]
592#[educe(PartialEq, Eq)]
593#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
594#[serde(rename_all = "camelCase")]
595#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
596#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
597pub struct StandardEntityType<N> {
598    /// Entities of this [`StandardEntityType`] are allowed to be members of entities of
599    /// these types.
600    #[serde(skip_serializing_if = "Vec::is_empty")]
601    #[serde(default)]
602    pub member_of_types: Vec<N>,
603    /// Description of the attributes for entities of this [`StandardEntityType`].
604    #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
605    #[serde(default)]
606    pub shape: AttributesOrContext<N>,
607    /// Tag type for entities of this [`StandardEntityType`]; `None` means entities of this [`StandardEntityType`] do not have tags.
608    #[serde(skip_serializing_if = "Option::is_none")]
609    #[serde(default)]
610    pub tags: Option<Type<N>>,
611}
612
613#[cfg(test)]
614impl<N> From<StandardEntityType<N>> for EntityType<N> {
615    fn from(value: StandardEntityType<N>) -> Self {
616        Self {
617            kind: EntityTypeKind::Standard(value),
618            annotations: Annotations::new(),
619            loc: None,
620        }
621    }
622}
623
624impl EntityType<RawName> {
625    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
626    pub fn conditionally_qualify_type_references(
627        self,
628        ns: Option<&InternalName>,
629    ) -> EntityType<ConditionalName> {
630        let Self {
631            kind,
632            annotations,
633            loc,
634        } = self;
635        match kind {
636            EntityTypeKind::Enum { choices } => EntityType {
637                kind: EntityTypeKind::Enum { choices },
638                annotations,
639                loc,
640            },
641            EntityTypeKind::Standard(ty) => EntityType {
642                kind: EntityTypeKind::Standard(StandardEntityType {
643                    member_of_types: ty
644                        .member_of_types
645                        .into_iter()
646                        .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) // Only entity, not common, here for now; see #1064
647                        .collect(),
648                    shape: ty.shape.conditionally_qualify_type_references(ns),
649                    tags: ty
650                        .tags
651                        .map(|ty| ty.conditionally_qualify_type_references(ns)),
652                }),
653                annotations,
654                loc,
655            },
656        }
657    }
658}
659
660impl EntityType<ConditionalName> {
661    /// Convert this [`EntityType<ConditionalName>`] into an
662    /// [`EntityType<InternalName>`] by fully-qualifying all typenames that
663    /// appear anywhere in any definitions.
664    ///
665    /// `all_defs` needs to contain the full set of all fully-qualified typenames
666    /// and actions that are defined in the schema (in all schema fragments).
667    pub fn fully_qualify_type_references(
668        self,
669        all_defs: &AllDefs,
670    ) -> std::result::Result<EntityType<InternalName>, TypeNotDefinedError> {
671        let Self {
672            kind,
673            annotations,
674            loc,
675        } = self;
676        Ok(match kind {
677            EntityTypeKind::Enum { choices } => EntityType {
678                kind: EntityTypeKind::Enum { choices },
679                annotations,
680                loc,
681            },
682            EntityTypeKind::Standard(ty) => EntityType {
683                kind: EntityTypeKind::Standard(StandardEntityType {
684                    member_of_types: ty
685                        .member_of_types
686                        .into_iter()
687                        .map(|cname| cname.resolve(all_defs))
688                        .collect::<std::result::Result<_, _>>()?,
689                    shape: ty.shape.fully_qualify_type_references(all_defs)?,
690                    tags: ty
691                        .tags
692                        .map(|ty| ty.fully_qualify_type_references(all_defs))
693                        .transpose()?,
694                }),
695                annotations,
696                loc,
697            },
698        })
699    }
700}
701
702/// Declaration of entity or record attributes, or of an action context.
703/// These share a JSON format.
704///
705/// The parameter `N` is the type of entity type names and common type names in
706/// this [`AttributesOrContext`], including recursively.
707/// See notes on [`Fragment`].
708#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
709#[educe(PartialEq, Eq)]
710#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
711#[serde(transparent)]
712#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
713#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
714pub struct AttributesOrContext<N>(
715    // We use the usual `Type` deserialization, but it will ultimately need to
716    // be a `Record` or common-type reference which resolves to a `Record`.
717    pub Type<N>,
718);
719
720impl<N> AttributesOrContext<N> {
721    /// Convert the [`AttributesOrContext`] into its [`Type`].
722    pub fn into_inner(self) -> Type<N> {
723        self.0
724    }
725
726    /// Is this `AttributesOrContext` an empty record?
727    pub fn is_empty_record(&self) -> bool {
728        self.0.is_empty_record()
729    }
730
731    /// Get the source location of this `AttributesOrContext`
732    pub fn loc(&self) -> Option<&Loc> {
733        self.0.loc()
734    }
735}
736
737impl<N> Default for AttributesOrContext<N> {
738    fn default() -> Self {
739        Self::from(RecordType::default())
740    }
741}
742
743impl<N: Display> Display for AttributesOrContext<N> {
744    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
745        self.0.fmt(f)
746    }
747}
748
749impl<N> From<RecordType<N>> for AttributesOrContext<N> {
750    fn from(rty: RecordType<N>) -> AttributesOrContext<N> {
751        Self(Type::Type {
752            ty: TypeVariant::Record(rty),
753            loc: None,
754        })
755    }
756}
757
758impl AttributesOrContext<RawName> {
759    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
760    pub fn conditionally_qualify_type_references(
761        self,
762        ns: Option<&InternalName>,
763    ) -> AttributesOrContext<ConditionalName> {
764        AttributesOrContext(self.0.conditionally_qualify_type_references(ns))
765    }
766}
767
768impl AttributesOrContext<ConditionalName> {
769    /// Convert this [`AttributesOrContext<ConditionalName>`] into an
770    /// [`AttributesOrContext<InternalName>`] by fully-qualifying all typenames
771    /// that appear anywhere in any definitions.
772    ///
773    /// `all_defs` needs to contain the full set of all fully-qualified typenames
774    /// and actions that are defined in the schema (in all schema fragments).
775    pub fn fully_qualify_type_references(
776        self,
777        all_defs: &AllDefs,
778    ) -> std::result::Result<AttributesOrContext<InternalName>, TypeNotDefinedError> {
779        Ok(AttributesOrContext(
780            self.0.fully_qualify_type_references(all_defs)?,
781        ))
782    }
783}
784
785/// An [`ActionType`] describes a specific action entity.
786/// It also describes what principals/resources/contexts are valid for the
787/// action.
788///
789/// The parameter `N` is the type of entity type names and common type names in
790/// this [`ActionType`], including recursively.
791/// See notes on [`Fragment`].
792#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
793#[educe(PartialEq, Eq)]
794#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
795#[serde(deny_unknown_fields)]
796#[serde(rename_all = "camelCase")]
797#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
798#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
799pub struct ActionType<N> {
800    /// Vestigial attributes left in place to avoid any possibly breaking change
801    /// to schema parsing. Providing anything other than `None` will result in an error.
802    #[serde(default)]
803    #[serde(skip_serializing_if = "Option::is_none")]
804    pub attributes: Option<HashMap<SmolStr, CedarValueJson>>,
805    /// Describes what principals/resources/contexts are valid for this action.
806    #[serde(default)]
807    #[serde(skip_serializing_if = "Option::is_none")]
808    pub applies_to: Option<ApplySpec<N>>,
809    /// Which actions are parents of this action.
810    #[serde(default)]
811    #[serde(skip_serializing_if = "Option::is_none")]
812    pub member_of: Option<Vec<ActionEntityUID<N>>>,
813    /// Annotations
814    #[serde(default)]
815    #[serde(skip_serializing_if = "Annotations::is_empty")]
816    pub annotations: Annotations,
817    /// Source location of the whole type
818    ///
819    /// (As of this writing, this is not populated when parsing from JSON.
820    /// It is only populated if constructing this structure from the
821    /// corresponding Cedar-syntax structure.)
822    #[serde(skip)]
823    #[educe(PartialEq(ignore))]
824    pub loc: Option<Loc>,
825
826    /// Source location of only the action definition
827    #[cfg(feature = "extended-schema")]
828    #[serde(skip)]
829    #[educe(PartialEq(ignore))]
830    pub(crate) defn_loc: Option<Loc>,
831}
832
833impl ActionType<RawName> {
834    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
835    pub fn conditionally_qualify_type_references(
836        self,
837        ns: Option<&InternalName>,
838    ) -> ActionType<ConditionalName> {
839        ActionType {
840            attributes: self.attributes,
841            applies_to: self
842                .applies_to
843                .map(|applyspec| applyspec.conditionally_qualify_type_references(ns)),
844            member_of: self.member_of.map(|v| {
845                v.into_iter()
846                    .map(|aeuid| aeuid.conditionally_qualify_type_references(ns))
847                    .collect()
848            }),
849            annotations: self.annotations,
850            loc: self.loc,
851            #[cfg(feature = "extended-schema")]
852            defn_loc: self.defn_loc,
853        }
854    }
855}
856
857impl ActionType<ConditionalName> {
858    /// Convert this [`ActionType<ConditionalName>`] into an
859    /// [`ActionType<InternalName>`] by fully-qualifying all typenames that
860    /// appear anywhere in any definitions.
861    ///
862    /// `all_defs` needs to contain the full set of all fully-qualified typenames
863    /// and actions that are defined in the schema (in all schema fragments).
864    pub fn fully_qualify_type_references(
865        self,
866        all_defs: &AllDefs,
867    ) -> Result<ActionType<InternalName>> {
868        Ok(ActionType {
869            attributes: self.attributes,
870            applies_to: self
871                .applies_to
872                .map(|applyspec| applyspec.fully_qualify_type_references(all_defs))
873                .transpose()?,
874            member_of: self
875                .member_of
876                .map(|v| {
877                    v.into_iter()
878                        .map(|aeuid| aeuid.fully_qualify_type_references(all_defs))
879                        .collect::<std::result::Result<_, ActionNotDefinedError>>()
880                })
881                .transpose()?,
882            annotations: self.annotations,
883            loc: self.loc,
884            #[cfg(feature = "extended-schema")]
885            defn_loc: self.defn_loc,
886        })
887    }
888}
889
890/// The apply spec specifies what principals and resources an action can be used
891/// with.  This specification can either be done through containing to entity
892/// types.
893/// An empty list is interpreted as specifying that there are no principals or
894/// resources that an action applies to.
895///
896/// The parameter `N` is the type of entity type names and common type names in
897/// this [`ApplySpec`], including recursively.
898/// See notes on [`Fragment`].
899#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
900#[educe(PartialEq, Eq)]
901#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
902#[serde(deny_unknown_fields)]
903#[serde(rename_all = "camelCase")]
904#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
905#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
906pub struct ApplySpec<N> {
907    /// Resource types that are valid for the action
908    pub resource_types: Vec<N>,
909    /// Principal types that are valid for the action
910    pub principal_types: Vec<N>,
911    /// Context type that this action expects
912    #[serde(default)]
913    #[serde(skip_serializing_if = "AttributesOrContext::is_empty_record")]
914    pub context: AttributesOrContext<N>,
915}
916
917impl ApplySpec<RawName> {
918    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
919    pub fn conditionally_qualify_type_references(
920        self,
921        ns: Option<&InternalName>,
922    ) -> ApplySpec<ConditionalName> {
923        ApplySpec {
924            resource_types: self
925                .resource_types
926                .into_iter()
927                .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) // Only entity, not common, here for now; see #1064
928                .collect(),
929            principal_types: self
930                .principal_types
931                .into_iter()
932                .map(|rname| rname.conditionally_qualify_with(ns, ReferenceType::Entity)) // Only entity, not common, here for now; see #1064
933                .collect(),
934            context: self.context.conditionally_qualify_type_references(ns),
935        }
936    }
937}
938
939impl ApplySpec<ConditionalName> {
940    /// Convert this [`ApplySpec<ConditionalName>`] into an
941    /// [`ApplySpec<InternalName>`] by fully-qualifying all typenames that
942    /// appear anywhere in any definitions.
943    ///
944    /// `all_defs` needs to contain the full set of all fully-qualified typenames
945    /// and actions that are defined in the schema (in all schema fragments).
946    pub fn fully_qualify_type_references(
947        self,
948        all_defs: &AllDefs,
949    ) -> std::result::Result<ApplySpec<InternalName>, TypeNotDefinedError> {
950        Ok(ApplySpec {
951            resource_types: self
952                .resource_types
953                .into_iter()
954                .map(|cname| cname.resolve(all_defs))
955                .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
956            principal_types: self
957                .principal_types
958                .into_iter()
959                .map(|cname| cname.resolve(all_defs))
960                .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
961            context: self.context.fully_qualify_type_references(all_defs)?,
962        })
963    }
964}
965
966/// Represents the [`crate::ast::EntityUID`] of an action
967#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
968#[educe(PartialEq, Eq, Hash)]
969#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
970#[serde(deny_unknown_fields)]
971#[serde(rename_all = "camelCase")]
972#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
973#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
974pub struct ActionEntityUID<N> {
975    /// Represents the [`crate::ast::Eid`] of the action
976    pub id: SmolStr,
977
978    /// Represents the type of the action.
979    /// `None` is shorthand for `Action`.
980    /// If this is `Some`, the last component of the `N` should be `Action`.
981    ///
982    /// INVARIANT: This can only be `None` in the `N` = `RawName` case.
983    /// This invariant is upheld by all the code below that constructs
984    /// `ActionEntityUID`.
985    /// We also rely on `ActionEntityUID<N>` only being `Deserialize` for
986    /// `N` = `RawName`, so that you can't create an `ActionEntityUID` that
987    /// violates this invariant via deserialization.
988    #[serde(rename = "type")]
989    #[serde(default)]
990    #[serde(skip_serializing_if = "Option::is_none")]
991    pub ty: Option<N>,
992    #[cfg(feature = "extended-schema")]
993    #[serde(skip)]
994    /// Source location - if available
995    pub loc: Option<Loc>,
996}
997
998impl ActionEntityUID<RawName> {
999    /// Create a new `ActionEntityUID<RawName>`.
1000    /// `ty` = `None` is shorthand for `Action`.
1001    pub fn new(ty: Option<RawName>, id: SmolStr) -> Self {
1002        Self {
1003            id,
1004            ty,
1005            #[cfg(feature = "extended-schema")]
1006            loc: None,
1007        }
1008    }
1009
1010    /// Given an `id`, get the [`ActionEntityUID`] representing `Action::<id>`.
1011    //
1012    // This function is only available for `RawName` and not other values of `N`,
1013    // in order to uphold the INVARIANT on self.ty.
1014    pub fn default_type(id: SmolStr) -> Self {
1015        Self {
1016            id,
1017            ty: None,
1018            #[cfg(feature = "extended-schema")]
1019            loc: None,
1020        }
1021    }
1022
1023    /// Given an `id`, get the [`ActionEntityUID`] representing `Action::<id>`.
1024    //
1025    // This function is only available for `RawName` and not other values of `N`,
1026    // in order to uphold the INVARIANT on self.ty.
1027    #[cfg(feature = "extended-schema")]
1028    pub fn default_type_with_loc(id: SmolStr, loc: Option<Loc>) -> Self {
1029        Self { id, ty: None, loc }
1030    }
1031}
1032
1033impl<N: std::fmt::Display> std::fmt::Display for ActionEntityUID<N> {
1034    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1035        if let Some(ty) = &self.ty {
1036            write!(f, "{ty}::")?
1037        } else {
1038            write!(f, "Action::")?
1039        }
1040        write!(f, "\"{}\"", self.id.escape_debug())
1041    }
1042}
1043
1044impl ActionEntityUID<RawName> {
1045    /// (Conditionally) prefix this action entity UID's typename with the given namespace
1046    pub fn conditionally_qualify_type_references(
1047        self,
1048        ns: Option<&InternalName>,
1049    ) -> ActionEntityUID<ConditionalName> {
1050        // Upholding the INVARIANT on ActionEntityUID.ty: constructing an `ActionEntityUID<ConditionalName>`,
1051        // so in the constructed `ActionEntityUID`, `.ty` must be `Some` in all cases
1052        ActionEntityUID {
1053            id: self.id,
1054            ty: {
1055                // PANIC SAFETY: this is a valid raw name
1056                #[allow(clippy::expect_used)]
1057                let raw_name = self
1058                    .ty
1059                    .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1060                Some(raw_name.conditionally_qualify_with(ns, ReferenceType::Entity))
1061            },
1062            #[cfg(feature = "extended-schema")]
1063            loc: None,
1064        }
1065    }
1066
1067    /// Unconditionally prefix this action entity UID's typename with the given namespace
1068    pub fn qualify_with(self, ns: Option<&InternalName>) -> ActionEntityUID<InternalName> {
1069        // Upholding the INVARIANT on ActionEntityUID.ty: constructing an `ActionEntityUID<InternalName>`,
1070        // so in the constructed `ActionEntityUID`, `.ty` must be `Some` in all cases
1071        ActionEntityUID {
1072            id: self.id,
1073            ty: {
1074                // PANIC SAFETY: this is a valid raw name
1075                #[allow(clippy::expect_used)]
1076                let raw_name = self
1077                    .ty
1078                    .unwrap_or_else(|| RawName::from_str("Action").expect("valid raw name"));
1079                Some(raw_name.qualify_with(ns))
1080            },
1081            #[cfg(feature = "extended-schema")]
1082            loc: self.loc,
1083        }
1084    }
1085}
1086
1087impl ActionEntityUID<ConditionalName> {
1088    /// Get the action type, as a [`ConditionalName`].
1089    pub fn ty(&self) -> &ConditionalName {
1090        // PANIC SAFETY: by INVARIANT on self.ty
1091        #[allow(clippy::expect_used)]
1092        self.ty.as_ref().expect("by INVARIANT on self.ty")
1093    }
1094
1095    /// Convert this [`ActionEntityUID<ConditionalName>`] into an
1096    /// [`ActionEntityUID<InternalName>`] by fully-qualifying its typename.
1097    ///
1098    /// `all_defs` needs to contain the full set of all fully-qualified typenames
1099    /// and actions that are defined in the schema (in all schema fragments).
1100    /// This `ActionEntityUID<ConditionalName>` must resolve to something defined
1101    /// in `all_defs` or else it throws [`ActionNotDefinedError`].
1102    pub fn fully_qualify_type_references(
1103        self,
1104        all_defs: &AllDefs,
1105    ) -> std::result::Result<ActionEntityUID<InternalName>, ActionNotDefinedError> {
1106        for possibility in self.possibilities() {
1107            // This ignores any possibilities that aren't valid `EntityUID`,
1108            // because we know that all defined actions are valid `EntityUID`s
1109            // (because `all_action_defs` has type `&HashSet<EntityUID>`).
1110            if let Ok(euid) = EntityUID::try_from(possibility.clone()) {
1111                if all_defs.is_defined_as_action(&euid) {
1112                    return Ok(possibility);
1113                }
1114            }
1115        }
1116        Err(ActionNotDefinedError(nonempty!(self)))
1117    }
1118
1119    /// Get the possible fully-qualified [`ActionEntityUID<InternalName>`]s
1120    /// which this [`ActionEntityUID<ConditionalName>`] might resolve to, in
1121    /// priority order (highest-priority first).
1122    pub(crate) fn possibilities(&self) -> impl Iterator<Item = ActionEntityUID<InternalName>> + '_ {
1123        // Upholding the INVARIANT on ActionEntityUID.ty: constructing `ActionEntityUID<InternalName>`,
1124        // so in the constructed `ActionEntityUID`, `.ty` must be `Some` in all cases
1125        self.ty()
1126            .possibilities()
1127            .map(|possibility| ActionEntityUID {
1128                id: self.id.clone(),
1129                ty: Some(possibility.clone()),
1130                #[cfg(feature = "extended-schema")]
1131                loc: None,
1132            })
1133    }
1134
1135    /// Convert this [`ActionEntityUID<ConditionalName>`] back into a [`ActionEntityUID<RawName>`].
1136    /// As of this writing, [`ActionEntityUID<RawName>`] has a `Display` impl while
1137    /// [`ActionEntityUID<ConditionalName>`] does not.
1138    pub(crate) fn as_raw(&self) -> ActionEntityUID<RawName> {
1139        ActionEntityUID {
1140            id: self.id.clone(),
1141            ty: self.ty.as_ref().map(|ty| ty.raw().clone()),
1142            #[cfg(feature = "extended-schema")]
1143            loc: None,
1144        }
1145    }
1146}
1147
1148impl ActionEntityUID<Name> {
1149    /// Get the action type, as a [`Name`].
1150    pub fn ty(&self) -> &Name {
1151        // PANIC SAFETY: by INVARIANT on self.ty
1152        #[allow(clippy::expect_used)]
1153        self.ty.as_ref().expect("by INVARIANT on self.ty")
1154    }
1155}
1156
1157impl ActionEntityUID<InternalName> {
1158    /// Get the action type, as an [`InternalName`].
1159    pub fn ty(&self) -> &InternalName {
1160        // PANIC SAFETY: by INVARIANT on self.ty
1161        #[allow(clippy::expect_used)]
1162        self.ty.as_ref().expect("by INVARIANT on self.ty")
1163    }
1164}
1165
1166impl From<ActionEntityUID<Name>> for EntityUID {
1167    fn from(aeuid: ActionEntityUID<Name>) -> Self {
1168        EntityUID::from_components(aeuid.ty().clone().into(), Eid::new(aeuid.id), None)
1169    }
1170}
1171
1172impl TryFrom<ActionEntityUID<InternalName>> for EntityUID {
1173    type Error = <InternalName as TryInto<Name>>::Error;
1174    fn try_from(
1175        aeuid: ActionEntityUID<InternalName>,
1176    ) -> std::result::Result<Self, <InternalName as TryInto<Name>>::Error> {
1177        let ty = Name::try_from(aeuid.ty().clone())?;
1178        #[cfg(feature = "extended-schema")]
1179        let loc = aeuid.loc;
1180        #[cfg(not(feature = "extended-schema"))]
1181        let loc = None;
1182        Ok(EntityUID::from_components(
1183            ty.into(),
1184            Eid::new(aeuid.id),
1185            loc,
1186        ))
1187    }
1188}
1189
1190impl From<EntityUID> for ActionEntityUID<Name> {
1191    fn from(euid: EntityUID) -> Self {
1192        let (ty, id) = euid.components();
1193        ActionEntityUID {
1194            ty: Some(ty.into()),
1195            id: id.into_smolstr(),
1196            #[cfg(feature = "extended-schema")]
1197            loc: None,
1198        }
1199    }
1200}
1201
1202/// A restricted version of the [`crate::validator::types::Type`] enum containing only the types
1203/// which are exposed to users.
1204///
1205/// The parameter `N` is the type of entity type names and common type names in
1206/// this [`Type`], including recursively.
1207/// See notes on [`Fragment`].
1208#[derive(Educe, Debug, Clone, Serialize)]
1209#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1210// This enum is `untagged` with these variants as a workaround to a serde
1211// limitation. It is not possible to have the known variants on one enum, and
1212// then, have catch-all variant for any unrecognized tag in the same enum that
1213// captures the name of the unrecognized tag.
1214#[serde(untagged)]
1215#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1216#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1217pub enum Type<N> {
1218    /// One of the standard types exposed to users.
1219    ///
1220    /// This branch also includes the "entity-or-common-type-reference" possibility.
1221    Type {
1222        /// The type
1223        #[serde(flatten)]
1224        ty: TypeVariant<N>,
1225        /// Source location
1226        ///
1227        /// (As of this writing, this is not populated when parsing from JSON.
1228        /// It is only populated if constructing this structure from the
1229        /// corresponding Cedar-syntax structure.)
1230        #[serde(skip)]
1231        #[educe(PartialEq(ignore))]
1232        #[educe(PartialOrd(ignore))]
1233        loc: Option<Loc>,
1234    },
1235    /// Reference to a common type
1236    ///
1237    /// This is only used for references that _must_ resolve to common types.
1238    /// References that may resolve to either common or entity types can use
1239    /// `Type::Type(TypeVariant::EntityOrCommon)`.
1240    CommonTypeRef {
1241        /// Name of the common type.
1242        /// For the important case of `N` = [`RawName`], this is the schema JSON
1243        /// format, and the `RawName` is exactly how it appears in the schema;
1244        /// may not yet be fully qualified
1245        #[serde(rename = "type")]
1246        type_name: N,
1247        /// Source location
1248        ///
1249        /// (As of this writing, this is not populated when parsing from JSON.
1250        /// It is only populated if constructing this structure from the
1251        /// corresponding Cedar-syntax structure.)
1252        #[serde(skip)]
1253        #[educe(PartialEq(ignore))]
1254        #[educe(PartialOrd(ignore))]
1255        loc: Option<Loc>,
1256    },
1257}
1258
1259impl<N> Type<N> {
1260    /// Iterate over all references which occur in the type and (must or may)
1261    /// resolve to a common type
1262    pub(crate) fn common_type_references(&self) -> Box<dyn Iterator<Item = &N> + '_> {
1263        match self {
1264            Type::Type {
1265                ty: TypeVariant::Record(RecordType { attributes, .. }),
1266                ..
1267            } => attributes
1268                .values()
1269                .map(|ty| ty.ty.common_type_references())
1270                .fold(Box::new(std::iter::empty()), |it, tys| {
1271                    Box::new(it.chain(tys))
1272                }),
1273            Type::Type {
1274                ty: TypeVariant::Set { element },
1275                ..
1276            } => element.common_type_references(),
1277            Type::Type {
1278                ty: TypeVariant::EntityOrCommon { type_name },
1279                ..
1280            } => Box::new(std::iter::once(type_name)),
1281            Type::CommonTypeRef { type_name, .. } => Box::new(std::iter::once(type_name)),
1282            _ => Box::new(std::iter::empty()),
1283        }
1284    }
1285
1286    /// Is this [`Type`] an extension type, or does it contain one
1287    /// (recursively)? Returns `None` if this is a `CommonTypeRef` or
1288    /// `EntityOrCommon` because we can't easily check the type of a common type
1289    /// reference, accounting for namespaces, without first converting to a
1290    /// [`crate::validator::types::Type`].
1291    pub fn is_extension(&self) -> Option<bool> {
1292        match self {
1293            Self::Type {
1294                ty: TypeVariant::Extension { .. },
1295                ..
1296            } => Some(true),
1297            Self::Type {
1298                ty: TypeVariant::Set { element },
1299                ..
1300            } => element.is_extension(),
1301            Self::Type {
1302                ty: TypeVariant::Record(RecordType { attributes, .. }),
1303                ..
1304            } => attributes
1305                .values()
1306                .try_fold(false, |a, e| match e.ty.is_extension() {
1307                    Some(true) => Some(true),
1308                    Some(false) => Some(a),
1309                    None => None,
1310                }),
1311            Self::Type { .. } => Some(false),
1312            Self::CommonTypeRef { .. } => None,
1313        }
1314    }
1315
1316    /// Is this [`Type`] an empty record? This function is used by the `Display`
1317    /// implementation to avoid printing unnecessary entity/action data.
1318    pub fn is_empty_record(&self) -> bool {
1319        match self {
1320            Self::Type {
1321                ty: TypeVariant::Record(rty),
1322                ..
1323            } => rty.is_empty_record(),
1324            _ => false,
1325        }
1326    }
1327
1328    /// Get the source location of this [`Type`]
1329    pub fn loc(&self) -> Option<&Loc> {
1330        match self {
1331            Self::Type { loc, .. } => loc.as_ref(),
1332            Self::CommonTypeRef { loc, .. } => loc.as_ref(),
1333        }
1334    }
1335
1336    /// Create a new copy of self but with a difference source location
1337    pub fn with_loc(self, new_loc: Option<Loc>) -> Self {
1338        match self {
1339            Self::Type { ty, loc: _loc } => Self::Type { ty, loc: new_loc },
1340            Self::CommonTypeRef {
1341                type_name,
1342                loc: _loc,
1343            } => Self::CommonTypeRef {
1344                type_name,
1345                loc: new_loc,
1346            },
1347        }
1348    }
1349}
1350
1351impl Type<RawName> {
1352    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
1353    pub fn conditionally_qualify_type_references(
1354        self,
1355        ns: Option<&InternalName>,
1356    ) -> Type<ConditionalName> {
1357        match self {
1358            Self::Type { ty, loc } => Type::Type {
1359                ty: ty.conditionally_qualify_type_references(ns),
1360                loc,
1361            },
1362            Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1363                type_name: type_name.conditionally_qualify_with(ns, ReferenceType::Common),
1364                loc,
1365            },
1366        }
1367    }
1368
1369    fn into_n<N: From<RawName>>(self) -> Type<N> {
1370        match self {
1371            Self::Type { ty, loc } => Type::Type {
1372                ty: ty.into_n(),
1373                loc,
1374            },
1375            Self::CommonTypeRef { type_name, loc } => Type::CommonTypeRef {
1376                type_name: type_name.into(),
1377                loc,
1378            },
1379        }
1380    }
1381}
1382
1383impl Type<ConditionalName> {
1384    /// Convert this [`Type<ConditionalName>`] into a [`Type<InternalName>`] by
1385    /// fully-qualifying all typenames that appear anywhere in any definitions.
1386    ///
1387    /// `all_defs` needs to contain the full set of all fully-qualified typenames
1388    /// and actions that are defined in the schema (in all schema fragments).
1389    pub fn fully_qualify_type_references(
1390        self,
1391        all_defs: &AllDefs,
1392    ) -> std::result::Result<Type<InternalName>, TypeNotDefinedError> {
1393        match self {
1394            Self::Type { ty, loc } => Ok(Type::Type {
1395                ty: ty.fully_qualify_type_references(all_defs)?,
1396                loc,
1397            }),
1398            Self::CommonTypeRef { type_name, loc } => Ok(Type::CommonTypeRef {
1399                type_name: type_name.resolve(all_defs)?,
1400                loc,
1401            }),
1402        }
1403    }
1404}
1405
1406impl<'de, N: Deserialize<'de> + From<RawName>> Deserialize<'de> for Type<N> {
1407    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1408    where
1409        D: serde::Deserializer<'de>,
1410    {
1411        deserializer.deserialize_any(TypeVisitor {
1412            _phantom: PhantomData,
1413        })
1414    }
1415}
1416
1417/// The fields for a `Type`. Used for implementing deserialization.
1418#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize)]
1419#[serde(field_identifier, rename_all = "camelCase")]
1420enum TypeFields {
1421    Type,
1422    Element,
1423    Attributes,
1424    AdditionalAttributes,
1425    Name,
1426}
1427
1428// This macro is used to avoid duplicating the fields names when calling
1429// `serde::de::Error::unknown_field`. It wants an `&'static [&'static str]`, and
1430// AFAIK, the elements of the static slice must be literals.
1431macro_rules! type_field_name {
1432    (Type) => {
1433        "type"
1434    };
1435    (Element) => {
1436        "element"
1437    };
1438    (Attributes) => {
1439        "attributes"
1440    };
1441    (AdditionalAttributes) => {
1442        "additionalAttributes"
1443    };
1444    (Name) => {
1445        "name"
1446    };
1447}
1448
1449impl TypeFields {
1450    fn as_str(&self) -> &'static str {
1451        match self {
1452            TypeFields::Type => type_field_name!(Type),
1453            TypeFields::Element => type_field_name!(Element),
1454            TypeFields::Attributes => type_field_name!(Attributes),
1455            TypeFields::AdditionalAttributes => type_field_name!(AdditionalAttributes),
1456            TypeFields::Name => type_field_name!(Name),
1457        }
1458    }
1459}
1460
1461/// Used during deserialization to deserialize the attributes type map while
1462/// reporting an error if there are any duplicate keys in the map. I could not
1463/// find a way to do the `serde_with` conversion inline without introducing this
1464/// struct.
1465#[derive(Debug, Deserialize)]
1466struct AttributesTypeMap(
1467    #[serde(with = "serde_with::rust::maps_duplicate_key_is_error")]
1468    BTreeMap<SmolStr, TypeOfAttribute<RawName>>,
1469);
1470
1471struct TypeVisitor<N> {
1472    _phantom: PhantomData<N>,
1473}
1474
1475impl<'de, N: Deserialize<'de> + From<RawName>> Visitor<'de> for TypeVisitor<N> {
1476    type Value = Type<N>;
1477
1478    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1479        formatter.write_str("builtin type or reference to type defined in commonTypes")
1480    }
1481
1482    fn visit_map<M>(self, mut map: M) -> std::result::Result<Self::Value, M::Error>
1483    where
1484        M: MapAccess<'de>,
1485    {
1486        use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1487
1488        let mut type_name: Option<SmolStr> = None;
1489        let mut element: Option<Type<N>> = None;
1490        let mut attributes: Option<AttributesTypeMap> = None;
1491        let mut additional_attributes: Option<bool> = None;
1492        let mut name: Option<SmolStr> = None;
1493
1494        // Gather all the fields in the object. Any fields that are not one of
1495        // the possible fields for some schema type will have been reported by
1496        // serde already.
1497        while let Some(key) = map.next_key()? {
1498            match key {
1499                TypeField => {
1500                    if type_name.is_some() {
1501                        return Err(serde::de::Error::duplicate_field(TypeField.as_str()));
1502                    }
1503                    type_name = Some(map.next_value()?);
1504                }
1505                Element => {
1506                    if element.is_some() {
1507                        return Err(serde::de::Error::duplicate_field(Element.as_str()));
1508                    }
1509                    element = Some(map.next_value()?);
1510                }
1511                Attributes => {
1512                    if attributes.is_some() {
1513                        return Err(serde::de::Error::duplicate_field(Attributes.as_str()));
1514                    }
1515                    attributes = Some(map.next_value()?);
1516                }
1517                AdditionalAttributes => {
1518                    if additional_attributes.is_some() {
1519                        return Err(serde::de::Error::duplicate_field(
1520                            AdditionalAttributes.as_str(),
1521                        ));
1522                    }
1523                    additional_attributes = Some(map.next_value()?);
1524                }
1525                Name => {
1526                    if name.is_some() {
1527                        return Err(serde::de::Error::duplicate_field(Name.as_str()));
1528                    }
1529                    name = Some(map.next_value()?);
1530                }
1531            }
1532        }
1533
1534        Self::build_schema_type::<M>(
1535            type_name.as_ref(),
1536            element,
1537            attributes,
1538            additional_attributes,
1539            name,
1540        )
1541    }
1542}
1543
1544impl<'de, N: Deserialize<'de> + From<RawName>> TypeVisitor<N> {
1545    /// Construct a schema type given the name of the type and its fields.
1546    /// Fields which were not present are `None`. It is an error for a field
1547    /// which is not used for a particular type to be `Some` when building that
1548    /// type.
1549    fn build_schema_type<M>(
1550        type_name: Option<&SmolStr>,
1551        element: Option<Type<N>>,
1552        attributes: Option<AttributesTypeMap>,
1553        additional_attributes: Option<bool>,
1554        name: Option<SmolStr>,
1555    ) -> std::result::Result<Type<N>, M::Error>
1556    where
1557        M: MapAccess<'de>,
1558    {
1559        use TypeFields::{AdditionalAttributes, Attributes, Element, Name, Type as TypeField};
1560        // Fields that remain to be parsed
1561        let mut remaining_fields = [
1562            (TypeField, type_name.is_some()),
1563            (Element, element.is_some()),
1564            (Attributes, attributes.is_some()),
1565            (AdditionalAttributes, additional_attributes.is_some()),
1566            (Name, name.is_some()),
1567        ]
1568        .into_iter()
1569        .filter(|(_, present)| *present)
1570        .map(|(field, _)| field)
1571        .collect::<HashSet<_>>();
1572
1573        match type_name {
1574            Some(s) => {
1575                // We've concluded that type exists
1576                remaining_fields.remove(&TypeField);
1577                // Used to generate the appropriate serde error if a field is present
1578                // when it is not expected.
1579                let error_if_fields = |fs: &[TypeFields],
1580                                       expected: &'static [&'static str]|
1581                 -> std::result::Result<(), M::Error> {
1582                    for f in fs {
1583                        if remaining_fields.contains(f) {
1584                            return Err(serde::de::Error::unknown_field(f.as_str(), expected));
1585                        }
1586                    }
1587                    Ok(())
1588                };
1589                let error_if_any_fields = || -> std::result::Result<(), M::Error> {
1590                    error_if_fields(&[Element, Attributes, AdditionalAttributes, Name], &[])
1591                };
1592                match s.as_str() {
1593                    "String" => {
1594                        error_if_any_fields()?;
1595                        Ok(Type::Type {
1596                            ty: TypeVariant::String,
1597                            loc: None,
1598                        })
1599                    }
1600                    "Long" => {
1601                        error_if_any_fields()?;
1602                        Ok(Type::Type {
1603                            ty: TypeVariant::Long,
1604                            loc: None,
1605                        })
1606                    }
1607                    "Boolean" => {
1608                        error_if_any_fields()?;
1609                        Ok(Type::Type {
1610                            ty: TypeVariant::Boolean,
1611                            loc: None,
1612                        })
1613                    }
1614                    "Set" => {
1615                        error_if_fields(
1616                            &[Attributes, AdditionalAttributes, Name],
1617                            &[type_field_name!(Element)],
1618                        )?;
1619
1620                        match element {
1621                            Some(element) => Ok(Type::Type {
1622                                ty: TypeVariant::Set {
1623                                    element: Box::new(element),
1624                                },
1625                                loc: None,
1626                            }),
1627                            None => Err(serde::de::Error::missing_field(Element.as_str())),
1628                        }
1629                    }
1630                    "Record" => {
1631                        error_if_fields(
1632                            &[Element, Name],
1633                            &[
1634                                type_field_name!(Attributes),
1635                                type_field_name!(AdditionalAttributes),
1636                            ],
1637                        )?;
1638
1639                        if let Some(attributes) = attributes {
1640                            let additional_attributes =
1641                                additional_attributes.unwrap_or_else(partial_schema_default);
1642                            Ok(Type::Type {
1643                                ty: TypeVariant::Record(RecordType {
1644                                    attributes: attributes
1645                                        .0
1646                                        .into_iter()
1647                                        .map(
1648                                            |(
1649                                                k,
1650                                                TypeOfAttribute {
1651                                                    ty,
1652                                                    required,
1653                                                    annotations,
1654                                                    #[cfg(feature = "extended-schema")]
1655                                                    loc,
1656                                                },
1657                                            )| {
1658                                                (
1659                                                    k,
1660                                                    TypeOfAttribute {
1661                                                        ty: ty.into_n(),
1662                                                        required,
1663                                                        annotations,
1664                                                        #[cfg(feature = "extended-schema")]
1665                                                        loc,
1666                                                    },
1667                                                )
1668                                            },
1669                                        )
1670                                        .collect(),
1671                                    additional_attributes,
1672                                }),
1673                                loc: None,
1674                            })
1675                        } else {
1676                            Err(serde::de::Error::missing_field(Attributes.as_str()))
1677                        }
1678                    }
1679                    "Entity" => {
1680                        error_if_fields(
1681                            &[Element, Attributes, AdditionalAttributes],
1682                            &[type_field_name!(Name)],
1683                        )?;
1684                        match name {
1685                            Some(name) => Ok(Type::Type {
1686                                ty: TypeVariant::Entity {
1687                                    name: RawName::from_normalized_str(&name)
1688                                        .map_err(|err| {
1689                                            serde::de::Error::custom(format!(
1690                                                "invalid entity type `{name}`: {err}"
1691                                            ))
1692                                        })?
1693                                        .into(),
1694                                },
1695                                loc: None,
1696                            }),
1697                            None => Err(serde::de::Error::missing_field(Name.as_str())),
1698                        }
1699                    }
1700                    "EntityOrCommon" => {
1701                        error_if_fields(
1702                            &[Element, Attributes, AdditionalAttributes],
1703                            &[type_field_name!(Name)],
1704                        )?;
1705                        match name {
1706                            Some(name) => Ok(Type::Type {
1707                                ty: TypeVariant::EntityOrCommon {
1708                                    type_name: RawName::from_normalized_str(&name)
1709                                        .map_err(|err| {
1710                                            serde::de::Error::custom(format!(
1711                                                "invalid entity or common type `{name}`: {err}"
1712                                            ))
1713                                        })?
1714                                        .into(),
1715                                },
1716                                loc: None,
1717                            }),
1718                            None => Err(serde::de::Error::missing_field(Name.as_str())),
1719                        }
1720                    }
1721                    "Extension" => {
1722                        error_if_fields(
1723                            &[Element, Attributes, AdditionalAttributes],
1724                            &[type_field_name!(Name)],
1725                        )?;
1726
1727                        match name {
1728                            Some(name) => Ok(Type::Type {
1729                                ty: TypeVariant::Extension {
1730                                    name: UnreservedId::from_normalized_str(&name).map_err(
1731                                        |err| {
1732                                            serde::de::Error::custom(format!(
1733                                                "invalid extension type `{name}`: {err}"
1734                                            ))
1735                                        },
1736                                    )?,
1737                                },
1738                                loc: None,
1739                            }),
1740                            None => Err(serde::de::Error::missing_field(Name.as_str())),
1741                        }
1742                    }
1743                    type_name => {
1744                        error_if_any_fields()?;
1745                        Ok(Type::CommonTypeRef {
1746                            type_name: N::from(RawName::from_normalized_str(type_name).map_err(
1747                                |err| {
1748                                    serde::de::Error::custom(format!(
1749                                        "invalid common type `{type_name}`: {err}"
1750                                    ))
1751                                },
1752                            )?),
1753                            loc: None,
1754                        })
1755                    }
1756                }
1757            }
1758            None => Err(serde::de::Error::missing_field(TypeField.as_str())),
1759        }
1760    }
1761}
1762
1763impl<N> From<TypeVariant<N>> for Type<N> {
1764    fn from(ty: TypeVariant<N>) -> Self {
1765        Self::Type { ty, loc: None }
1766    }
1767}
1768
1769/// Represents the type-level information about a record type.
1770///
1771/// The parameter `N` is the type of entity type names and common type names in
1772/// this [`RecordType`], including recursively.
1773/// See notes on [`Fragment`].
1774#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1775#[educe(PartialEq, Eq, PartialOrd, Ord)]
1776#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1777#[serde(rename_all = "camelCase")]
1778#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1779#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1780pub struct RecordType<N> {
1781    /// Attribute names and types for the record
1782    pub attributes: BTreeMap<SmolStr, TypeOfAttribute<N>>,
1783    /// Whether "additional attributes" are possible on this record
1784    #[serde(default = "partial_schema_default")]
1785    #[serde(skip_serializing_if = "is_partial_schema_default")]
1786    pub additional_attributes: bool,
1787}
1788
1789impl<N> Default for RecordType<N> {
1790    fn default() -> Self {
1791        Self {
1792            attributes: BTreeMap::new(),
1793            additional_attributes: partial_schema_default(),
1794        }
1795    }
1796}
1797
1798impl<N> RecordType<N> {
1799    /// Is this [`RecordType`] an empty record?
1800    pub fn is_empty_record(&self) -> bool {
1801        self.additional_attributes == partial_schema_default() && self.attributes.is_empty()
1802    }
1803}
1804
1805impl RecordType<RawName> {
1806    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
1807    pub fn conditionally_qualify_type_references(
1808        self,
1809        ns: Option<&InternalName>,
1810    ) -> RecordType<ConditionalName> {
1811        RecordType {
1812            attributes: self
1813                .attributes
1814                .into_iter()
1815                .map(|(k, v)| (k, v.conditionally_qualify_type_references(ns)))
1816                .collect(),
1817            additional_attributes: self.additional_attributes,
1818        }
1819    }
1820}
1821
1822impl RecordType<ConditionalName> {
1823    /// Convert this [`RecordType<ConditionalName>`] into a
1824    /// [`RecordType<InternalName>`] by fully-qualifying all typenames that
1825    /// appear anywhere in any definitions.
1826    ///
1827    /// `all_defs` needs to contain the full set of all fully-qualified typenames
1828    /// and actions that are defined in the schema (in all schema fragments).
1829    pub fn fully_qualify_type_references(
1830        self,
1831        all_defs: &AllDefs,
1832    ) -> std::result::Result<RecordType<InternalName>, TypeNotDefinedError> {
1833        Ok(RecordType {
1834            attributes: self
1835                .attributes
1836                .into_iter()
1837                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
1838                .collect::<std::result::Result<_, TypeNotDefinedError>>()?,
1839            additional_attributes: self.additional_attributes,
1840        })
1841    }
1842}
1843
1844/// All the variants of [`Type`] other than common types, which are handled
1845/// directly in [`Type`]. See notes on [`Type`] for why it's necessary to have a
1846/// separate enum here.
1847///
1848/// The parameter `N` is the type of entity type names and common type names in
1849/// this [`TypeVariant`], including recursively.
1850/// See notes on [`Fragment`].
1851#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
1852#[educe(PartialEq(bound(N: PartialEq)), Eq, PartialOrd, Ord(bound(N: Ord)))]
1853#[serde(tag = "type")]
1854#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
1855#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
1856#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
1857pub enum TypeVariant<N> {
1858    /// String
1859    String,
1860    /// Long
1861    Long,
1862    /// Boolean
1863    Boolean,
1864    /// Set
1865    Set {
1866        /// Element type
1867        element: Box<Type<N>>,
1868    },
1869    /// Record
1870    Record(RecordType<N>),
1871    /// Entity
1872    Entity {
1873        /// Name of the entity type.
1874        /// For the important case of `N` = `RawName`, this is the schema JSON
1875        /// format, and the `RawName` is exactly how it appears in the schema;
1876        /// may not yet be fully qualified
1877        name: N,
1878    },
1879    /// Reference that may resolve to either an entity or common type
1880    EntityOrCommon {
1881        /// Name of the entity or common type.
1882        /// For the important case of `N` = `RawName`, this is the schema JSON
1883        /// format, and the `RawName` is exactly how it appears in the schema;
1884        /// may not yet be fully qualified.
1885        ///
1886        /// There is no possible ambiguity in the JSON syntax between this and
1887        /// `Entity`, nor between this and `Type::Common`.
1888        /// - To represent a must-be-entity-type reference in the JSON syntax,
1889        ///   use `{ "type": "Entity", "name": "foo" }`. This ser/de as
1890        ///   `Type::Type(TypeVariant::Entity)`.
1891        /// - To represent a must-be-common-type reference in the JSON syntax,
1892        ///   use `{ "type": "foo" }`. This ser/de as
1893        ///   `Type::CommonTypeRef`.
1894        /// - To represent an either-entity-or-common-type reference in the
1895        ///   JSON syntax, use `{ "type": "EntityOrCommon", "name": "foo" }`.
1896        ///   This ser/de as `Type::Type(TypeVariant::EntityOrCommon`.
1897        ///
1898        /// You can still use `{ "type": "Entity" }` alone (no `"name"` key) to
1899        /// indicate a common type named `Entity`, and likewise for
1900        /// `EntityOrCommon`.
1901        #[serde(rename = "name")]
1902        type_name: N,
1903    },
1904    /// Extension types
1905    Extension {
1906        /// Name of the extension type
1907        name: UnreservedId,
1908    },
1909}
1910
1911impl TypeVariant<RawName> {
1912    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
1913    pub fn conditionally_qualify_type_references(
1914        self,
1915        ns: Option<&InternalName>,
1916    ) -> TypeVariant<ConditionalName> {
1917        match self {
1918            Self::Boolean => TypeVariant::Boolean,
1919            Self::Long => TypeVariant::Long,
1920            Self::String => TypeVariant::String,
1921            Self::Extension { name } => TypeVariant::Extension { name },
1922            Self::Entity { name } => TypeVariant::Entity {
1923                name: name.conditionally_qualify_with(ns, ReferenceType::Entity), // `Self::Entity` must resolve to an entity type, not a common type
1924            },
1925            Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1926                type_name: type_name.conditionally_qualify_with(ns, ReferenceType::CommonOrEntity),
1927            },
1928            Self::Set { element } => TypeVariant::Set {
1929                element: Box::new(element.conditionally_qualify_type_references(ns)),
1930            },
1931            Self::Record(RecordType {
1932                attributes,
1933                additional_attributes,
1934            }) => TypeVariant::Record(RecordType {
1935                attributes: BTreeMap::from_iter(attributes.into_iter().map(
1936                    |(
1937                        attr,
1938                        TypeOfAttribute {
1939                            ty,
1940                            required,
1941                            annotations,
1942                            #[cfg(feature = "extended-schema")]
1943                            loc,
1944                        },
1945                    )| {
1946                        (
1947                            attr,
1948                            TypeOfAttribute {
1949                                ty: ty.conditionally_qualify_type_references(ns),
1950                                required,
1951                                annotations,
1952                                #[cfg(feature = "extended-schema")]
1953                                loc,
1954                            },
1955                        )
1956                    },
1957                )),
1958                additional_attributes,
1959            }),
1960        }
1961    }
1962
1963    fn into_n<N: From<RawName>>(self) -> TypeVariant<N> {
1964        match self {
1965            Self::Boolean => TypeVariant::Boolean,
1966            Self::Long => TypeVariant::Long,
1967            Self::String => TypeVariant::String,
1968            Self::Entity { name } => TypeVariant::Entity { name: name.into() },
1969            Self::EntityOrCommon { type_name } => TypeVariant::EntityOrCommon {
1970                type_name: type_name.into(),
1971            },
1972            Self::Record(RecordType {
1973                attributes,
1974                additional_attributes,
1975            }) => TypeVariant::Record(RecordType {
1976                attributes: attributes
1977                    .into_iter()
1978                    .map(|(k, v)| (k, v.into_n()))
1979                    .collect(),
1980                additional_attributes,
1981            }),
1982            Self::Set { element } => TypeVariant::Set {
1983                element: Box::new(element.into_n()),
1984            },
1985            Self::Extension { name } => TypeVariant::Extension { name },
1986        }
1987    }
1988}
1989
1990impl TypeVariant<ConditionalName> {
1991    /// Convert this [`TypeVariant<ConditionalName>`] into a
1992    /// [`TypeVariant<InternalName>`] by fully-qualifying all typenames that
1993    /// appear anywhere in any definitions.
1994    ///
1995    /// `all_defs` needs to contain the full set of all fully-qualified typenames
1996    /// and actions that are defined in the schema (in all schema fragments).
1997    pub fn fully_qualify_type_references(
1998        self,
1999        all_defs: &AllDefs,
2000    ) -> std::result::Result<TypeVariant<InternalName>, TypeNotDefinedError> {
2001        match self {
2002            Self::Boolean => Ok(TypeVariant::Boolean),
2003            Self::Long => Ok(TypeVariant::Long),
2004            Self::String => Ok(TypeVariant::String),
2005            Self::Extension { name } => Ok(TypeVariant::Extension { name }),
2006            Self::Entity { name } => Ok(TypeVariant::Entity {
2007                name: name.resolve(all_defs)?,
2008            }),
2009            Self::EntityOrCommon { type_name } => Ok(TypeVariant::EntityOrCommon {
2010                type_name: type_name.resolve(all_defs)?,
2011            }),
2012            Self::Set { element } => Ok(TypeVariant::Set {
2013                element: Box::new(element.fully_qualify_type_references(all_defs)?),
2014            }),
2015            Self::Record(RecordType {
2016                attributes,
2017                additional_attributes,
2018            }) => Ok(TypeVariant::Record(RecordType {
2019                attributes: attributes
2020                    .into_iter()
2021                    .map(
2022                        |(
2023                            attr,
2024                            TypeOfAttribute {
2025                                ty,
2026                                required,
2027                                annotations,
2028                                #[cfg(feature = "extended-schema")]
2029                                loc,
2030                            },
2031                        )| {
2032                            Ok((
2033                                attr,
2034                                TypeOfAttribute {
2035                                    ty: ty.fully_qualify_type_references(all_defs)?,
2036                                    required,
2037                                    annotations,
2038                                    #[cfg(feature = "extended-schema")]
2039                                    loc,
2040                                },
2041                            ))
2042                        },
2043                    )
2044                    .collect::<std::result::Result<BTreeMap<_, _>, TypeNotDefinedError>>()?,
2045                additional_attributes,
2046            })),
2047        }
2048    }
2049}
2050
2051// Only used for serialization
2052#[allow(
2053    clippy::trivially_copy_pass_by_ref,
2054    reason = "Reference required to work with derived serde serialize implementation"
2055)]
2056fn is_partial_schema_default(b: &bool) -> bool {
2057    *b == partial_schema_default()
2058}
2059
2060#[cfg(feature = "arbitrary")]
2061// PANIC SAFETY property testing code
2062#[allow(clippy::panic)]
2063impl<'a> arbitrary::Arbitrary<'a> for Type<RawName> {
2064    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Type<RawName>> {
2065        use std::collections::BTreeSet;
2066
2067        Ok(Type::Type {
2068            ty: match u.int_in_range::<u8>(1..=8)? {
2069                1 => TypeVariant::String,
2070                2 => TypeVariant::Long,
2071                3 => TypeVariant::Boolean,
2072                4 => TypeVariant::Set {
2073                    element: Box::new(u.arbitrary()?),
2074                },
2075                5 => {
2076                    let attributes = {
2077                        let attr_names: BTreeSet<String> = u.arbitrary()?;
2078                        attr_names
2079                            .into_iter()
2080                            .map(|attr_name| {
2081                                Ok((attr_name.into(), u.arbitrary::<TypeOfAttribute<RawName>>()?))
2082                            })
2083                            .collect::<arbitrary::Result<_>>()?
2084                    };
2085                    TypeVariant::Record(RecordType {
2086                        attributes,
2087                        additional_attributes: u.arbitrary()?,
2088                    })
2089                }
2090                6 => TypeVariant::Entity {
2091                    name: u.arbitrary()?,
2092                },
2093                7 => TypeVariant::Extension {
2094                    // PANIC SAFETY: `ipaddr` is a valid `UnreservedId`
2095                    #[allow(clippy::unwrap_used)]
2096                    name: "ipaddr".parse().unwrap(),
2097                },
2098                8 => TypeVariant::Extension {
2099                    // PANIC SAFETY: `decimal` is a valid `UnreservedId`
2100                    #[allow(clippy::unwrap_used)]
2101                    name: "decimal".parse().unwrap(),
2102                },
2103                n => panic!("bad index: {n}"),
2104            },
2105            loc: None,
2106        })
2107    }
2108    fn size_hint(_depth: usize) -> (usize, Option<usize>) {
2109        (1, None) // Unfortunately, we probably can't be more precise than this
2110    }
2111}
2112
2113/// Used to describe the type of a record or entity attribute. It contains a the
2114/// type of the attribute and whether the attribute is required. The type is
2115/// flattened for serialization, so, in JSON format, this appears as a regular
2116/// type with one extra property `required`.
2117///
2118/// The parameter `N` is the type of entity type names and common type names in
2119/// this [`TypeOfAttribute`], including recursively.
2120/// See notes on [`Fragment`].
2121///
2122/// Note that we can't add `#[serde(deny_unknown_fields)]` here because we are
2123/// using `#[serde(tag = "type")]` in [`Type`] which is flattened here.
2124/// The way `serde(flatten)` is implemented means it may be possible to access
2125/// fields incorrectly if a struct contains two structs that are flattened
2126/// (`<https://github.com/serde-rs/serde/issues/1547>`). This shouldn't apply to
2127/// us as we're using `flatten` only once
2128/// (`<https://github.com/serde-rs/serde/issues/1600>`). This should be ok because
2129/// unknown fields for [`TypeOfAttribute`] should be passed to [`Type`] where
2130/// they will be denied (`<https://github.com/serde-rs/serde/issues/1600>`).
2131#[derive(Educe, Debug, Clone, Serialize, Deserialize)]
2132#[educe(PartialEq, Eq, PartialOrd, Ord)]
2133#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
2134pub struct TypeOfAttribute<N> {
2135    /// Underlying type of the attribute
2136    #[serde(flatten)]
2137    pub ty: Type<N>,
2138    /// Annotations
2139    #[serde(default)]
2140    #[serde(skip_serializing_if = "Annotations::is_empty")]
2141    pub annotations: Annotations,
2142    /// Whether the attribute is required
2143    #[serde(default = "record_attribute_required_default")]
2144    #[serde(skip_serializing_if = "is_record_attribute_required_default")]
2145    pub required: bool,
2146
2147    /// Source location - if available
2148    #[cfg(feature = "extended-schema")]
2149    #[educe(Eq(ignore))]
2150    #[serde(skip)]
2151    pub loc: Option<Loc>,
2152}
2153
2154impl TypeOfAttribute<RawName> {
2155    fn into_n<N: From<RawName>>(self) -> TypeOfAttribute<N> {
2156        TypeOfAttribute {
2157            ty: self.ty.into_n(),
2158
2159            required: self.required,
2160            annotations: self.annotations,
2161            #[cfg(feature = "extended-schema")]
2162            loc: self.loc,
2163        }
2164    }
2165
2166    /// (Conditionally) prefix unqualified entity and common type references with the namespace they are in
2167    pub fn conditionally_qualify_type_references(
2168        self,
2169        ns: Option<&InternalName>,
2170    ) -> TypeOfAttribute<ConditionalName> {
2171        TypeOfAttribute {
2172            ty: self.ty.conditionally_qualify_type_references(ns),
2173            required: self.required,
2174            annotations: self.annotations,
2175            #[cfg(feature = "extended-schema")]
2176            loc: self.loc,
2177        }
2178    }
2179}
2180
2181impl TypeOfAttribute<ConditionalName> {
2182    /// Convert this [`TypeOfAttribute<ConditionalName>`] into a
2183    /// [`TypeOfAttribute<InternalName>`] by fully-qualifying all typenames that
2184    /// appear anywhere in any definitions.
2185    ///
2186    /// `all_defs` needs to contain the full set of all fully-qualified typenames
2187    /// and actions that are defined in the schema (in all schema fragments).
2188    pub fn fully_qualify_type_references(
2189        self,
2190        all_defs: &AllDefs,
2191    ) -> std::result::Result<TypeOfAttribute<InternalName>, TypeNotDefinedError> {
2192        Ok(TypeOfAttribute {
2193            ty: self.ty.fully_qualify_type_references(all_defs)?,
2194            required: self.required,
2195            annotations: self.annotations,
2196            #[cfg(feature = "extended-schema")]
2197            loc: self.loc,
2198        })
2199    }
2200}
2201
2202#[cfg(feature = "arbitrary")]
2203impl<'a> arbitrary::Arbitrary<'a> for TypeOfAttribute<RawName> {
2204    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
2205        Ok(Self {
2206            ty: u.arbitrary::<Type<RawName>>()?,
2207            required: u.arbitrary()?,
2208            annotations: u.arbitrary()?,
2209            #[cfg(feature = "extended-schema")]
2210            loc: None,
2211        })
2212    }
2213
2214    fn size_hint(depth: usize) -> (usize, Option<usize>) {
2215        arbitrary::size_hint::and_all(&[
2216            <Type<RawName> as arbitrary::Arbitrary>::size_hint(depth),
2217            <bool as arbitrary::Arbitrary>::size_hint(depth),
2218            <crate::est::Annotations as arbitrary::Arbitrary>::size_hint(depth),
2219        ])
2220    }
2221}
2222
2223// Only used for serialization
2224#[allow(
2225    clippy::trivially_copy_pass_by_ref,
2226    reason = "Reference required to work with derived serde serialize implementation"
2227)]
2228fn is_record_attribute_required_default(b: &bool) -> bool {
2229    *b == record_attribute_required_default()
2230}
2231
2232/// By default schema properties which enable parts of partial schema validation
2233/// should be `false`.  Defines the default value for `additionalAttributes`.
2234fn partial_schema_default() -> bool {
2235    false
2236}
2237
2238/// Defines the default value for `required` on record and entity attributes.
2239fn record_attribute_required_default() -> bool {
2240    true
2241}
2242
2243#[cfg(test)]
2244mod test {
2245    use crate::{
2246        extensions::Extensions,
2247        test_utils::{expect_err, ExpectedErrorMessageBuilder},
2248    };
2249    use cool_asserts::assert_matches;
2250
2251    use crate::validator::ValidatorSchema;
2252
2253    use super::*;
2254
2255    #[test]
2256    fn test_entity_type_parser1() {
2257        let user = r#"
2258        {
2259            "memberOfTypes" : ["UserGroup"]
2260        }
2261        "#;
2262        assert_matches!(serde_json::from_str::<EntityType<RawName>>(user), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2263        assert_eq!(et.member_of_types, vec!["UserGroup".parse().unwrap()]);
2264        assert_eq!(
2265            et.shape,
2266            AttributesOrContext(Type::Type {
2267                ty: TypeVariant::Record(RecordType {
2268                    attributes: BTreeMap::new(),
2269                    additional_attributes: false
2270                }),
2271                loc: None
2272            }),
2273        );});
2274    }
2275
2276    #[test]
2277    fn test_entity_type_parser2() {
2278        let src = r#"
2279              { }
2280        "#;
2281        assert_matches!(serde_json::from_str::<EntityType<RawName>>(src), Ok(EntityType { kind: EntityTypeKind::Standard(et), .. }) => {
2282        assert_eq!(et.member_of_types.len(), 0);
2283        assert_eq!(
2284            et.shape,
2285            AttributesOrContext(Type::Type {
2286                ty: TypeVariant::Record(RecordType {
2287                    attributes: BTreeMap::new(),
2288                    additional_attributes: false
2289                }),
2290                loc: None
2291            }),
2292        );});
2293    }
2294
2295    #[test]
2296    fn test_action_type_parser1() {
2297        let src = r#"
2298              {
2299                "appliesTo" : {
2300                  "resourceTypes": ["Album"],
2301                  "principalTypes": ["User"]
2302                },
2303                "memberOf": [{"id": "readWrite"}]
2304              }
2305        "#;
2306        let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2307        let spec = ApplySpec {
2308            resource_types: vec!["Album".parse().unwrap()],
2309            principal_types: vec!["User".parse().unwrap()],
2310            context: AttributesOrContext::default(),
2311        };
2312        assert_eq!(at.applies_to, Some(spec));
2313        assert_eq!(
2314            at.member_of,
2315            Some(vec![ActionEntityUID {
2316                ty: None,
2317                id: "readWrite".into(),
2318                #[cfg(feature = "extended-schema")]
2319                loc: None
2320            }])
2321        );
2322    }
2323
2324    #[test]
2325    fn test_action_type_parser2() {
2326        let src = r#"
2327              { }
2328        "#;
2329        let at: ActionType<RawName> = serde_json::from_str(src).expect("Parse Error");
2330        assert_eq!(at.applies_to, None);
2331        assert!(at.member_of.is_none());
2332    }
2333
2334    #[test]
2335    fn test_schema_file_parser() {
2336        let src = serde_json::json!(
2337        {
2338            "entityTypes": {
2339
2340              "User": {
2341                "memberOfTypes": ["UserGroup"]
2342              },
2343              "Photo": {
2344                "memberOfTypes": ["Album", "Account"]
2345              },
2346
2347              "Album": {
2348                "memberOfTypes": ["Album", "Account"]
2349              },
2350              "Account": { },
2351              "UserGroup": { }
2352           },
2353
2354           "actions": {
2355              "readOnly": { },
2356              "readWrite": { },
2357              "createAlbum": {
2358                "appliesTo" : {
2359                  "resourceTypes": ["Account", "Album"],
2360                  "principalTypes": ["User"]
2361                },
2362                "memberOf": [{"id": "readWrite"}]
2363              },
2364              "addPhotoToAlbum": {
2365                "appliesTo" : {
2366                  "resourceTypes": ["Album"],
2367                  "principalTypes": ["User"]
2368                },
2369                "memberOf": [{"id": "readWrite"}]
2370              },
2371              "viewPhoto": {
2372                "appliesTo" : {
2373                  "resourceTypes": ["Photo"],
2374                  "principalTypes": ["User"]
2375                },
2376                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2377              },
2378              "viewComments": {
2379                "appliesTo" : {
2380                  "resourceTypes": ["Photo"],
2381                  "principalTypes": ["User"]
2382                },
2383                "memberOf": [{"id": "readOnly"}, {"id": "readWrite"}]
2384              }
2385            }
2386          });
2387        let schema_file: NamespaceDefinition<RawName> =
2388            serde_json::from_value(src).expect("Parse Error");
2389
2390        assert_eq!(schema_file.entity_types.len(), 5);
2391        assert_eq!(schema_file.actions.len(), 6);
2392    }
2393
2394    #[test]
2395    fn test_parse_namespaces() {
2396        let src = r#"
2397        {
2398            "foo::foo::bar::baz": {
2399                "entityTypes": {},
2400                "actions": {}
2401            }
2402        }"#;
2403        let schema: Fragment<RawName> = serde_json::from_str(src).expect("Parse Error");
2404        let (namespace, _descriptor) = schema.0.into_iter().next().unwrap();
2405        assert_eq!(namespace, Some("foo::foo::bar::baz".parse().unwrap()));
2406    }
2407
2408    #[test]
2409    #[should_panic(expected = "unknown field `requiredddddd`")]
2410    fn test_schema_file_with_misspelled_required() {
2411        let src = serde_json::json!(
2412        {
2413            "entityTypes": {
2414                "User": {
2415                    "shape": {
2416                        "type": "Record",
2417                        "attributes": {
2418                            "favorite": {
2419                                "type": "Entity",
2420                                "name": "Photo",
2421                                "requiredddddd": false
2422                            }
2423                        }
2424                    }
2425                }
2426            },
2427            "actions": {}
2428        });
2429        let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2430        println!("{schema:#?}");
2431    }
2432
2433    #[test]
2434    #[should_panic(expected = "unknown field `nameeeeee`")]
2435    fn test_schema_file_with_misspelled_field() {
2436        let src = serde_json::json!(
2437        {
2438            "entityTypes": {
2439                "User": {
2440                    "shape": {
2441                        "type": "Record",
2442                        "attributes": {
2443                            "favorite": {
2444                                "type": "Entity",
2445                                "nameeeeee": "Photo",
2446                            }
2447                        }
2448                    }
2449                }
2450            },
2451            "actions": {}
2452        });
2453        let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2454        println!("{schema:#?}");
2455    }
2456
2457    #[test]
2458    #[should_panic(expected = "unknown field `extra`")]
2459    fn test_schema_file_with_extra_field() {
2460        let src = serde_json::json!(
2461        {
2462            "entityTypes": {
2463                "User": {
2464                    "shape": {
2465                        "type": "Record",
2466                        "attributes": {
2467                            "favorite": {
2468                                "type": "Entity",
2469                                "name": "Photo",
2470                                "extra": "Should not exist"
2471                            }
2472                        }
2473                    }
2474                }
2475            },
2476            "actions": {}
2477        });
2478        let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2479        println!("{schema:#?}");
2480    }
2481
2482    #[test]
2483    #[should_panic(expected = "unknown field `memberOfTypes`")]
2484    fn test_schema_file_with_misplaced_field() {
2485        let src = serde_json::json!(
2486        {
2487            "entityTypes": {
2488                "User": {
2489                    "shape": {
2490                        "memberOfTypes": [],
2491                        "type": "Record",
2492                        "attributes": {
2493                            "favorite": {
2494                                "type": "Entity",
2495                                "name": "Photo",
2496                            }
2497                        }
2498                    }
2499                }
2500            },
2501            "actions": {}
2502        });
2503        let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2504        println!("{schema:#?}");
2505    }
2506
2507    #[test]
2508    fn schema_file_with_missing_field() {
2509        let src = serde_json::json!(
2510        {
2511            "": {
2512                "entityTypes": {
2513                    "User": {
2514                        "shape": {
2515                            "type": "Record",
2516                            "attributes": {
2517                                "favorite": {
2518                                    "type": "Entity",
2519                                }
2520                            }
2521                        }
2522                    }
2523                },
2524                "actions": {}
2525            }
2526        });
2527        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2528        assert_matches!(schema, Err(e) => {
2529            expect_err(
2530                &src,
2531                &miette::Report::new(e),
2532                &ExpectedErrorMessageBuilder::error(r#"missing field `name`"#)
2533                    .build());
2534        });
2535    }
2536
2537    #[test]
2538    #[should_panic(expected = "missing field `type`")]
2539    fn schema_file_with_missing_type() {
2540        let src = serde_json::json!(
2541        {
2542            "entityTypes": {
2543                "User": {
2544                    "shape": { }
2545                }
2546            },
2547            "actions": {}
2548        });
2549        let schema: NamespaceDefinition<RawName> = serde_json::from_value(src).unwrap();
2550        println!("{schema:#?}");
2551    }
2552
2553    #[test]
2554    fn schema_file_unexpected_malformed_attribute() {
2555        let src = serde_json::json!(
2556        { "": {
2557            "entityTypes": {
2558                "User": {
2559                    "shape": {
2560                        "type": "Record",
2561                        "attributes": {
2562                            "a": {
2563                                "type": "Long",
2564                                "attributes": {
2565                                    "b": {"foo": "bar"}
2566                                }
2567                            }
2568                        }
2569                    }
2570                }
2571            },
2572            "actions": {}
2573        }});
2574        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2575        assert_matches!(schema, Err(e) => {
2576            expect_err(
2577                "",
2578                &miette::Report::new(e),
2579                &ExpectedErrorMessageBuilder::error(r#"unknown field `foo`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`"#).build()
2580            );
2581        });
2582    }
2583
2584    #[test]
2585    fn error_in_nested_attribute_fails_fast_top_level_attr() {
2586        let src = serde_json::json!(
2587            {
2588                "": {
2589                  "entityTypes": {
2590                    "User": {
2591                      "shape": {
2592                        "type": "Record",
2593                        "attributes": {
2594                          "foo": {
2595                            "type": "Record",
2596                            // Parsing should fail here when `element` is not expected instead of failing later on `"bar"`
2597                            "element": { "type": "Long" }
2598                          },
2599                          "bar": { "type": "Long" }
2600                        }
2601                      }
2602                    }
2603                  },
2604                  "actions": {}
2605                }
2606              }
2607        );
2608
2609        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2610        assert_matches!(schema, Err(e) => {
2611            expect_err(
2612                "",
2613                &miette::Report::new(e),
2614                &ExpectedErrorMessageBuilder::error(r#"unknown field `element`, expected `attributes` or `additionalAttributes`"#).build()
2615            );
2616        });
2617    }
2618
2619    #[test]
2620    fn error_in_nested_attribute_fails_fast_nested_attr() {
2621        let src = serde_json::json!(
2622            { "": {
2623                "entityTypes": {
2624                    "a": {
2625                        "shape": {
2626                            "type": "Record",
2627                            "attributes": {
2628                                 "foo": { "type": "Entity", "name": "b" },
2629                                 "baz": { "type": "Record",
2630                                    "attributes": {
2631                                        // Parsing should fail here instead of continuing and failing on the `"b"` as in #417
2632                                        "z": "Boolean"
2633                                    }
2634                                }
2635                            }
2636                        }
2637                    },
2638                    "b": {}
2639                }
2640             } }
2641        );
2642
2643        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2644        assert_matches!(schema, Err(e) => {
2645            expect_err(
2646                "",
2647                &miette::Report::new(e),
2648                &ExpectedErrorMessageBuilder::error(r#"invalid type: string "Boolean", expected struct TypeOfAttribute"#).build()
2649            );
2650        });
2651    }
2652
2653    #[test]
2654    fn missing_namespace() {
2655        let src = r#"
2656        {
2657            "entityTypes": { "User": { } },
2658            "actions": {}
2659        }"#;
2660        let schema = ValidatorSchema::from_json_str(src, Extensions::all_available());
2661        assert_matches!(schema, Err(e) => {
2662            expect_err(
2663                src,
2664                &miette::Report::new(e),
2665                &ExpectedErrorMessageBuilder::error(r#"unknown field `User`, expected one of `commonTypes`, `entityTypes`, `actions`, `annotations` at line 3 column 35"#)
2666                    .help("JSON formatted schema must specify a namespace. If you want to use the empty namespace, explicitly specify it with `{ \"\": {..} }`")
2667                    .build());
2668        });
2669    }
2670}
2671
2672/// Tests related to PR #749
2673#[cfg(test)]
2674mod strengthened_types {
2675    use cool_asserts::assert_matches;
2676
2677    use super::{
2678        ActionEntityUID, ApplySpec, EntityType, Fragment, NamespaceDefinition, RawName, Type,
2679    };
2680
2681    /// Assert that `result` is an `Err`, and the error message matches `msg`
2682    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
2683    fn assert_error_matches<T: std::fmt::Debug>(result: Result<T, serde_json::Error>, msg: &str) {
2684        assert_matches!(result, Err(err) => assert_eq!(&err.to_string(), msg));
2685    }
2686
2687    #[test]
2688    fn invalid_namespace() {
2689        let src = serde_json::json!(
2690        {
2691           "\n" : {
2692            "entityTypes": {},
2693            "actions": {}
2694           }
2695        });
2696        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2697        assert_error_matches(schema, "invalid namespace `\n`: unexpected end of input");
2698
2699        let src = serde_json::json!(
2700        {
2701           "1" : {
2702            "entityTypes": {},
2703            "actions": {}
2704           }
2705        });
2706        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2707        assert_error_matches(schema, "invalid namespace `1`: unexpected token `1`");
2708
2709        let src = serde_json::json!(
2710        {
2711           "*1" : {
2712            "entityTypes": {},
2713            "actions": {}
2714           }
2715        });
2716        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2717        assert_error_matches(schema, "invalid namespace `*1`: unexpected token `*`");
2718
2719        let src = serde_json::json!(
2720        {
2721           "::" : {
2722            "entityTypes": {},
2723            "actions": {}
2724           }
2725        });
2726        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2727        assert_error_matches(schema, "invalid namespace `::`: unexpected token `::`");
2728
2729        let src = serde_json::json!(
2730        {
2731           "A::" : {
2732            "entityTypes": {},
2733            "actions": {}
2734           }
2735        });
2736        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
2737        assert_error_matches(schema, "invalid namespace `A::`: unexpected end of input");
2738    }
2739
2740    #[test]
2741    fn invalid_common_type() {
2742        let src = serde_json::json!(
2743        {
2744            "entityTypes": {},
2745            "actions": {},
2746            "commonTypes": {
2747                "" : {
2748                    "type": "String"
2749                }
2750            }
2751        });
2752        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2753        assert_error_matches(schema, "invalid id ``: unexpected end of input");
2754
2755        let src = serde_json::json!(
2756        {
2757            "entityTypes": {},
2758            "actions": {},
2759            "commonTypes": {
2760                "~" : {
2761                    "type": "String"
2762                }
2763            }
2764        });
2765        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2766        assert_error_matches(schema, "invalid id `~`: invalid token");
2767
2768        let src = serde_json::json!(
2769        {
2770            "entityTypes": {},
2771            "actions": {},
2772            "commonTypes": {
2773                "A::B" : {
2774                    "type": "String"
2775                }
2776            }
2777        });
2778        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2779        assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2780    }
2781
2782    #[test]
2783    fn invalid_entity_type() {
2784        let src = serde_json::json!(
2785        {
2786            "entityTypes": {
2787                "": {}
2788            },
2789            "actions": {}
2790        });
2791        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2792        assert_error_matches(schema, "invalid id ``: unexpected end of input");
2793
2794        let src = serde_json::json!(
2795        {
2796            "entityTypes": {
2797                "*": {}
2798            },
2799            "actions": {}
2800        });
2801        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2802        assert_error_matches(schema, "invalid id `*`: unexpected token `*`");
2803
2804        let src = serde_json::json!(
2805        {
2806            "entityTypes": {
2807                "A::B": {}
2808            },
2809            "actions": {}
2810        });
2811        let schema: Result<NamespaceDefinition<RawName>, _> = serde_json::from_value(src);
2812        assert_error_matches(schema, "invalid id `A::B`: unexpected token `::`");
2813    }
2814
2815    #[test]
2816    fn invalid_member_of_types() {
2817        let src = serde_json::json!(
2818        {
2819           "memberOfTypes": [""]
2820        });
2821        let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2822        assert_error_matches(schema, "invalid name ``: unexpected end of input");
2823
2824        let src = serde_json::json!(
2825        {
2826           "memberOfTypes": ["*"]
2827        });
2828        let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2829        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2830
2831        let src = serde_json::json!(
2832        {
2833           "memberOfTypes": ["A::"]
2834        });
2835        let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2836        assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2837
2838        let src = serde_json::json!(
2839        {
2840           "memberOfTypes": ["::A"]
2841        });
2842        let schema: Result<EntityType<RawName>, _> = serde_json::from_value(src);
2843        assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2844    }
2845
2846    #[test]
2847    fn invalid_apply_spec() {
2848        let src = serde_json::json!(
2849        {
2850           "resourceTypes": [""]
2851        });
2852        let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2853        assert_error_matches(schema, "invalid name ``: unexpected end of input");
2854
2855        let src = serde_json::json!(
2856        {
2857           "resourceTypes": ["*"]
2858        });
2859        let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2860        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2861
2862        let src = serde_json::json!(
2863        {
2864           "resourceTypes": ["A::"]
2865        });
2866        let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2867        assert_error_matches(schema, "invalid name `A::`: unexpected end of input");
2868
2869        let src = serde_json::json!(
2870        {
2871           "resourceTypes": ["::A"]
2872        });
2873        let schema: Result<ApplySpec<RawName>, _> = serde_json::from_value(src);
2874        assert_error_matches(schema, "invalid name `::A`: unexpected token `::`");
2875    }
2876
2877    #[test]
2878    fn invalid_schema_entity_types() {
2879        let src = serde_json::json!(
2880        {
2881           "type": "Entity",
2882            "name": ""
2883        });
2884        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2885        assert_error_matches(schema, "invalid entity type ``: unexpected end of input");
2886
2887        let src = serde_json::json!(
2888        {
2889           "type": "Entity",
2890            "name": "*"
2891        });
2892        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2893        assert_error_matches(schema, "invalid entity type `*`: unexpected token `*`");
2894
2895        let src = serde_json::json!(
2896        {
2897           "type": "Entity",
2898            "name": "::A"
2899        });
2900        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2901        assert_error_matches(schema, "invalid entity type `::A`: unexpected token `::`");
2902
2903        let src = serde_json::json!(
2904        {
2905           "type": "Entity",
2906            "name": "A::"
2907        });
2908        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2909        assert_error_matches(schema, "invalid entity type `A::`: unexpected end of input");
2910    }
2911
2912    #[test]
2913    fn invalid_action_euid() {
2914        let src = serde_json::json!(
2915        {
2916           "id": "action",
2917            "type": ""
2918        });
2919        let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2920        assert_error_matches(schema, "invalid name ``: unexpected end of input");
2921
2922        let src = serde_json::json!(
2923        {
2924           "id": "action",
2925            "type": "*"
2926        });
2927        let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2928        assert_error_matches(schema, "invalid name `*`: unexpected token `*`");
2929
2930        let src = serde_json::json!(
2931        {
2932           "id": "action",
2933            "type": "Action::"
2934        });
2935        let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2936        assert_error_matches(schema, "invalid name `Action::`: unexpected end of input");
2937
2938        let src = serde_json::json!(
2939        {
2940           "id": "action",
2941            "type": "::Action"
2942        });
2943        let schema: Result<ActionEntityUID<RawName>, _> = serde_json::from_value(src);
2944        assert_error_matches(schema, "invalid name `::Action`: unexpected token `::`");
2945    }
2946
2947    #[test]
2948    fn invalid_schema_common_types() {
2949        let src = serde_json::json!(
2950        {
2951           "type": ""
2952        });
2953        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2954        assert_error_matches(schema, "invalid common type ``: unexpected end of input");
2955
2956        let src = serde_json::json!(
2957        {
2958           "type": "*"
2959        });
2960        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2961        assert_error_matches(schema, "invalid common type `*`: unexpected token `*`");
2962
2963        let src = serde_json::json!(
2964        {
2965           "type": "::A"
2966        });
2967        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2968        assert_error_matches(schema, "invalid common type `::A`: unexpected token `::`");
2969
2970        let src = serde_json::json!(
2971        {
2972           "type": "A::"
2973        });
2974        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2975        assert_error_matches(schema, "invalid common type `A::`: unexpected end of input");
2976    }
2977
2978    #[test]
2979    fn invalid_schema_extension_types() {
2980        let src = serde_json::json!(
2981        {
2982           "type": "Extension",
2983           "name": ""
2984        });
2985        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2986        assert_error_matches(schema, "invalid extension type ``: unexpected end of input");
2987
2988        let src = serde_json::json!(
2989        {
2990            "type": "Extension",
2991           "name": "*"
2992        });
2993        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
2994        assert_error_matches(schema, "invalid extension type `*`: unexpected token `*`");
2995
2996        let src = serde_json::json!(
2997        {
2998            "type": "Extension",
2999           "name": "__cedar::decimal"
3000        });
3001        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3002        assert_error_matches(
3003            schema,
3004            "invalid extension type `__cedar::decimal`: unexpected token `::`",
3005        );
3006
3007        let src = serde_json::json!(
3008        {
3009            "type": "Extension",
3010           "name": "__cedar::"
3011        });
3012        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3013        assert_error_matches(
3014            schema,
3015            "invalid extension type `__cedar::`: unexpected token `::`",
3016        );
3017
3018        let src = serde_json::json!(
3019        {
3020            "type": "Extension",
3021           "name": "::__cedar"
3022        });
3023        let schema: Result<Type<RawName>, _> = serde_json::from_value(src);
3024        assert_error_matches(
3025            schema,
3026            "invalid extension type `::__cedar`: unexpected token `::`",
3027        );
3028    }
3029}
3030
3031/// Tests involving entity tags (RFC 82)
3032#[cfg(test)]
3033mod entity_tags {
3034    use super::*;
3035    use crate::test_utils::{expect_err, ExpectedErrorMessageBuilder};
3036    use cool_asserts::assert_matches;
3037    use serde_json::json;
3038
3039    /// This schema taken directly from the RFC 82 text
3040    #[track_caller]
3041    fn example_json_schema() -> serde_json::Value {
3042        json!({"": {
3043            "entityTypes": {
3044                "User" : {
3045                    "shape" : {
3046                        "type" : "Record",
3047                        "attributes" : {
3048                            "jobLevel" : {
3049                                "type" : "Long"
3050                            },
3051                        }
3052                    },
3053                    "tags" : {
3054                        "type" : "Set",
3055                        "element": { "type": "String" }
3056                    }
3057                },
3058                "Document" : {
3059                    "shape" : {
3060                        "type" : "Record",
3061                        "attributes" : {
3062                            "owner" : {
3063                                "type" : "Entity",
3064                                "name" : "User"
3065                            },
3066                        }
3067                    },
3068                    "tags" : {
3069                      "type" : "Set",
3070                      "element": { "type": "String" }
3071                    }
3072                }
3073            },
3074            "actions": {}
3075        }})
3076    }
3077
3078    #[test]
3079    fn roundtrip() {
3080        let json = example_json_schema();
3081        let json_schema = Fragment::from_json_value(json.clone()).expect("should be valid");
3082        let serialized_json_schema = serde_json::to_value(json_schema).expect("should be valid");
3083        assert_eq!(json, serialized_json_schema);
3084    }
3085
3086    #[test]
3087    fn basic() {
3088        let json = example_json_schema();
3089        assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3090            assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3091            assert_matches!(&user.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3092                assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); // TODO: why is this `TypeVariant::String` in this case but `EntityOrCommon { "String" }` in all the other cases in this test? Do we accept common types as the element type for sets?
3093            });});
3094            assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"Document".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(doc), ..} => {
3095            assert_matches!(&doc.tags, Some(Type::Type { ty: TypeVariant::Set { element }, ..}) => {
3096                assert_matches!(&**element, Type::Type{ ty: TypeVariant::String, ..}); // TODO: why is this `TypeVariant::String` in this case but `EntityOrCommon { "String" }` in all the other cases in this test? Do we accept common types as the element type for sets?
3097            });
3098        })})
3099    }
3100
3101    /// In this schema, the tag type is a common type
3102    #[test]
3103    fn tag_type_is_common_type() {
3104        let json = json!({"": {
3105            "commonTypes": {
3106                "T": { "type": "String" },
3107            },
3108            "entityTypes": {
3109                "User" : {
3110                    "shape" : {
3111                        "type" : "Record",
3112                        "attributes" : {
3113                            "jobLevel" : {
3114                                "type" : "Long"
3115                            },
3116                        }
3117                    },
3118                    "tags" : { "type" : "T" },
3119                },
3120            },
3121            "actions": {}
3122        }});
3123        assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3124            assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType {kind: EntityTypeKind::Standard(user), ..} => {
3125            assert_matches!(&user.tags, Some(Type::CommonTypeRef { type_name, .. }) => {
3126                assert_eq!(&format!("{type_name}"), "T");
3127            });
3128        })});
3129    }
3130
3131    /// In this schema, the tag type is an entity type
3132    #[test]
3133    fn tag_type_is_entity_type() {
3134        let json = json!({"": {
3135            "entityTypes": {
3136                "User" : {
3137                    "shape" : {
3138                        "type" : "Record",
3139                        "attributes" : {
3140                            "jobLevel" : {
3141                                "type" : "Long"
3142                            },
3143                        }
3144                    },
3145                    "tags" : { "type" : "Entity", "name": "User" },
3146                },
3147            },
3148            "actions": {}
3149        }});
3150        assert_matches!(Fragment::from_json_value(json), Ok(frag) => {
3151            assert_matches!(frag.0.get(&None).unwrap().entity_types.get(&"User".parse().unwrap()).unwrap(), EntityType { kind: EntityTypeKind::Standard(user), ..} => {
3152            assert_matches!(&user.tags, Some(Type::Type{ ty: TypeVariant::Entity{ name }, ..}) => {
3153                assert_eq!(&format!("{name}"), "User");
3154            });
3155        })});
3156    }
3157
3158    /// This schema has `tags` inside `shape` instead of parallel to it
3159    #[test]
3160    fn bad_tags() {
3161        let json = json!({"": {
3162            "entityTypes": {
3163                "User": {
3164                    "shape": {
3165                        "type": "Record",
3166                        "attributes": {
3167                            "jobLevel": {
3168                                "type": "Long"
3169                            },
3170                        },
3171                        "tags": { "type": "String" },
3172                    }
3173                },
3174            },
3175            "actions": {}
3176        }});
3177        assert_matches!(Fragment::from_json_value(json.clone()), Err(e) => {
3178            expect_err(
3179                &json,
3180                &miette::Report::new(e),
3181                &ExpectedErrorMessageBuilder::error("unknown field `tags`, expected one of `type`, `element`, `attributes`, `additionalAttributes`, `name`")
3182                    .build(),
3183            );
3184        });
3185    }
3186}
3187
3188/// Check that (de)serialization works as expected.
3189#[cfg(test)]
3190mod test_json_roundtrip {
3191    use super::*;
3192
3193    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
3194    fn roundtrip(schema: &Fragment<RawName>) {
3195        let json = serde_json::to_value(schema.clone()).unwrap();
3196        let new_schema: Fragment<RawName> = serde_json::from_value(json).unwrap();
3197        assert_eq!(schema, &new_schema);
3198    }
3199
3200    #[test]
3201    fn empty_namespace() {
3202        let fragment = Fragment(BTreeMap::from([(None, NamespaceDefinition::new([], []))]));
3203        roundtrip(&fragment);
3204    }
3205
3206    #[test]
3207    fn nonempty_namespace() {
3208        let fragment = Fragment(BTreeMap::from([(
3209            Some("a".parse().unwrap()),
3210            NamespaceDefinition::new([], []),
3211        )]));
3212        roundtrip(&fragment);
3213    }
3214
3215    #[test]
3216    fn nonempty_entity_types() {
3217        let fragment = Fragment(BTreeMap::from([(
3218            None,
3219            NamespaceDefinition::new(
3220                [(
3221                    "a".parse().unwrap(),
3222                    EntityType {
3223                        kind: EntityTypeKind::Standard(StandardEntityType {
3224                            member_of_types: vec!["a".parse().unwrap()],
3225                            shape: AttributesOrContext(Type::Type {
3226                                ty: TypeVariant::Record(RecordType {
3227                                    attributes: BTreeMap::new(),
3228                                    additional_attributes: false,
3229                                }),
3230                                loc: None,
3231                            }),
3232                            tags: None,
3233                        }),
3234                        annotations: Annotations::new(),
3235                        loc: None,
3236                    },
3237                )],
3238                [(
3239                    "action".into(),
3240                    ActionType {
3241                        attributes: None,
3242                        applies_to: Some(ApplySpec {
3243                            resource_types: vec!["a".parse().unwrap()],
3244                            principal_types: vec!["a".parse().unwrap()],
3245                            context: AttributesOrContext(Type::Type {
3246                                ty: TypeVariant::Record(RecordType {
3247                                    attributes: BTreeMap::new(),
3248                                    additional_attributes: false,
3249                                }),
3250                                loc: None,
3251                            }),
3252                        }),
3253                        member_of: None,
3254                        annotations: Annotations::new(),
3255                        loc: None,
3256                        #[cfg(feature = "extended-schema")]
3257                        defn_loc: None,
3258                    },
3259                )],
3260            ),
3261        )]));
3262        roundtrip(&fragment);
3263    }
3264
3265    #[test]
3266    fn multiple_namespaces() {
3267        let fragment = Fragment(BTreeMap::from([
3268            (
3269                Some("foo".parse().unwrap()),
3270                NamespaceDefinition::new(
3271                    [(
3272                        "a".parse().unwrap(),
3273                        EntityType {
3274                            kind: EntityTypeKind::Standard(StandardEntityType {
3275                                member_of_types: vec!["a".parse().unwrap()],
3276                                shape: AttributesOrContext(Type::Type {
3277                                    ty: TypeVariant::Record(RecordType {
3278                                        attributes: BTreeMap::new(),
3279                                        additional_attributes: false,
3280                                    }),
3281                                    loc: None,
3282                                }),
3283                                tags: None,
3284                            }),
3285                            annotations: Annotations::new(),
3286                            loc: None,
3287                        },
3288                    )],
3289                    [],
3290                ),
3291            ),
3292            (
3293                None,
3294                NamespaceDefinition::new(
3295                    [],
3296                    [(
3297                        "action".into(),
3298                        ActionType {
3299                            attributes: None,
3300                            applies_to: Some(ApplySpec {
3301                                resource_types: vec!["foo::a".parse().unwrap()],
3302                                principal_types: vec!["foo::a".parse().unwrap()],
3303                                context: AttributesOrContext(Type::Type {
3304                                    ty: TypeVariant::Record(RecordType {
3305                                        attributes: BTreeMap::new(),
3306                                        additional_attributes: false,
3307                                    }),
3308                                    loc: None,
3309                                }),
3310                            }),
3311                            member_of: None,
3312                            annotations: Annotations::new(),
3313                            loc: None,
3314                            #[cfg(feature = "extended-schema")]
3315                            defn_loc: None,
3316                        },
3317                    )],
3318                ),
3319            ),
3320        ]));
3321        roundtrip(&fragment);
3322    }
3323}
3324
3325/// Tests in this module check the behavior of schema parsing given duplicate
3326/// map keys. The `json!` macro silently drops duplicate keys before they reach
3327/// our parser, so these tests must be written with `from_json_str`
3328/// instead.
3329#[cfg(test)]
3330mod test_duplicates_error {
3331    use super::*;
3332
3333    #[test]
3334    #[should_panic(expected = "invalid entry: found duplicate key")]
3335    fn namespace() {
3336        let src = r#"{
3337            "Foo": {
3338              "entityTypes" : {},
3339              "actions": {}
3340            },
3341            "Foo": {
3342              "entityTypes" : {},
3343              "actions": {}
3344            }
3345        }"#;
3346        Fragment::from_json_str(src).unwrap();
3347    }
3348
3349    #[test]
3350    #[should_panic(expected = "invalid entry: found duplicate key")]
3351    fn entity_type() {
3352        let src = r#"{
3353            "Foo": {
3354              "entityTypes" : {
3355                "Bar": {},
3356                "Bar": {}
3357              },
3358              "actions": {}
3359            }
3360        }"#;
3361        Fragment::from_json_str(src).unwrap();
3362    }
3363
3364    #[test]
3365    #[should_panic(expected = "invalid entry: found duplicate key")]
3366    fn action() {
3367        let src = r#"{
3368            "Foo": {
3369              "entityTypes" : {},
3370              "actions": {
3371                "Bar": {},
3372                "Bar": {}
3373              }
3374            }
3375        }"#;
3376        Fragment::from_json_str(src).unwrap();
3377    }
3378
3379    #[test]
3380    #[should_panic(expected = "invalid entry: found duplicate key")]
3381    fn common_types() {
3382        let src = r#"{
3383            "Foo": {
3384              "entityTypes" : {},
3385              "actions": { },
3386              "commonTypes": {
3387                "Bar": {"type": "Long"},
3388                "Bar": {"type": "String"}
3389              }
3390            }
3391        }"#;
3392        Fragment::from_json_str(src).unwrap();
3393    }
3394
3395    #[test]
3396    #[should_panic(expected = "invalid entry: found duplicate key")]
3397    fn record_type() {
3398        let src = r#"{
3399            "Foo": {
3400              "entityTypes" : {
3401                "Bar": {
3402                    "shape": {
3403                        "type": "Record",
3404                        "attributes": {
3405                            "Baz": {"type": "Long"},
3406                            "Baz": {"type": "String"}
3407                        }
3408                    }
3409                }
3410              },
3411              "actions": { }
3412            }
3413        }"#;
3414        Fragment::from_json_str(src).unwrap();
3415    }
3416
3417    #[test]
3418    #[should_panic(expected = "missing field `resourceTypes`")]
3419    fn missing_resource() {
3420        let src = r#"{
3421            "Foo": {
3422              "entityTypes" : {},
3423              "actions": {
3424                "foo" : {
3425                    "appliesTo" : {
3426                        "principalTypes" : ["a"]
3427                    }
3428                }
3429              }
3430            }
3431        }"#;
3432        Fragment::from_json_str(src).unwrap();
3433    }
3434
3435    #[test]
3436    #[should_panic(expected = "missing field `principalTypes`")]
3437    fn missing_principal() {
3438        let src = r#"{
3439            "Foo": {
3440              "entityTypes" : {},
3441              "actions": {
3442                "foo" : {
3443                    "appliesTo" : {
3444                        "resourceTypes" : ["a"]
3445                    }
3446                }
3447              }
3448            }
3449        }"#;
3450        Fragment::from_json_str(src).unwrap();
3451    }
3452
3453    #[test]
3454    #[should_panic(expected = "missing field `resourceTypes`")]
3455    fn missing_both() {
3456        let src = r#"{
3457            "Foo": {
3458              "entityTypes" : {},
3459              "actions": {
3460                "foo" : {
3461                    "appliesTo" : {
3462                    }
3463                }
3464              }
3465            }
3466        }"#;
3467        Fragment::from_json_str(src).unwrap();
3468    }
3469}
3470
3471#[cfg(test)]
3472mod annotations {
3473    use crate::validator::RawName;
3474    use cool_asserts::assert_matches;
3475
3476    use super::Fragment;
3477
3478    #[test]
3479    fn empty_namespace() {
3480        let src = serde_json::json!(
3481        {
3482           "" : {
3483            "entityTypes": {},
3484            "actions": {},
3485            "annotations": {
3486                "doc": "this is a doc"
3487            }
3488           }
3489        });
3490        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3491        assert_matches!(schema, Err(err) => {
3492            assert_eq!(&err.to_string(), "annotations are not allowed on the empty namespace");
3493        });
3494    }
3495
3496    #[test]
3497    fn basic() {
3498        let src = serde_json::json!(
3499        {
3500           "N" : {
3501            "entityTypes": {},
3502            "actions": {},
3503            "annotations": {
3504                "doc": "this is a doc"
3505            }
3506           }
3507        });
3508        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3509        assert_matches!(schema, Ok(_));
3510
3511        let src = serde_json::json!(
3512        {
3513           "N" : {
3514            "entityTypes": {
3515                "a": {
3516                    "annotations": {
3517                        "a": "",
3518                        // null is also allowed like ESTs
3519                        "d": null,
3520                        "b": "c",
3521                    },
3522                    "shape": {
3523                        "type": "Long",
3524                    }
3525                }
3526            },
3527            "actions": {},
3528            "annotations": {
3529                "doc": "this is a doc"
3530            }
3531           }
3532        });
3533        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3534        assert_matches!(schema, Ok(_));
3535
3536        let src = serde_json::json!(
3537        {
3538           "N" : {
3539            "entityTypes": {
3540                "a": {
3541                    "annotations": {
3542                        "a": "",
3543                        "b": "c",
3544                    },
3545                    "shape": {
3546                        "type": "Long",
3547                    }
3548                }
3549            },
3550            "actions": {
3551                "a": {
3552                    "annotations": {
3553                        "doc": "this is a doc"
3554                    },
3555                    "appliesTo": {
3556                        "principalTypes": ["A"],
3557                        "resourceTypes": ["B"],
3558                    }
3559                },
3560            },
3561            "annotations": {
3562                "doc": "this is a doc"
3563            }
3564           }
3565        });
3566        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3567        assert_matches!(schema, Ok(_));
3568
3569        let src = serde_json::json!({
3570            "N": {
3571            "entityTypes": {},
3572            "actions": {},
3573            "commonTypes": {
3574                "Task": {
3575                "annotations": {
3576                    "doc": "a common type representing a task"
3577                },
3578                "type": "Record",
3579                "attributes": {
3580                    "id": {
3581                        "type": "Long",
3582                        "annotations": {
3583                            "doc": "task id"
3584                        }
3585                    },
3586                    "name": {
3587                        "type": "String"
3588                    },
3589                    "state": {
3590                        "type": "String"
3591                    }
3592                }
3593        }}}});
3594        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3595        assert_matches!(schema, Ok(_));
3596
3597        let src = serde_json::json!({
3598            "N": {
3599                "entityTypes": {
3600                    "User" : {
3601                        "shape" : {
3602                            "type" : "Record",
3603                            "attributes" : {
3604                                "name" : {
3605                                    "annotations": {
3606                                        "a": null,
3607                                    },
3608                                    "type" : "String"
3609                                },
3610                                "age" : {
3611                                    "type" : "Long"
3612                                }
3613                            }
3614                        }
3615                    }
3616                },
3617                "actions": {},
3618                "commonTypes": {}
3619        }});
3620        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3621        assert_matches!(schema, Ok(_));
3622
3623        // nested record
3624        let src = serde_json::json!({
3625            "N": {
3626                "entityTypes": {
3627                    "User" : {
3628                        "shape" : {
3629                            "type" : "Record",
3630                            "attributes" : {
3631                                "name" : {
3632                                    "annotations": {
3633                                        "first_layer": "b"
3634                                    },
3635                                    "type" : "Record",
3636                                    "attributes": {
3637                                        "a": {
3638                                            "type": "Record",
3639                                            "annotations": {
3640                                                "second_layer": "d"
3641                                            },
3642                                            "attributes": {
3643                                                "...": {
3644                                                    "annotations": {
3645                                                        "last_layer": null,
3646                                                    },
3647                                                    "type": "Long"
3648                                                }
3649                                            }
3650                                        }
3651                                    }
3652                                },
3653                                "age" : {
3654                                    "type" : "Long"
3655                                }
3656                            }
3657                        }
3658                    }
3659                },
3660                "actions": {},
3661                "commonTypes": {}
3662        }});
3663        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3664        assert_matches!(schema, Ok(_));
3665    }
3666
3667    #[track_caller]
3668    fn test_unknown_fields(src: serde_json::Value, field: &str, expected: &str) {
3669        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3670        assert_matches!(schema, Err(errs) => {
3671            assert_eq!(errs.to_string(), format!("unknown field {field}, expected one of {expected}"));
3672        });
3673    }
3674
3675    const ENTITY_TYPE_EXPECTED_ATTRIBUTES: &str =
3676        "`memberOfTypes`, `shape`, `tags`, `enum`, `annotations`";
3677    const NAMESPACE_EXPECTED_ATTRIBUTES: &str =
3678        "`commonTypes`, `entityTypes`, `actions`, `annotations`";
3679    const ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES: &str =
3680        "`type`, `element`, `attributes`, `additionalAttributes`, `name`";
3681    const APPLIES_TO_EXPECTED_ATTRIBUTES: &str = "`resourceTypes`, `principalTypes`, `context`";
3682
3683    #[test]
3684    fn unknown_fields() {
3685        let src = serde_json::json!(
3686        {
3687            "N": {
3688                "entityTypes": {
3689            "UserGroup": {
3690                "shape44": {
3691                    "type": "Record",
3692                    "attributes": {}
3693                },
3694                "memberOfTypes": [
3695                    "UserGroup"
3696                ]
3697            }},
3698            "actions": {},
3699        }});
3700        test_unknown_fields(src, "`shape44`", ENTITY_TYPE_EXPECTED_ATTRIBUTES);
3701
3702        let src = serde_json::json!(
3703        {
3704            "N": {
3705                "entityTypes": {},
3706                "actions": {},
3707                "commonTypes": {
3708                "C": {
3709                    "type": "Set",
3710                        "element": {
3711                            "annotations": {
3712                            "doc": "this is a doc"
3713                            },
3714                           "type": "Long"
3715                        }
3716                }
3717        }}});
3718        test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3719
3720        let src = serde_json::json!(
3721        {
3722            "N": {
3723                "entityTypes": {},
3724                "actions": {},
3725                "commonTypes": {
3726                "C": {
3727                    "type": "Long",
3728                    "foo": 1,
3729                            "annotations": {
3730                            "doc": "this is a doc"
3731                            },
3732        }}}});
3733        test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3734
3735        let src = serde_json::json!(
3736        {
3737            "N": {
3738                "entityTypes": {},
3739                "actions": {},
3740                "commonTypes": {
3741                "C": {
3742                    "type": "Record",
3743                    "attributes": {
3744                        "a": {
3745                            "annotations": {
3746                            "doc": "this is a doc"
3747                            },
3748                            "type": "Long",
3749                            "foo": 2,
3750                            "required": true,
3751                        }
3752                    },
3753        }}}});
3754        test_unknown_fields(src, "`foo`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3755
3756        let src = serde_json::json!(
3757        {
3758            "N": {
3759                "entityTypes": {},
3760                "actions": {},
3761                "commonTypes": {
3762                "C": {
3763                    "type": "Record",
3764                    "attributes": {
3765                        "a": {
3766                            "annotations": {
3767                            "doc": "this is a doc"
3768                            },
3769                            "type": "Record",
3770                            "attributes": {
3771                                "b": {
3772                                    "annotations": {
3773                            "doc": "this is a doc"
3774                            },
3775                            "type": "Long",
3776                            "bar": 3,
3777                                },
3778                            },
3779                            "required": true,
3780                        }
3781                    },
3782        }}}});
3783        test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3784
3785        let src = serde_json::json!(
3786        {
3787            "N": {
3788                "entityTypes": {
3789            "UserGroup": {
3790                "shape": {
3791                    "annotations": {
3792                        "doc": "this is a doc"
3793                    },
3794                    "type": "Record",
3795                    "attributes": {}
3796                },
3797                "memberOfTypes": [
3798                    "UserGroup"
3799                ]
3800            }},
3801            "actions": {},
3802        }});
3803        test_unknown_fields(src, "`annotations`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3804
3805        let src = serde_json::json!(
3806        {
3807            "N": {
3808                "entityTypes": {},
3809                "actions": {
3810                    "a": {
3811                        "appliesTo": {
3812                            "annotations": {
3813                                "doc": "this is a doc"
3814                            },
3815                            "principalTypes": ["A"],
3816                            "resourceTypes": ["B"],
3817                        }
3818                    },
3819                },
3820        }});
3821        test_unknown_fields(src, "`annotations`", APPLIES_TO_EXPECTED_ATTRIBUTES);
3822
3823        let src = serde_json::json!(
3824        {
3825           "N" : {
3826            "entityTypes": {},
3827            "actions": {},
3828            "foo": "",
3829            "annotations": {
3830                "doc": "this is a doc"
3831            }
3832           }
3833        });
3834        test_unknown_fields(src, "`foo`", NAMESPACE_EXPECTED_ATTRIBUTES);
3835
3836        let src = serde_json::json!(
3837        {
3838           "" : {
3839            "entityTypes": {},
3840            "actions": {},
3841            "commonTypes": {
3842                "a": {
3843                    "type": "Long",
3844                    "annotations": {
3845                        "foo": ""
3846                    },
3847                    "bar": 1,
3848                }
3849            }
3850           }
3851        });
3852        test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3853
3854        let src = serde_json::json!(
3855        {
3856           "N" : {
3857            "entityTypes": {},
3858            "actions": {},
3859            "commonTypes": {
3860                "a": {
3861                    "type": "Record",
3862                    "annotations": {
3863                        "foo": ""
3864                    },
3865                    "attributes": {
3866                        "a": {
3867                            "bar": 1,
3868                            "type": "Long"
3869                        }
3870                    }
3871                }
3872            }
3873           }
3874        });
3875        test_unknown_fields(src, "`bar`", ATTRIBUTE_TYPE_EXPECTED_ATTRIBUTES);
3876    }
3877}
3878
3879#[cfg(test)]
3880mod ord {
3881    use super::{InternalName, RawName, Type, TypeVariant};
3882    use std::collections::BTreeSet;
3883
3884    /// Tests that `Type<RawName>` and `Type<InternalName>` are `Ord`
3885    #[test]
3886    #[allow(clippy::collection_is_never_read)]
3887    fn type_ord() {
3888        let mut set: BTreeSet<Type<RawName>> = BTreeSet::default();
3889        set.insert(Type::Type {
3890            ty: TypeVariant::String,
3891            loc: None,
3892        });
3893        let mut set: BTreeSet<Type<InternalName>> = BTreeSet::default();
3894        set.insert(Type::Type {
3895            ty: TypeVariant::String,
3896            loc: None,
3897        });
3898    }
3899}
3900
3901#[cfg(test)]
3902// PANIC SAFETY: tests
3903#[allow(clippy::indexing_slicing)]
3904mod enumerated_entity_types {
3905    use cool_asserts::assert_matches;
3906
3907    use crate::validator::{
3908        json_schema::{EntityType, EntityTypeKind, Fragment},
3909        RawName,
3910    };
3911
3912    #[test]
3913    fn basic() {
3914        let src = serde_json::json!({
3915            "": {
3916                "entityTypes": {
3917                    "Foo": {
3918                        "enum": ["foo", "bar"],
3919                        "annotations": {
3920                            "a": "b",
3921                        }
3922                    },
3923                },
3924                "actions": {},
3925            }
3926        });
3927        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3928        assert_matches!(schema, Ok(frag) => {
3929            assert_matches!(&frag.0[&None].entity_types[&"Foo".parse().unwrap()], EntityType {
3930                kind: EntityTypeKind::Enum {choices},
3931                ..
3932            } => {
3933                assert_eq!(Vec::from(choices.clone()), ["foo", "bar"]);
3934            });
3935        });
3936
3937        let src = serde_json::json!({
3938            "": {
3939                "entityTypes": {
3940                    "Foo": {
3941                        "enum": [],
3942                        "annotations": {
3943                            "a": "b",
3944                        }
3945                    },
3946                },
3947                "actions": {},
3948            }
3949        });
3950        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3951        assert_matches!(schema, Err(errs) => {
3952            // TODO: write our own error messages if it's deemed to be too ugly.
3953            assert_eq!(errs.to_string(), "the vector provided was empty, NonEmpty needs at least one element");
3954        });
3955
3956        let src = serde_json::json!({
3957            "": {
3958                "entityTypes": {
3959                    "Foo": {
3960                        "enum": null,
3961                    },
3962                },
3963                "actions": {},
3964            }
3965        });
3966        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3967        assert_matches!(schema, Err(errs) => {
3968            assert_eq!(errs.to_string(), "invalid type: null, expected a sequence");
3969        });
3970
3971        let src = serde_json::json!({
3972            "": {
3973                "entityTypes": {
3974                    "Foo": {
3975                        "enum": ["foo"],
3976                        "memberOfTypes": ["bar"],
3977                    },
3978                },
3979                "actions": {},
3980            }
3981        });
3982        let schema: Result<Fragment<RawName>, _> = serde_json::from_value(src);
3983        assert_matches!(schema, Err(errs) => {
3984            assert_eq!(errs.to_string(), "unexpected field: memberOfTypes");
3985        });
3986    }
3987}