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