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 };
144 (
145 qualified.into_iter(),
146 Some(Annotated {
147 data: unqual,
148 annotations: Annotations::new(),
149 }),
150 )
151 }
152}
153
154fn convert_namespace(
156 namespace: Annotated<Namespace>,
157) -> Result<(Option<Name>, json_schema::NamespaceDefinition<RawName>), ToJsonSchemaErrors> {
158 let ns_name = namespace
159 .data
160 .name
161 .clone()
162 .map(|p| {
163 let internal_name = RawName::from(p.clone()).qualify_with(None); Name::try_from(internal_name)
165 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), p.loc().clone()))
166 })
167 .transpose()?;
168 let def = namespace.try_into()?;
169 Ok((ns_name, def))
170}
171
172impl TryFrom<Annotated<Namespace>> for json_schema::NamespaceDefinition<RawName> {
173 type Error = ToJsonSchemaErrors;
174
175 fn try_from(
176 n: Annotated<Namespace>,
177 ) -> Result<json_schema::NamespaceDefinition<RawName>, Self::Error> {
178 let (entity_types, action, common_types) = into_partition_decls(n.data.decls);
180
181 let entity_types = collect_all_errors(entity_types.into_iter().map(convert_entity_decl))?
183 .flatten()
184 .collect();
185
186 let actions = collect_all_errors(action.into_iter().map(convert_action_decl))?
188 .flatten()
189 .collect();
190
191 let common_types = common_types
193 .into_iter()
194 .map(|decl| {
195 let name_loc = decl.data.node.name.loc.clone();
196 let id = UnreservedId::try_from(decl.data.node.name.node)
197 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), name_loc.clone()))?;
198 let ctid = json_schema::CommonTypeId::new(id)
199 .map_err(|e| ToJsonSchemaError::reserved_keyword(&e.id, name_loc))?;
200 Ok((
201 ctid,
202 CommonType {
203 ty: cedar_type_to_json_type(decl.data.node.def),
204 annotations: decl.annotations.into(),
205 loc: Some(decl.data.loc),
206 },
207 ))
208 })
209 .collect::<Result<_, ToJsonSchemaError>>()?;
210
211 Ok(json_schema::NamespaceDefinition {
212 common_types,
213 entity_types,
214 actions,
215 annotations: n.annotations.into(),
216 })
217 }
218}
219
220fn convert_action_decl(
222 a: Annotated<Node<ActionDecl>>,
223) -> Result<impl Iterator<Item = (SmolStr, json_schema::ActionType<RawName>)>, ToJsonSchemaErrors> {
224 let ActionDecl {
225 names,
226 parents,
227 app_decls,
228 } = a.data.node;
229 let applies_to = app_decls
231 .map(|decls| convert_app_decls(&names.first().node, &names.first().loc, decls))
232 .transpose()?
233 .unwrap_or_else(|| json_schema::ApplySpec {
234 resource_types: vec![],
235 principal_types: vec![],
236 context: json_schema::AttributesOrContext::default(),
237 });
238 let member_of = parents.map(|parents| parents.into_iter().map(convert_qual_name).collect());
239 let ty = json_schema::ActionType {
240 attributes: None, applies_to: Some(applies_to),
242 member_of,
243 annotations: a.annotations.into(),
244 loc: Some(a.data.loc),
245 };
246 Ok(names.into_iter().map(move |name| (name.node, ty.clone())))
248}
249
250fn convert_qual_name(qn: Node<QualName>) -> json_schema::ActionEntityUID<RawName> {
251 json_schema::ActionEntityUID::new(qn.node.path.map(Into::into), qn.node.eid)
252}
253
254fn convert_app_decls(
259 name: &SmolStr,
260 name_loc: &Loc,
261 decls: Node<NonEmpty<Node<AppDecl>>>,
262) -> Result<json_schema::ApplySpec<RawName>, ToJsonSchemaErrors> {
263 let (decls, _) = decls.into_inner();
265 let mut principal_types: Option<Node<Vec<RawName>>> = None;
266 let mut resource_types: Option<Node<Vec<RawName>>> = None;
267 let mut context: Option<Node<json_schema::AttributesOrContext<RawName>>> = None;
268
269 for decl in decls {
270 match decl {
271 Node {
272 node: AppDecl::Context(context_decl),
273 loc,
274 } => match context {
275 Some(existing_context) => {
276 return Err(ToJsonSchemaError::duplicate_context(
277 name,
278 existing_context.loc,
279 loc,
280 )
281 .into());
282 }
283 None => {
284 context = Some(Node::with_source_loc(
285 convert_context_decl(context_decl),
286 loc,
287 ));
288 }
289 },
290 Node {
291 node:
292 AppDecl::PR(PRAppDecl {
293 kind:
294 Node {
295 node: PR::Principal,
296 ..
297 },
298 entity_tys,
299 }),
300 loc,
301 } => match principal_types {
302 Some(existing_tys) => {
303 return Err(ToJsonSchemaError::duplicate_principal(
304 name,
305 existing_tys.loc,
306 loc,
307 )
308 .into());
309 }
310 None => {
311 principal_types = Some(Node::with_source_loc(
312 entity_tys.iter().map(|n| n.clone().into()).collect(),
313 loc,
314 ))
315 }
316 },
317 Node {
318 node:
319 AppDecl::PR(PRAppDecl {
320 kind:
321 Node {
322 node: PR::Resource, ..
323 },
324 entity_tys,
325 }),
326 loc,
327 } => match resource_types {
328 Some(existing_tys) => {
329 return Err(
330 ToJsonSchemaError::duplicate_resource(name, existing_tys.loc, loc).into(),
331 );
332 }
333 None => {
334 resource_types = Some(Node::with_source_loc(
335 entity_tys.iter().map(|n| n.clone().into()).collect(),
336 loc,
337 ))
338 }
339 },
340 }
341 }
342 Ok(json_schema::ApplySpec {
343 resource_types: resource_types
344 .map(|node| node.node)
345 .ok_or_else(|| ToJsonSchemaError::no_resource(&name, name_loc.clone()))?,
346 principal_types: principal_types
347 .map(|node| node.node)
348 .ok_or_else(|| ToJsonSchemaError::no_principal(&name, name_loc.clone()))?,
349 context: context.map(|c| c.node).unwrap_or_default(),
350 })
351}
352
353fn convert_id(node: Node<Id>) -> Result<UnreservedId, ToJsonSchemaError> {
354 UnreservedId::try_from(node.node)
355 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), node.loc))
356}
357
358fn convert_entity_decl(
360 e: Annotated<Node<EntityDecl>>,
361) -> Result<
362 impl Iterator<Item = (UnreservedId, json_schema::EntityType<RawName>)>,
363 ToJsonSchemaErrors,
364> {
365 let etype = json_schema::EntityType {
367 member_of_types: e
368 .data
369 .node
370 .member_of_types
371 .into_iter()
372 .map(RawName::from)
373 .collect(),
374 shape: convert_attr_decls(e.data.node.attrs),
375 tags: e.data.node.tags.map(cedar_type_to_json_type),
376 annotations: e.annotations.into(),
377 loc: Some(e.data.loc),
378 };
379
380 collect_all_errors(e.data.node.names.into_iter().map(
382 move |name| -> Result<_, ToJsonSchemaErrors> { Ok((convert_id(name)?, etype.clone())) },
383 ))
384}
385
386fn convert_attr_decls(
388 attrs: Node<impl IntoIterator<Item = Node<Annotated<AttrDecl>>>>,
389) -> json_schema::AttributesOrContext<RawName> {
390 json_schema::AttributesOrContext(json_schema::Type::Type {
391 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
392 attributes: attrs.node.into_iter().map(convert_attr_decl).collect(),
393 additional_attributes: false,
394 }),
395 loc: Some(attrs.loc),
396 })
397}
398
399fn convert_context_decl(
401 decl: Either<Path, Node<Vec<Node<Annotated<AttrDecl>>>>>,
402) -> json_schema::AttributesOrContext<RawName> {
403 json_schema::AttributesOrContext(match decl {
404 Either::Left(p) => json_schema::Type::CommonTypeRef {
405 loc: Some(p.loc().clone()),
406 type_name: p.into(),
407 },
408 Either::Right(attrs) => json_schema::Type::Type {
409 ty: json_schema::TypeVariant::Record(json_schema::RecordType {
410 attributes: attrs.node.into_iter().map(convert_attr_decl).collect(),
411 additional_attributes: false,
412 }),
413 loc: Some(attrs.loc),
414 },
415 })
416}
417
418fn convert_attr_decl(
420 attr: Node<Annotated<AttrDecl>>,
421) -> (SmolStr, json_schema::TypeOfAttribute<RawName>) {
422 (
423 attr.node.data.name.node,
424 json_schema::TypeOfAttribute {
425 ty: cedar_type_to_json_type(attr.node.data.ty),
426 required: attr.node.data.required,
427 annotations: attr.node.annotations.into(),
428 },
429 )
430}
431
432fn collect_all_errors<A, E>(
436 iter: impl IntoIterator<Item = Result<A, E>>,
437) -> Result<impl Iterator<Item = A>, ToJsonSchemaErrors>
438where
439 E: IntoIterator<Item = ToJsonSchemaError>,
440{
441 let mut answers = vec![];
442 let mut errs = vec![];
443 for r in iter.into_iter() {
444 match r {
445 Ok(a) => {
446 answers.push(a);
447 }
448 Err(e) => {
449 let mut v = e.into_iter().collect::<Vec<_>>();
450 errs.append(&mut v)
451 }
452 }
453 }
454 match NonEmpty::collect(errs) {
455 None => Ok(answers.into_iter()),
456 Some(errs) => Err(ToJsonSchemaErrors::new(errs)),
457 }
458}
459
460#[derive(Default)]
461struct NamespaceRecord {
462 entities: HashMap<Id, Node<()>>,
463 common_types: HashMap<Id, Node<()>>,
464 loc: Option<Loc>,
465}
466
467impl NamespaceRecord {
468 fn new(namespace: &Namespace) -> Result<(Option<Name>, Self), ToJsonSchemaErrors> {
469 let ns = namespace
470 .name
471 .clone()
472 .map(|n| {
473 let internal_name = RawName::from(n.clone()).qualify_with(None); Name::try_from(internal_name)
475 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), n.loc().clone()))
476 })
477 .transpose()?;
478 let (entities, actions, types) = partition_decls(&namespace.decls);
479
480 let entities = collect_decls(
481 entities
482 .into_iter()
483 .flat_map(|decl| decl.names.clone())
484 .map(extract_name),
485 )?;
486 collect_decls(
488 actions
489 .into_iter()
490 .flat_map(ActionDecl::names)
491 .map(extract_name),
492 )?;
493 let common_types = collect_decls(
494 types
495 .into_iter()
496 .flat_map(|decl| std::iter::once(decl.name.clone()))
497 .map(extract_name),
498 )?;
499
500 let record = NamespaceRecord {
501 entities,
502 common_types,
503 loc: namespace.name.as_ref().map(|n| n.loc().clone()),
504 };
505
506 Ok((ns, record))
507 }
508}
509
510fn collect_decls<N>(
511 i: impl Iterator<Item = (N, Node<()>)>,
512) -> Result<HashMap<N, Node<()>>, ToJsonSchemaErrors>
513where
514 N: std::cmp::Eq + std::hash::Hash + Clone + ToSmolStr,
515{
516 let mut map: HashMap<N, Node<()>> = HashMap::new();
517 for (key, node) in i {
518 match map.entry(key.clone()) {
519 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_decls(
520 &key,
521 entry.get().loc.clone(),
522 node.loc,
523 )),
524 Entry::Vacant(entry) => {
525 entry.insert(node);
526 Ok(())
527 }
528 }?;
529 }
530 Ok(map)
531}
532
533fn compute_namespace_warnings<'a>(
534 fragment: &'a HashMap<Option<Name>, NamespaceRecord>,
535 extensions: &'a Extensions<'a>,
536) -> impl Iterator<Item = SchemaWarning> + 'a {
537 fragment
538 .values()
539 .flat_map(move |nr| make_warning_for_shadowing(nr, extensions))
540}
541
542fn make_warning_for_shadowing<'a>(
543 n: &'a NamespaceRecord,
544 extensions: &'a Extensions<'a>,
545) -> impl Iterator<Item = SchemaWarning> + 'a {
546 let mut warnings = vec![];
547 for (common_name, common_src_node) in n.common_types.iter() {
548 if let Some(entity_src_node) = n.entities.get(common_name) {
550 let warning = schema_warnings::ShadowsEntityWarning {
551 name: common_name.to_smolstr(),
552 entity_loc: entity_src_node.loc.clone(),
553 common_loc: common_src_node.loc.clone(),
554 }
555 .into();
556 warnings.push(warning);
557 }
558 if let Some(warning) = shadows_builtin(common_name, common_src_node, extensions) {
560 warnings.push(warning);
561 }
562 }
563 let entity_shadows = n
564 .entities
565 .iter()
566 .filter_map(move |(name, node)| shadows_builtin(name, node, extensions));
567 warnings.into_iter().chain(entity_shadows)
568}
569
570fn extract_name<N: Clone>(n: Node<N>) -> (N, Node<()>) {
571 (n.node.clone(), n.map(|_| ()))
572}
573
574fn shadows_builtin(
575 name: &Id,
576 node: &Node<()>,
577 extensions: &Extensions<'_>,
578) -> Option<SchemaWarning> {
579 if is_valid_ext_type(name, extensions) || BUILTIN_TYPES.contains(&name.as_ref()) {
580 Some(
581 schema_warnings::ShadowsBuiltinWarning {
582 name: name.to_smolstr(),
583 loc: node.loc.clone(),
584 }
585 .into(),
586 )
587 } else {
588 None
589 }
590}
591
592fn build_namespace_bindings<'a>(
594 namespaces: impl Iterator<Item = &'a Namespace>,
595) -> Result<HashMap<Option<Name>, NamespaceRecord>, ToJsonSchemaErrors> {
596 let mut map = HashMap::new();
597 for (name, record) in collect_all_errors(namespaces.map(NamespaceRecord::new))? {
598 update_namespace_record(&mut map, name, record)?;
599 }
600 Ok(map)
601}
602
603fn update_namespace_record(
604 map: &mut HashMap<Option<Name>, NamespaceRecord>,
605 name: Option<Name>,
606 record: NamespaceRecord,
607) -> Result<(), ToJsonSchemaErrors> {
608 match map.entry(name.clone()) {
609 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_namespace(
610 &name.map_or("".into(), |n| n.to_smolstr()),
611 record.loc,
612 entry.get().loc.clone(),
613 )
614 .into()),
615 Entry::Vacant(entry) => {
616 entry.insert(record);
617 Ok(())
618 }
619 }
620}
621
622fn partition_decls(
623 decls: &[Annotated<Node<Declaration>>],
624) -> (Vec<&EntityDecl>, Vec<&ActionDecl>, Vec<&TypeDecl>) {
625 let mut entities = vec![];
626 let mut actions = vec![];
627 let mut types = vec![];
628
629 for decl in decls.iter() {
630 match &decl.data.node {
631 Declaration::Entity(e) => entities.push(e),
632 Declaration::Action(a) => actions.push(a),
633 Declaration::Type(t) => types.push(t),
634 }
635 }
636
637 (entities, actions, types)
638}
639
640fn into_partition_decls(
641 decls: impl IntoIterator<Item = Annotated<Node<Declaration>>>,
642) -> (
643 Vec<Annotated<Node<EntityDecl>>>,
644 Vec<Annotated<Node<ActionDecl>>>,
645 Vec<Annotated<Node<TypeDecl>>>,
646) {
647 let mut entities = vec![];
648 let mut actions = vec![];
649 let mut types = vec![];
650
651 for decl in decls.into_iter() {
652 let loc = decl.data.loc;
653 match decl.data.node {
654 Declaration::Entity(e) => entities.push(Annotated {
655 data: Node { node: e, loc },
656 annotations: decl.annotations,
657 }),
658 Declaration::Action(a) => actions.push(Annotated {
659 data: Node { node: a, loc },
660 annotations: decl.annotations,
661 }),
662 Declaration::Type(t) => types.push(Annotated {
663 data: Node { node: t, loc },
664 annotations: decl.annotations,
665 }),
666 }
667 }
668
669 (entities, actions, types)
670}
671
672#[cfg(test)]
673mod preserves_source_locations {
674 use super::*;
675 use cool_asserts::assert_matches;
676
677 #[test]
678 fn entity_action_and_common_type_decls() {
679 let (schema, _) = json_schema::Fragment::from_cedarschema_str(
680 r#"
681 namespace NS {
682 type S = String;
683 entity A;
684 entity B in A;
685 entity C in A {
686 bool: Bool,
687 s: S,
688 a: Set<A>,
689 b: { inner: B },
690 };
691 type AA = A;
692 action Read, Write;
693 action List in Read appliesTo {
694 principal: [A],
695 resource: [B, C],
696 context: {
697 s: Set<S>,
698 ab: { a: AA, b: B },
699 }
700 };
701 }
702 "#,
703 Extensions::all_available(),
704 )
705 .unwrap();
706 let ns = schema
707 .0
708 .get(&Some(Name::parse_unqualified_name("NS").unwrap()))
709 .expect("couldn't find namespace NS");
710
711 let entityA = ns
712 .entity_types
713 .get(&"A".parse().unwrap())
714 .expect("couldn't find entity A");
715 let entityB = ns
716 .entity_types
717 .get(&"B".parse().unwrap())
718 .expect("couldn't find entity B");
719 let entityC = ns
720 .entity_types
721 .get(&"C".parse().unwrap())
722 .expect("couldn't find entity C");
723 let ctypeS = ns
724 .common_types
725 .get(&json_schema::CommonTypeId::new("S".parse().unwrap()).unwrap())
726 .expect("couldn't find common type S");
727 let ctypeAA = ns
728 .common_types
729 .get(&json_schema::CommonTypeId::new("AA".parse().unwrap()).unwrap())
730 .expect("couldn't find common type AA");
731 let actionRead = ns.actions.get("Read").expect("couldn't find action Read");
732 let actionWrite = ns.actions.get("Write").expect("couldn't find action Write");
733 let actionList = ns.actions.get("List").expect("couldn't find action List");
734
735 assert_matches!(&entityA.loc, Some(loc) => assert_matches!(loc.snippet(),
736 Some("entity A;")
737 ));
738 assert_matches!(&entityB.loc, Some(loc) => assert_matches!(loc.snippet(),
739 Some("entity B in A;")
740 ));
741 assert_matches!(&entityC.loc, Some(loc) => assert_matches!(loc.snippet(),
742 Some("entity C in A {\n bool: Bool,\n s: S,\n a: Set<A>,\n b: { inner: B },\n };")
743 ));
744 assert_matches!(&ctypeS.loc, Some(loc) => assert_matches!(loc.snippet(),
745 Some("type S = String;")
746 ));
747 assert_matches!(&ctypeAA.loc, Some(loc) => assert_matches!(loc.snippet(),
748 Some("type AA = A;")
749 ));
750 assert_matches!(&actionRead.loc, Some(loc) => assert_matches!(loc.snippet(),
751 Some("action Read, Write;")
752 ));
753 assert_matches!(&actionWrite.loc, Some(loc) => assert_matches!(loc.snippet(),
754 Some("action Read, Write;")
755 ));
756 assert_matches!(&actionList.loc, Some(loc) => assert_matches!(loc.snippet(),
757 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 };")
758 ));
759 }
760
761 #[test]
762 fn types() {
763 let (schema, _) = json_schema::Fragment::from_cedarschema_str(
764 r#"
765 namespace NS {
766 type S = String;
767 entity A;
768 entity B in A;
769 entity C in A {
770 bool: Bool,
771 s: S,
772 a: Set<A>,
773 b: { inner: B },
774 };
775 type AA = A;
776 action Read, Write;
777 action List in Read appliesTo {
778 principal: [A],
779 resource: [B, C],
780 context: {
781 s: Set<S>,
782 ab: { a: AA, b: B },
783 }
784 };
785 }
786 "#,
787 Extensions::all_available(),
788 )
789 .unwrap();
790 let ns = schema
791 .0
792 .get(&Some(Name::parse_unqualified_name("NS").unwrap()))
793 .expect("couldn't find namespace NS");
794
795 let entityC = ns
796 .entity_types
797 .get(&"C".parse().unwrap())
798 .expect("couldn't find entity C");
799 assert_matches!(entityC.member_of_types.first().unwrap().loc(), Some(loc) => {
800 assert_matches!(loc.snippet(), Some("A"));
801 });
802 assert_matches!(entityC.shape.0.loc(), Some(loc) => {
803 assert_matches!(loc.snippet(), Some("{\n bool: Bool,\n s: S,\n a: Set<A>,\n b: { inner: B },\n }"));
804 });
805 assert_matches!(&entityC.shape.0, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(rty), .. } => {
806 let b = rty.attributes.get("bool").expect("couldn't find attribute `bool` on entity C");
807 assert_matches!(b.ty.loc(), Some(loc) => {
808 assert_matches!(loc.snippet(), Some("Bool"));
809 });
810 let s = rty.attributes.get("s").expect("couldn't find attribute `s` on entity C");
811 assert_matches!(s.ty.loc(), Some(loc) => {
812 assert_matches!(loc.snippet(), Some("S"));
813 });
814 let a = rty.attributes.get("a").expect("couldn't find attribute `a` on entity C");
815 assert_matches!(a.ty.loc(), Some(loc) => {
816 assert_matches!(loc.snippet(), Some("Set<A>"));
817 });
818 assert_matches!(&a.ty, json_schema::Type::Type { ty: json_schema::TypeVariant::Set { element }, .. } => {
819 assert_matches!(element.loc(), Some(loc) => {
820 assert_matches!(loc.snippet(), Some("A"));
821 });
822 });
823 let b = rty.attributes.get("b").expect("couldn't find attribute `b` on entity C");
824 assert_matches!(b.ty.loc(), Some(loc) => {
825 assert_matches!(loc.snippet(), Some("{ inner: B }"));
826 });
827 assert_matches!(&b.ty, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(b_rty), .. } => {
828 let inner = b_rty.attributes.get("inner").expect("couldn't find inner attribute");
829 assert_matches!(inner.ty.loc(), Some(loc) => {
830 assert_matches!(loc.snippet(), Some("B"));
831 });
832 });
833 });
834
835 let ctypeAA = ns
836 .common_types
837 .get(&json_schema::CommonTypeId::new("AA".parse().unwrap()).unwrap())
838 .expect("couldn't find common type AA");
839 assert_matches!(ctypeAA.ty.loc(), Some(loc) => {
840 assert_matches!(loc.snippet(), Some("A"));
841 });
842
843 let actionList = ns.actions.get("List").expect("couldn't find action List");
844 assert_matches!(&actionList.applies_to, Some(appliesto) => {
845 assert_matches!(appliesto.principal_types.first().expect("principal types were empty").loc(), Some(loc) => {
846 assert_matches!(loc.snippet(), Some("A"));
847 });
848 assert_matches!(appliesto.resource_types.first().expect("resource types were empty").loc(), Some(loc) => {
849 assert_matches!(loc.snippet(), Some("B"));
850 });
851 assert_matches!(appliesto.context.loc(), Some(loc) => {
852 assert_matches!(loc.snippet(), Some("{\n s: Set<S>,\n ab: { a: AA, b: B },\n }"));
853 });
854 assert_matches!(&appliesto.context.0, json_schema::Type::Type { ty: json_schema::TypeVariant::Record(rty), .. } => {
855 let s = rty.attributes.get("s").expect("couldn't find attribute `s` on context");
856 assert_matches!(s.ty.loc(), Some(loc) => {
857 assert_matches!(loc.snippet(), Some("Set<S>"));
858 });
859 let ab = rty.attributes.get("ab").expect("couldn't find attribute `ab` on context");
860 assert_matches!(ab.ty.loc(), Some(loc) => {
861 assert_matches!(loc.snippet(), Some("{ a: AA, b: B }"));
862 });
863 });
864 });
865 }
866}