1use std::collections::HashMap;
20
21use cedar_policy_core::{
22 ast::{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, AppDecl, AttrDecl, Decl, Declaration, EntityDecl, Namespace, PRAppDecl, Path,
34 QualName, Schema, Type, TypeDecl, BUILTIN_TYPES, PR,
35 },
36 err::{schema_warnings, SchemaWarning, ToJsonSchemaError, ToJsonSchemaErrors},
37};
38use crate::{cedar_schema, json_schema, RawName};
39
40impl From<cedar_schema::Path> for RawName {
41 fn from(p: cedar_schema::Path) -> Self {
42 RawName::from_name(p.into())
43 }
44}
45
46pub fn cedar_schema_to_json_schema(
55 schema: Schema,
56 extensions: &Extensions<'_>,
57) -> Result<
58 (
59 json_schema::Fragment<RawName>,
60 impl Iterator<Item = SchemaWarning>,
61 ),
62 ToJsonSchemaErrors,
63> {
64 let (qualified_namespaces, unqualified_namespace) =
73 split_unqualified_namespace(schema.into_iter().map(|n| n.node));
74 let all_namespaces = qualified_namespaces
76 .chain(unqualified_namespace)
77 .collect::<Vec<_>>();
78
79 let names = build_namespace_bindings(all_namespaces.iter())?;
80 let warnings = compute_namespace_warnings(&names, extensions);
81 let fragment = collect_all_errors(all_namespaces.into_iter().map(convert_namespace))?.collect();
82 Ok((
83 json_schema::Fragment(fragment),
84 warnings.collect::<Vec<_>>().into_iter(),
85 ))
86}
87
88fn is_valid_ext_type(ty: &Id, extensions: &Extensions<'_>) -> bool {
90 extensions
91 .ext_types()
92 .filter(|ext_ty| ext_ty.as_ref().is_unqualified()) .any(|ext_ty| ty == ext_ty.basename_as_ref())
94}
95
96pub fn cedar_type_to_json_type(ty: Node<Type>) -> json_schema::Type<RawName> {
98 match ty.node {
99 Type::Set(t) => json_schema::Type::Type(json_schema::TypeVariant::Set {
100 element: Box::new(cedar_type_to_json_type(*t)),
101 }),
102 Type::Ident(p) => json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon {
103 type_name: RawName::from(p),
104 }),
105 Type::Record(fields) => {
106 json_schema::Type::Type(json_schema::TypeVariant::Record(json_schema::RecordType {
107 attributes: fields
108 .into_iter()
109 .map(|field| convert_attr_decl(field.node))
110 .collect(),
111 additional_attributes: false,
112 }))
113 }
114 }
115}
116
117fn split_unqualified_namespace(
120 namespaces: impl IntoIterator<Item = Namespace>,
121) -> (impl Iterator<Item = Namespace>, Option<Namespace>) {
122 let (qualified, unqualified): (Vec<_>, Vec<_>) =
124 namespaces.into_iter().partition(|n| n.name.is_some());
125
126 let mut unqualified_decls = vec![];
128 for mut unqualified_namespace in unqualified.into_iter() {
129 unqualified_decls.append(&mut unqualified_namespace.decls);
130 }
131
132 if unqualified_decls.is_empty() {
133 (qualified.into_iter(), None)
134 } else {
135 let unqual = Namespace {
136 name: None,
137 decls: unqualified_decls,
138 };
139 (qualified.into_iter(), Some(unqual))
140 }
141}
142
143fn convert_namespace(
145 namespace: Namespace,
146) -> Result<(Option<Name>, json_schema::NamespaceDefinition<RawName>), ToJsonSchemaErrors> {
147 let ns_name = namespace
148 .name
149 .clone()
150 .map(|p| {
151 let internal_name = RawName::from(p.node).qualify_with(None); Name::try_from(internal_name)
153 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), p.loc))
154 })
155 .transpose()?;
156 let def = namespace.try_into()?;
157 Ok((ns_name, def))
158}
159
160impl TryFrom<Namespace> for json_schema::NamespaceDefinition<RawName> {
161 type Error = ToJsonSchemaErrors;
162
163 fn try_from(n: Namespace) -> Result<json_schema::NamespaceDefinition<RawName>, Self::Error> {
164 let (entity_types, action, common_types) = into_partition_decls(n.decls);
166
167 let entity_types = collect_all_errors(entity_types.into_iter().map(convert_entity_decl))?
169 .flatten()
170 .collect();
171
172 let actions = collect_all_errors(action.into_iter().map(convert_action_decl))?
174 .flatten()
175 .collect();
176
177 let common_types = common_types
179 .into_iter()
180 .map(|decl| {
181 let name_loc = decl.name.loc.clone();
182 let id = UnreservedId::try_from(decl.name.node)
183 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), name_loc.clone()))?;
184 let ctid = json_schema::CommonTypeId::new(id)
185 .map_err(|e| ToJsonSchemaError::reserved_keyword(e.id, name_loc))?;
186 Ok((ctid, cedar_type_to_json_type(decl.def)))
187 })
188 .collect::<Result<_, ToJsonSchemaError>>()?;
189
190 Ok(json_schema::NamespaceDefinition {
191 common_types,
192 entity_types,
193 actions,
194 })
195 }
196}
197
198fn convert_action_decl(
200 a: ActionDecl,
201) -> Result<impl Iterator<Item = (SmolStr, json_schema::ActionType<RawName>)>, ToJsonSchemaErrors> {
202 let ActionDecl {
203 names,
204 parents,
205 app_decls,
206 } = a;
207 let applies_to = app_decls
209 .map(|decls| convert_app_decls(&names.first().node, &names.first().loc, decls))
210 .transpose()?
211 .unwrap_or_else(|| json_schema::ApplySpec {
212 resource_types: vec![],
213 principal_types: vec![],
214 context: json_schema::AttributesOrContext::default(),
215 });
216 let member_of = parents.map(|parents| parents.into_iter().map(convert_qual_name).collect());
217 let ty = json_schema::ActionType {
218 attributes: None, applies_to: Some(applies_to),
220 member_of,
221 };
222 Ok(names.into_iter().map(move |name| (name.node, ty.clone())))
224}
225
226fn convert_qual_name(qn: Node<QualName>) -> json_schema::ActionEntityUID<RawName> {
227 json_schema::ActionEntityUID::new(qn.node.path.map(Into::into), qn.node.eid)
228}
229
230fn convert_app_decls(
235 name: &SmolStr,
236 name_loc: &Loc,
237 decls: Node<NonEmpty<Node<AppDecl>>>,
238) -> Result<json_schema::ApplySpec<RawName>, ToJsonSchemaErrors> {
239 let (decls, _) = decls.into_inner();
241 let mut principal_types: Option<Node<Vec<RawName>>> = None;
242 let mut resource_types: Option<Node<Vec<RawName>>> = None;
243 let mut context: Option<Node<json_schema::AttributesOrContext<RawName>>> = None;
244
245 for decl in decls {
246 match decl {
247 Node {
248 node: AppDecl::Context(context_decl),
249 loc,
250 } => match context {
251 Some(existing_context) => {
252 return Err(ToJsonSchemaError::duplicate_context(
253 name.clone(),
254 existing_context.loc,
255 loc,
256 )
257 .into());
258 }
259 None => {
260 context = Some(Node::with_source_loc(
261 convert_context_decl(context_decl),
262 loc,
263 ));
264 }
265 },
266 Node {
267 node:
268 AppDecl::PR(PRAppDecl {
269 kind:
270 Node {
271 node: PR::Principal,
272 ..
273 },
274 entity_tys,
275 }),
276 loc,
277 } => match principal_types {
278 Some(existing_tys) => {
279 return Err(ToJsonSchemaError::duplicate_principal(
280 name.clone(),
281 existing_tys.loc,
282 loc,
283 )
284 .into());
285 }
286 None => {
287 principal_types = Some(Node::with_source_loc(
288 entity_tys.iter().map(|n| n.clone().into()).collect(),
289 loc,
290 ))
291 }
292 },
293 Node {
294 node:
295 AppDecl::PR(PRAppDecl {
296 kind:
297 Node {
298 node: PR::Resource, ..
299 },
300 entity_tys,
301 }),
302 loc,
303 } => match resource_types {
304 Some(existing_tys) => {
305 return Err(ToJsonSchemaError::duplicate_resource(
306 name.clone(),
307 existing_tys.loc,
308 loc,
309 )
310 .into());
311 }
312 None => {
313 resource_types = Some(Node::with_source_loc(
314 entity_tys.iter().map(|n| n.clone().into()).collect(),
315 loc,
316 ))
317 }
318 },
319 }
320 }
321 Ok(json_schema::ApplySpec {
322 resource_types: resource_types.map(|node| node.node).ok_or(
323 ToJsonSchemaError::no_resource(name.clone(), name_loc.clone()),
324 )?,
325 principal_types: principal_types.map(|node| node.node).ok_or(
326 ToJsonSchemaError::no_principal(name.clone(), name_loc.clone()),
327 )?,
328 context: context.map(|c| c.node).unwrap_or_default(),
329 })
330}
331
332fn convert_id(node: Node<Id>) -> Result<UnreservedId, ToJsonSchemaError> {
333 UnreservedId::try_from(node.node)
334 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), node.loc))
335}
336
337fn convert_entity_decl(
339 e: EntityDecl,
340) -> Result<
341 impl Iterator<Item = (UnreservedId, json_schema::EntityType<RawName>)>,
342 ToJsonSchemaErrors,
343> {
344 let etype = json_schema::EntityType {
346 member_of_types: e.member_of_types.into_iter().map(RawName::from).collect(),
347 shape: convert_attr_decls(e.attrs),
348 tags: e.tags.map(cedar_type_to_json_type),
349 };
350
351 collect_all_errors(
353 e.names
354 .into_iter()
355 .map(move |name| -> Result<_, ToJsonSchemaErrors> {
356 Ok((convert_id(name)?, etype.clone()))
357 }),
358 )
359}
360
361fn convert_attr_decls(
363 attrs: impl IntoIterator<Item = Node<AttrDecl>>,
364) -> json_schema::AttributesOrContext<RawName> {
365 json_schema::RecordType {
366 attributes: attrs
367 .into_iter()
368 .map(|attr| convert_attr_decl(attr.node))
369 .collect(),
370 additional_attributes: false,
371 }
372 .into()
373}
374
375fn convert_context_decl(
377 decl: Either<Path, Vec<Node<AttrDecl>>>,
378) -> json_schema::AttributesOrContext<RawName> {
379 json_schema::AttributesOrContext(match decl {
380 Either::Left(p) => json_schema::Type::CommonTypeRef {
381 type_name: p.into(),
382 },
383 Either::Right(attrs) => {
384 json_schema::Type::Type(json_schema::TypeVariant::Record(json_schema::RecordType {
385 attributes: attrs
386 .into_iter()
387 .map(|attr| convert_attr_decl(attr.node))
388 .collect(),
389 additional_attributes: false,
390 }))
391 }
392 })
393}
394
395fn convert_attr_decl(attr: AttrDecl) -> (SmolStr, json_schema::TypeOfAttribute<RawName>) {
397 (
398 attr.name.node,
399 json_schema::TypeOfAttribute {
400 ty: cedar_type_to_json_type(attr.ty),
401 required: attr.required,
402 },
403 )
404}
405
406fn collect_all_errors<A, E>(
410 iter: impl IntoIterator<Item = Result<A, E>>,
411) -> Result<impl Iterator<Item = A>, ToJsonSchemaErrors>
412where
413 E: IntoIterator<Item = ToJsonSchemaError>,
414{
415 let mut answers = vec![];
416 let mut errs = vec![];
417 for r in iter.into_iter() {
418 match r {
419 Ok(a) => {
420 answers.push(a);
421 }
422 Err(e) => {
423 let mut v = e.into_iter().collect::<Vec<_>>();
424 errs.append(&mut v)
425 }
426 }
427 }
428 match NonEmpty::collect(errs) {
429 None => Ok(answers.into_iter()),
430 Some(errs) => Err(ToJsonSchemaErrors::new(errs)),
431 }
432}
433
434#[derive(Default)]
435struct NamespaceRecord {
436 entities: HashMap<Id, Node<()>>,
437 common_types: HashMap<Id, Node<()>>,
438 loc: Option<Loc>,
439}
440
441impl NamespaceRecord {
442 fn new(namespace: &Namespace) -> Result<(Option<Name>, Self), ToJsonSchemaErrors> {
443 let ns = namespace
444 .name
445 .clone()
446 .map(|n| {
447 let internal_name = RawName::from(n.node).qualify_with(None); Name::try_from(internal_name)
449 .map_err(|e| ToJsonSchemaError::reserved_name(e.name(), n.loc))
450 })
451 .transpose()?;
452 let (entities, actions, types) = partition_decls(&namespace.decls);
453
454 let entities = collect_decls(
455 entities
456 .into_iter()
457 .flat_map(|decl| decl.names.clone())
458 .map(extract_name),
459 )?;
460 collect_decls(
462 actions
463 .into_iter()
464 .flat_map(ActionDecl::names)
465 .map(extract_name),
466 )?;
467 let common_types = collect_decls(
468 types
469 .into_iter()
470 .flat_map(|decl| std::iter::once(decl.name.clone()))
471 .map(extract_name),
472 )?;
473
474 let record = NamespaceRecord {
475 entities,
476 common_types,
477 loc: namespace.name.as_ref().map(|n| n.loc.clone()),
478 };
479
480 Ok((ns, record))
481 }
482}
483
484fn collect_decls<N>(
485 i: impl Iterator<Item = (N, Node<()>)>,
486) -> Result<HashMap<N, Node<()>>, ToJsonSchemaErrors>
487where
488 N: std::cmp::Eq + std::hash::Hash + Clone + ToSmolStr,
489{
490 let mut map: HashMap<N, Node<()>> = HashMap::new();
491 for (key, node) in i {
492 match map.entry(key.clone()) {
493 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_decls(
494 key,
495 entry.get().loc.clone(),
496 node.loc,
497 )),
498 Entry::Vacant(entry) => {
499 entry.insert(node);
500 Ok(())
501 }
502 }?;
503 }
504 Ok(map)
505}
506
507fn compute_namespace_warnings<'a>(
508 fragment: &'a HashMap<Option<Name>, NamespaceRecord>,
509 extensions: &'a Extensions<'a>,
510) -> impl Iterator<Item = SchemaWarning> + 'a {
511 fragment
512 .values()
513 .flat_map(move |nr| make_warning_for_shadowing(nr, extensions))
514}
515
516fn make_warning_for_shadowing<'a>(
517 n: &'a NamespaceRecord,
518 extensions: &'a Extensions<'a>,
519) -> impl Iterator<Item = SchemaWarning> + 'a {
520 let mut warnings = vec![];
521 for (common_name, common_src_node) in n.common_types.iter() {
522 if let Some(entity_src_node) = n.entities.get(common_name) {
524 let warning = schema_warnings::ShadowsEntityWarning {
525 name: common_name.to_smolstr(),
526 entity_loc: entity_src_node.loc.clone(),
527 common_loc: common_src_node.loc.clone(),
528 }
529 .into();
530 warnings.push(warning);
531 }
532 if let Some(warning) = shadows_builtin(common_name, common_src_node, extensions) {
534 warnings.push(warning);
535 }
536 }
537 let entity_shadows = n
538 .entities
539 .iter()
540 .filter_map(move |(name, node)| shadows_builtin(name, node, extensions));
541 warnings.into_iter().chain(entity_shadows)
542}
543
544fn extract_name<N: Clone>(n: Node<N>) -> (N, Node<()>) {
545 (n.node.clone(), n.map(|_| ()))
546}
547
548fn shadows_builtin(
549 name: &Id,
550 node: &Node<()>,
551 extensions: &Extensions<'_>,
552) -> Option<SchemaWarning> {
553 if is_valid_ext_type(name, extensions) || BUILTIN_TYPES.contains(&name.as_ref()) {
554 Some(
555 schema_warnings::ShadowsBuiltinWarning {
556 name: name.to_smolstr(),
557 loc: node.loc.clone(),
558 }
559 .into(),
560 )
561 } else {
562 None
563 }
564}
565
566fn build_namespace_bindings<'a>(
568 namespaces: impl Iterator<Item = &'a Namespace>,
569) -> Result<HashMap<Option<Name>, NamespaceRecord>, ToJsonSchemaErrors> {
570 let mut map = HashMap::new();
571 for (name, record) in collect_all_errors(namespaces.map(NamespaceRecord::new))? {
572 update_namespace_record(&mut map, name, record)?;
573 }
574 Ok(map)
575}
576
577fn update_namespace_record(
578 map: &mut HashMap<Option<Name>, NamespaceRecord>,
579 name: Option<Name>,
580 record: NamespaceRecord,
581) -> Result<(), ToJsonSchemaErrors> {
582 match map.entry(name.clone()) {
583 Entry::Occupied(entry) => Err(ToJsonSchemaError::duplicate_namespace(
584 name.map_or("".into(), |n| n.to_smolstr()),
585 record.loc,
586 entry.get().loc.clone(),
587 )
588 .into()),
589 Entry::Vacant(entry) => {
590 entry.insert(record);
591 Ok(())
592 }
593 }
594}
595
596fn partition_decls(
597 decls: &[Node<Declaration>],
598) -> (Vec<&EntityDecl>, Vec<&ActionDecl>, Vec<&TypeDecl>) {
599 let mut entities = vec![];
600 let mut actions = vec![];
601 let mut types = vec![];
602
603 for decl in decls.iter() {
604 match &decl.node {
605 Declaration::Entity(e) => entities.push(e),
606 Declaration::Action(a) => actions.push(a),
607 Declaration::Type(t) => types.push(t),
608 }
609 }
610
611 (entities, actions, types)
612}
613
614fn into_partition_decls(
615 decls: Vec<Node<Declaration>>,
616) -> (Vec<EntityDecl>, Vec<ActionDecl>, Vec<TypeDecl>) {
617 let mut entities = vec![];
618 let mut actions = vec![];
619 let mut types = vec![];
620
621 for decl in decls.into_iter() {
622 match decl.node {
623 Declaration::Entity(e) => entities.push(e),
624 Declaration::Action(a) => actions.push(a),
625 Declaration::Type(t) => types.push(t),
626 }
627 }
628
629 (entities, actions, types)
630}