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