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