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