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