Skip to main content

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