1use std::collections::HashMap;
20
21use cedar_policy_core::{
22 ast::{Annotations, Id, Name, UnreservedId},
23 extensions::Extensions,
24 parser::{Loc, Node},
25};
26use itertools::Either;
27use nonempty::NonEmpty;
28use smol_str::{SmolStr, ToSmolStr};
29use std::collections::hash_map::Entry;
30
31use super::{
32 ast::{
33 ActionDecl, Annotated, AppDecl, AttrDecl, Decl, Declaration, EntityDecl, Namespace,
34 PRAppDecl, Path, QualName, Schema, Type, TypeDecl, BUILTIN_TYPES, PR,
35 },
36 err::{schema_warnings, SchemaWarning, ToJsonSchemaError, ToJsonSchemaErrors},
37};
38use crate::{
39 cedar_schema,
40 json_schema::{self, CommonType},
41 RawName,
42};
43
44impl From<cedar_schema::Path> for RawName {
45 fn from(p: cedar_schema::Path) -> Self {
46 RawName::from_name(p.into())
47 }
48}
49
50pub fn cedar_schema_to_json_schema(
59 schema: Schema,
60 extensions: &Extensions<'_>,
61) -> Result<
62 (
63 json_schema::Fragment<RawName>,
64 impl Iterator<Item = SchemaWarning>,
65 ),
66 ToJsonSchemaErrors,
67> {
68 let (qualified_namespaces, unqualified_namespace) = split_unqualified_namespace(schema);
77 let all_namespaces = qualified_namespaces
79 .chain(unqualified_namespace)
80 .collect::<Vec<_>>();
81
82 let names = build_namespace_bindings(all_namespaces.iter().map(|ns| &ns.data))?;
83 let warnings = compute_namespace_warnings(&names, extensions);
84 let fragment = collect_all_errors(all_namespaces.into_iter().map(convert_namespace))?.collect();
85 Ok((
86 json_schema::Fragment(fragment),
87 warnings.collect::<Vec<_>>().into_iter(),
88 ))
89}
90
91fn is_valid_ext_type(ty: &Id, extensions: &Extensions<'_>) -> bool {
93 extensions
94 .ext_types()
95 .filter(|ext_ty| ext_ty.as_ref().is_unqualified()) .any(|ext_ty| ty == ext_ty.basename_as_ref())
97}
98
99pub fn cedar_type_to_json_type(ty: Node<Type>) -> json_schema::Type<RawName> {
101 let variant = match ty.node {
102 Type::Set(t) => json_schema::TypeVariant::Set {
103 element: Box::new(cedar_type_to_json_type(*t)),
104 },
105 Type::Ident(p) => json_schema::TypeVariant::EntityOrCommon {
106 type_name: RawName::from(p),
107 },
108 Type::Record(fields) => json_schema::TypeVariant::Record(json_schema::RecordType {
109 attributes: fields.into_iter().map(convert_attr_decl).collect(),
110 additional_attributes: false,
111 }),
112 };
113 json_schema::Type::Type {
114 ty: variant,
115 loc: Some(ty.loc),
116 }
117}
118
119fn split_unqualified_namespace(
122 namespaces: impl IntoIterator<Item = Annotated<Namespace>>,
123) -> (
124 impl Iterator<Item = Annotated<Namespace>>,
125 Option<Annotated<Namespace>>,
126) {
127 let (qualified, unqualified): (Vec<_>, Vec<_>) =
129 namespaces.into_iter().partition(|n| n.data.name.is_some());
130
131 let mut unqualified_decls = vec![];
133 for mut unqualified_namespace in unqualified.into_iter() {
134 unqualified_decls.append(&mut unqualified_namespace.data.decls);
135 }
136
137 if unqualified_decls.is_empty() {
138 (qualified.into_iter(), None)
139 } else {
140 let unqual = Namespace {
141 name: None,
142 decls: unqualified_decls,
143 loc: None,
144 };
145 (
146 qualified.into_iter(),
147 Some(Annotated {
148 data: unqual,
149 annotations: Annotations::new(),
150 }),
151 )
152 }
153}
154
155fn convert_namespace(
157 namespace: Annotated<Namespace>,
158) -> Result<(Option<Name>, json_schema::NamespaceDefinition<RawName>), ToJsonSchemaErrors> {
159 let ns_name = namespace
160 .data
161 .name
162 .clone()
163 .map(|p| {
164 let internal_name = RawName::from(p.clone()).qualify_with(None); Name::try_from(internal_name)
166 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), p.loc().clone()))
167 })
168 .transpose()?;
169 let def = namespace.try_into()?;
170 Ok((ns_name, def))
171}
172
173impl TryFrom<Annotated<Namespace>> for json_schema::NamespaceDefinition<RawName> {
174 type Error = ToJsonSchemaErrors;
175
176 fn try_from(
177 n: Annotated<Namespace>,
178 ) -> Result<json_schema::NamespaceDefinition<RawName>, Self::Error> {
179 let (entity_types, action, common_types) = into_partition_decls(n.data.decls);
181
182 let entity_types = collect_all_errors(entity_types.into_iter().map(convert_entity_decl))?
184 .flatten()
185 .collect();
186
187 let actions = collect_all_errors(action.into_iter().map(convert_action_decl))?
189 .flatten()
190 .collect();
191
192 let common_types = common_types
194 .into_iter()
195 .map(|decl| {
196 let name_loc = decl.data.node.name.loc.clone();
197 let id = UnreservedId::try_from(decl.data.node.name.node)
198 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), name_loc.clone()))?;
199 let ctid = json_schema::CommonTypeId::new(id)
200 .map_err(|e| ToJsonSchemaError::reserved_keyword(&e.id, name_loc))?;
201 Ok((
202 ctid,
203 CommonType {
204 ty: cedar_type_to_json_type(decl.data.node.def),
205 annotations: decl.annotations.into(),
206 loc: Some(decl.data.loc),
207 },
208 ))
209 })
210 .collect::<Result<_, ToJsonSchemaError>>()?;
211
212 Ok(json_schema::NamespaceDefinition {
213 common_types,
214 entity_types,
215 actions,
216 annotations: n.annotations.into(),
217 #[cfg(feature = "extended-schema")]
218 loc: n.data.loc,
219 })
220 }
221}
222
223fn convert_action_decl(
225 a: Annotated<Node<ActionDecl>>,
226) -> Result<impl Iterator<Item = (SmolStr, json_schema::ActionType<RawName>)>, ToJsonSchemaErrors> {
227 let ActionDecl {
228 names,
229 parents,
230 app_decls,
231 } = a.data.node;
232 let applies_to = app_decls
234 .map(|decls| convert_app_decls(&names.first().node, &names.first().loc, decls))
235 .transpose()?
236 .unwrap_or_else(|| json_schema::ApplySpec {
237 resource_types: vec![],
238 principal_types: vec![],
239 context: json_schema::AttributesOrContext::default(),
240 });
241 let member_of = parents.map(|parents| parents.into_iter().map(convert_qual_name).collect());
242
243 Ok(names.into_iter().map(move |name| {
244 let ty = json_schema::ActionType {
245 attributes: None, applies_to: Some(applies_to.clone()),
247 member_of: member_of.clone(),
248 annotations: a.annotations.clone().into(),
249 loc: Some(a.data.loc.clone()),
250 #[cfg(feature = "extended-schema")]
251 defn_loc: Some(name.loc),
252 };
253 (name.node, ty)
254 }))
255}
256
257fn convert_qual_name(qn: Node<QualName>) -> json_schema::ActionEntityUID<RawName> {
258 json_schema::ActionEntityUID::new(qn.node.path.map(Into::into), qn.node.eid)
259}
260
261fn convert_app_decls(
266 name: &SmolStr,
267 name_loc: &Loc,
268 decls: Node<NonEmpty<Node<AppDecl>>>,
269) -> Result<json_schema::ApplySpec<RawName>, ToJsonSchemaErrors> {
270 let (decls, _) = decls.into_inner();
272 let mut principal_types: Option<Node<Vec<RawName>>> = None;
273 let mut resource_types: Option<Node<Vec<RawName>>> = None;
274 let mut context: Option<Node<json_schema::AttributesOrContext<RawName>>> = None;
275
276 for decl in decls {
277 match decl {
278 Node {
279 node: AppDecl::Context(context_decl),
280 loc,
281 } => match context {
282 Some(existing_context) => {
283 return Err(ToJsonSchemaError::duplicate_context(
284 name,
285 existing_context.loc,
286 loc,
287 )
288 .into());
289 }
290 None => {
291 context = Some(Node::with_source_loc(
292 convert_context_decl(context_decl),
293 loc,
294 ));
295 }
296 },
297 Node {
298 node:
299 AppDecl::PR(PRAppDecl {
300 kind:
301 Node {
302 node: PR::Principal,
303 ..
304 },
305 entity_tys,
306 }),
307 loc,
308 } => match principal_types {
309 Some(existing_tys) => {
310 return Err(ToJsonSchemaError::duplicate_principal(
311 name,
312 existing_tys.loc,
313 loc,
314 )
315 .into());
316 }
317 None => match entity_tys {
318 None => {
319 return Err(
320 ToJsonSchemaError::empty_principal(name, name_loc.clone(), loc).into(),
321 )
322 }
323 Some(entity_tys) => {
324 principal_types = Some(Node::with_source_loc(
325 entity_tys.iter().map(|n| n.clone().into()).collect(),
326 loc,
327 ))
328 }
329 },
330 },
331 Node {
332 node:
333 AppDecl::PR(PRAppDecl {
334 kind:
335 Node {
336 node: PR::Resource, ..
337 },
338 entity_tys,
339 }),
340 loc,
341 } => match resource_types {
342 Some(existing_tys) => {
343 return Err(
344 ToJsonSchemaError::duplicate_resource(name, existing_tys.loc, loc).into(),
345 );
346 }
347 None => match entity_tys {
348 None => {
349 return Err(
350 ToJsonSchemaError::empty_resource(name, name_loc.clone(), loc).into(),
351 )
352 }
353 Some(entity_tys) => {
354 resource_types = Some(Node::with_source_loc(
355 entity_tys.iter().map(|n| n.clone().into()).collect(),
356 loc,
357 ))
358 }
359 },
360 },
361 }
362 }
363 Ok(json_schema::ApplySpec {
364 resource_types: resource_types
365 .map(|node| node.node)
366 .ok_or_else(|| ToJsonSchemaError::no_resource(&name, name_loc.clone()))?,
367 principal_types: principal_types
368 .map(|node| node.node)
369 .ok_or_else(|| ToJsonSchemaError::no_principal(&name, name_loc.clone()))?,
370 context: context.map(|c| c.node).unwrap_or_default(),
371 })
372}
373
374fn convert_id(node: Node<Id>) -> Result<UnreservedId, ToJsonSchemaError> {
375 UnreservedId::try_from(node.node)
376 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), node.loc))
377}
378
379fn convert_entity_decl(
381 e: Annotated<Node<EntityDecl>>,
382) -> Result<
383 impl Iterator<Item = (UnreservedId, json_schema::EntityType<RawName>)>,
384 ToJsonSchemaErrors,
385> {
386 #[allow(clippy::needless_collect)]
388 let names: Vec<Node<Id>> = e.data.node.names().cloned().collect();
389 let etype = json_schema::EntityType {
390 kind: match e.data.node {
391 EntityDecl::Enum(d) => json_schema::EntityTypeKind::Enum {
392 choices: d.choices.map(|n| n.node),
393 },
394 EntityDecl::Standard(d) => {
395 json_schema::EntityTypeKind::Standard(json_schema::StandardEntityType {
397 member_of_types: d.member_of_types.into_iter().map(RawName::from).collect(),
398 shape: convert_attr_decls(d.attrs),
399 tags: d.tags.map(cedar_type_to_json_type),
400 })
401 }
402 },
403 annotations: e.annotations.into(),
404 loc: Some(e.data.loc.clone()),
405 };
406
407 collect_all_errors(
409 names
410 .into_iter()
411 .map(move |name| -> Result<_, ToJsonSchemaErrors> {
412 Ok((convert_id(name)?, etype.clone()))
413 }),
414 )
415}
416
417fn convert_attr_decls(
419 attrs: Node<impl IntoIterator<Item = Node<Annotated<AttrDecl>>>>,
420) -> json_schema::AttributesOrContext<RawName> {
421 json_schema::AttributesOrContext(json_schema::Type::Type {
422 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
423 attributes: attrs.node.into_iter().map(convert_attr_decl).collect(),
424 additional_attributes: false,
425 }),
426 loc: Some(attrs.loc),
427 })
428}
429
430fn convert_context_decl(
432 decl: Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>,
433) -> json_schema::AttributesOrContext<RawName> {
434 json_schema::AttributesOrContext(match decl {
435 Either::Left(p) => json_schema::Type::CommonTypeRef {
436 loc: Some(p.loc().clone()),
437 type_name: p.into(),
438 },
439 Either::Right(attrs) => json_schema::Type::Type {
440 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
441 attributes: attrs.node.into_iter().map(convert_attr_decl).collect(),
442 additional_attributes: false,
443 }),
444 loc: Some(attrs.loc),
445 },
446 })
447}
448
449fn convert_attr_decl(
451 attr: Node<Annotated<AttrDecl>>,
452) -> (SmolStr, json_schema::TypeOfAttribute<RawName>) {
453 (
454 attr.node.data.name.node,
455 json_schema::TypeOfAttribute {
456 ty: cedar_type_to_json_type(attr.node.data.ty),
457 required: attr.node.data.required,
458 annotations: attr.node.annotations.into(),
459 #[cfg(feature = "extended-schema")]
460 loc: Some(attr.loc),
461 },
462 )
463}
464
465fn collect_all_errors<A, E>(
469 iter: impl IntoIterator<Item = Result<A, E>>,
470) -> Result<impl Iterator<Item = A>, ToJsonSchemaErrors>
471where
472 E: IntoIterator<Item = ToJsonSchemaError>,
473{
474 let mut answers = vec![];
475 let mut errs = vec![];
476 for r in iter.into_iter() {
477 match r {
478 Ok(a) => {
479 answers.push(a);
480 }
481 Err(e) => {
482 let mut v = e.into_iter().collect::<Vec<_>>();
483 errs.append(&mut v)
484 }
485 }
486 }
487 match NonEmpty::collect(errs) {
488 None => Ok(answers.into_iter()),
489 Some(errs) => Err(ToJsonSchemaErrors::new(errs)),
490 }
491}
492
493#[derive(Default)]
494struct NamespaceRecord {
495 entities: HashMap<Id, Node<()>>,
496 common_types: HashMap<Id, Node<()>>,
497 loc: Option<Loc>,
498}
499
500impl NamespaceRecord {
501 fn new(namespace: &Namespace) -> Result<(Option<Name>, Self), ToJsonSchemaErrors> {
502 let ns = namespace
503 .name
504 .clone()
505 .map(|n| {
506 let internal_name = RawName::from(n.clone()).qualify_with(None); Name::try_from(internal_name)
508 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), n.loc().clone()))
509 })
510 .transpose()?;
511 let (entities, actions, types) = partition_decls(&namespace.decls);
512
513 let entities = collect_decls(
514 entities
515 .into_iter()
516 .flat_map(|decl| decl.names().cloned())
517 .map(extract_name),
518 )?;
519 collect_decls(
521 actions
522 .into_iter()
523 .flat_map(ActionDecl::names)
524 .map(extract_name),
525 )?;
526 let common_types = collect_decls(
527 types
528 .into_iter()
529 .flat_map(|decl| std::iter::once(decl.name.clone()))
530 .map(extract_name),
531 )?;
532
533 let record = NamespaceRecord {
534 entities,
535 common_types,
536 loc: namespace.name.as_ref().map(|n| n.loc().clone()),
537 };
538
539 Ok((ns, record))
540 }
541}
542
543fn collect_decls<N>(
544 i: impl Iterator<Item = (N, Node<()>)>,
545) -> Result<HashMap<N, Node<()>>, ToJsonSchemaErrors>
546where
547 N: std::cmp::Eq + std::hash::Hash + Clone + ToSmolStr,
548{
549 let mut map: HashMap<N, Node<()>> = HashMap::new();
550 for (key, node) in i {
551 match map.entry(key.clone()) {
552 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_decls(
553 &key,
554 entry.get().loc.clone(),
555 node.loc,
556 )),
557 Entry::Vacant(entry) => {
558 entry.insert(node);
559 Ok(())
560 }
561 }?;
562 }
563 Ok(map)
564}
565
566fn compute_namespace_warnings<'a>(
567 fragment: &'a HashMap<Option<Name>, NamespaceRecord>,
568 extensions: &'a Extensions<'a>,
569) -> impl Iterator<Item = SchemaWarning> + 'a {
570 fragment
571 .values()
572 .flat_map(move |nr| make_warning_for_shadowing(nr, extensions))
573}
574
575fn make_warning_for_shadowing<'a>(
576 n: &'a NamespaceRecord,
577 extensions: &'a Extensions<'a>,
578) -> impl Iterator<Item = SchemaWarning> + 'a {
579 let mut warnings = vec![];
580 for (common_name, common_src_node) in n.common_types.iter() {
581 if let Some(entity_src_node) = n.entities.get(common_name) {
583 let warning = schema_warnings::ShadowsEntityWarning {
584 name: common_name.to_smolstr(),
585 entity_loc: entity_src_node.loc.clone(),
586 common_loc: common_src_node.loc.clone(),
587 }
588 .into();
589 warnings.push(warning);
590 }
591 if let Some(warning) = shadows_builtin(common_name, common_src_node, extensions) {
593 warnings.push(warning);
594 }
595 }
596 let entity_shadows = n
597 .entities
598 .iter()
599 .filter_map(move |(name, node)| shadows_builtin(name, node, extensions));
600 warnings.into_iter().chain(entity_shadows)
601}
602
603fn extract_name<N: Clone>(n: Node<N>) -> (N, Node<()>) {
604 (n.node.clone(), n.map(|_| ()))
605}
606
607fn shadows_builtin(
608 name: &Id,
609 node: &Node<()>,
610 extensions: &Extensions<'_>,
611) -> Option<SchemaWarning> {
612 if is_valid_ext_type(name, extensions) || BUILTIN_TYPES.contains(&name.as_ref()) {
613 Some(
614 schema_warnings::ShadowsBuiltinWarning {
615 name: name.to_smolstr(),
616 loc: node.loc.clone(),
617 }
618 .into(),
619 )
620 } else {
621 None
622 }
623}
624
625fn build_namespace_bindings<'a>(
627 namespaces: impl Iterator<Item = &'a Namespace>,
628) -> Result<HashMap<Option<Name>, NamespaceRecord>, ToJsonSchemaErrors> {
629 let mut map = HashMap::new();
630 for (name, record) in collect_all_errors(namespaces.map(NamespaceRecord::new))? {
631 update_namespace_record(&mut map, name, record)?;
632 }
633 Ok(map)
634}
635
636fn update_namespace_record(
637 map: &mut HashMap<Option<Name>, NamespaceRecord>,
638 name: Option<Name>,
639 record: NamespaceRecord,
640) -> Result<(), ToJsonSchemaErrors> {
641 match map.entry(name.clone()) {
642 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_namespace(
643 &name.map_or("".into(), |n| n.to_smolstr()),
644 record.loc,
645 entry.get().loc.clone(),
646 )
647 .into()),
648 Entry::Vacant(entry) => {
649 entry.insert(record);
650 Ok(())
651 }
652 }
653}
654
655fn partition_decls(
656 decls: &[Annotated<Node<Declaration>>],
657) -> (Vec<&EntityDecl>, Vec<&ActionDecl>, Vec<&TypeDecl>) {
658 let mut entities = vec![];
659 let mut actions = vec![];
660 let mut types = vec![];
661
662 for decl in decls.iter() {
663 match &decl.data.node {
664 Declaration::Entity(e) => entities.push(e),
665 Declaration::Action(a) => actions.push(a),
666 Declaration::Type(t) => types.push(t),
667 }
668 }
669
670 (entities, actions, types)
671}
672
673#[allow(clippy::type_complexity)]
674fn into_partition_decls(
675 decls: impl IntoIterator<Item = Annotated<Node<Declaration>>>,
676) -> (
677 Vec<Annotated<Node<EntityDecl>>>,
678 Vec<Annotated<Node<ActionDecl>>>,
679 Vec<Annotated<Node<TypeDecl>>>,
680) {
681 let mut entities = vec![];
682 let mut actions = vec![];
683 let mut types = vec![];
684
685 for decl in decls.into_iter() {
686 let loc = decl.data.loc;
687 match decl.data.node {
688 Declaration::Entity(e) => entities.push(Annotated {
689 data: Node { node: e, loc },
690 annotations: decl.annotations,
691 }),
692 Declaration::Action(a) => actions.push(Annotated {
693 data: Node { node: a, loc },
694 annotations: decl.annotations,
695 }),
696 Declaration::Type(t) => types.push(Annotated {
697 data: Node { node: t, loc },
698 annotations: decl.annotations,
699 }),
700 }
701 }
702
703 (entities, actions, types)
704}
705
706#[cfg(test)]
707mod preserves_source_locations {
708 use super::*;
709 use cool_asserts::assert_matches;
710 use json_schema::{EntityType, EntityTypeKind};
711
712 #[test]
713 #[allow(clippy::cognitive_complexity)]
714 fn entity_action_and_common_type_decls() {
715 let (schema, _) = json_schema::Fragment::from_cedarschema_str(
716 r#"
717 namespace NS {
718 type S = String;
719 entity A;
720 entity B in A;
721 entity C in A {
722 bool: Bool,
723 s: S,
724 a: Set<A>,
725 b: { inner: B },
726 };
727 type AA = A;
728 action Read, Write;
729 action List in Read appliesTo {
730 principal: [A],
731 resource: [B, C],
732 context: {
733 s: Set<S>,
734 ab: { a: AA, b: B },
735 }
736 };
737 }
738 "#,
739 Extensions::all_available(),
740 )
741 .unwrap();
742 let ns = schema
743 .0
744 .get(&Some(Name::parse_unqualified_name("NS").unwrap()))
745 .expect("couldn't find namespace NS");
746
747 let entity_a = ns
748 .entity_types
749 .get(&"A".parse().unwrap())
750 .expect("couldn't find entity A");
751 let entity_b = ns
752 .entity_types
753 .get(&"B".parse().unwrap())
754 .expect("couldn't find entity B");
755 let entity_c = ns
756 .entity_types
757 .get(&"C".parse().unwrap())
758 .expect("couldn't find entity C");
759 let ctype_s = ns
760 .common_types
761 .get(&json_schema::CommonTypeId::new("S".parse().unwrap()).unwrap())
762 .expect("couldn't find common type S");
763 let ctype_aa = ns
764 .common_types
765 .get(&json_schema::CommonTypeId::new("AA".parse().unwrap()).unwrap())
766 .expect("couldn't find common type AA");
767 let action_read = ns.actions.get("Read").expect("couldn't find action Read");
768 let action_write = ns.actions.get("Write").expect("couldn't find action Write");
769 let action_list = ns.actions.get("List").expect("couldn't find action List");
770
771 assert_matches!(&entity_a.loc, Some(loc) => assert_matches!(loc.snippet(),
772 Some("entity A;")
773 ));
774 assert_matches!(&entity_b.loc, Some(loc) => assert_matches!(loc.snippet(),
775 Some("entity B in A;")
776 ));
777 assert_matches!(&entity_c.loc, Some(loc) => assert_matches!(loc.snippet(),
778 Some("entity C in A {\n bool: Bool,\n s: S,\n a: Set<A>,\n b: { inner: B },\n };")
779 ));
780 assert_matches!(&ctype_s.loc, Some(loc) => assert_matches!(loc.snippet(),
781 Some("type S = String;")
782 ));
783 assert_matches!(&ctype_aa.loc, Some(loc) => assert_matches!(loc.snippet(),
784 Some("type AA = A;")
785 ));
786 assert_matches!(&action_read.loc, Some(loc) => assert_matches!(loc.snippet(),
787 Some("action Read, Write;")
788 ));
789 assert_matches!(&action_write.loc, Some(loc) => assert_matches!(loc.snippet(),
790 Some("action Read, Write;")
791 ));
792 assert_matches!(&action_list.loc, Some(loc) => assert_matches!(loc.snippet(),
793 Some("action List in Read appliesTo {\n principal: [A],\n resource: [B, C],\n context: {\n s: Set<S>,\n ab: { a: AA, b: B },\n }\n };")
794 ));
795 }
796
797 #[test]
798 #[allow(clippy::cognitive_complexity)]
799 fn types() {
800 let (schema, _) = json_schema::Fragment::from_cedarschema_str(
801 r#"
802 namespace NS {
803 type S = String;
804 entity A;
805 entity B in A;
806 entity C in A {
807 bool: Bool,
808 s: S,
809 a: Set<A>,
810 b: { inner: B },
811 };
812 type AA = A;
813 action Read, Write;
814 action List in Read appliesTo {
815 principal: [A],
816 resource: [B, C],
817 context: {
818 s: Set<S>,
819 ab: { a: AA, b: B },
820 }
821 };
822 }
823 "#,
824 Extensions::all_available(),
825 )
826 .unwrap();
827 let ns = schema
828 .0
829 .get(&Some(Name::parse_unqualified_name("NS").unwrap()))
830 .expect("couldn't find namespace NS");
831
832 assert_matches!(ns
833 .entity_types
834 .get(&"C".parse().unwrap())
835 .expect("couldn't find entity C"), EntityType { kind: EntityTypeKind::Standard(entityC), ..} => {
836 assert_matches!(entityC.member_of_types.first().unwrap().loc(), Some(loc) => {
837 assert_matches!(loc.snippet(), Some("A"));
838 });
839 assert_matches!(entityC.shape.0.loc(), Some(loc) => {
840 assert_matches!(loc.snippet(), Some("{\n bool: Bool,\n s: S,\n a: Set<A>,\n b: { inner: B },\n }"));
841 });
842 assert_matches!(&entityC.shape.0, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(rty), .. } => {
843 let b = rty.attributes.get("bool").expect("couldn't find attribute `bool` on entity C");
844 assert_matches!(b.ty.loc(), Some(loc) => {
845 assert_matches!(loc.snippet(), Some("Bool"));
846 });
847 let s = rty.attributes.get("s").expect("couldn't find attribute `s` on entity C");
848 assert_matches!(s.ty.loc(), Some(loc) => {
849 assert_matches!(loc.snippet(), Some("S"));
850 });
851 let a = rty.attributes.get("a").expect("couldn't find attribute `a` on entity C");
852 assert_matches!(a.ty.loc(), Some(loc) => {
853 assert_matches!(loc.snippet(), Some("Set<A>"));
854 });
855 assert_matches!(&a.ty, json_schema::Type::Type { ty: json_schema::TypeVariant::Set { element }, .. } => {
856 assert_matches!(element.loc(), Some(loc) => {
857 assert_matches!(loc.snippet(), Some("A"));
858 });
859 });
860 let b = rty.attributes.get("b").expect("couldn't find attribute `b` on entity C");
861 assert_matches!(b.ty.loc(), Some(loc) => {
862 assert_matches!(loc.snippet(), Some("{ inner: B }"));
863 });
864 assert_matches!(&b.ty, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(b_rty), .. } => {
865 let inner = b_rty.attributes.get("inner").expect("couldn't find inner attribute");
866 assert_matches!(inner.ty.loc(), Some(loc) => {
867 assert_matches!(loc.snippet(), Some("B"));
868 });
869 });
870 });});
871
872 let ctype_aa = ns
873 .common_types
874 .get(&json_schema::CommonTypeId::new("AA".parse().unwrap()).unwrap())
875 .expect("couldn't find common type AA");
876 assert_matches!(ctype_aa.ty.loc(), Some(loc) => {
877 assert_matches!(loc.snippet(), Some("A"));
878 });
879
880 let action_list = ns.actions.get("List").expect("couldn't find action List");
881 assert_matches!(&action_list.applies_to, Some(appliesto) => {
882 assert_matches!(appliesto.principal_types.first().expect("principal types were empty").loc(), Some(loc) => {
883 assert_matches!(loc.snippet(), Some("A"));
884 });
885 assert_matches!(appliesto.resource_types.first().expect("resource types were empty").loc(), Some(loc) => {
886 assert_matches!(loc.snippet(), Some("B"));
887 });
888 assert_matches!(appliesto.context.loc(), Some(loc) => {
889 assert_matches!(loc.snippet(), Some("{\n s: Set<S>,\n ab: { a: AA, b: B },\n }"));
890 });
891 assert_matches!(&appliesto.context.0, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(rty), .. } => {
892 let s = rty.attributes.get("s").expect("couldn't find attribute `s` on context");
893 assert_matches!(s.ty.loc(), Some(loc) => {
894 assert_matches!(loc.snippet(), Some("Set<S>"));
895 });
896 let ab = rty.attributes.get("ab").expect("couldn't find attribute `ab` on context");
897 assert_matches!(ab.ty.loc(), Some(loc) => {
898 assert_matches!(loc.snippet(), Some("{ a: AA, b: B }"));
899 });
900 });
901 });
902 }
903}