scuffle_cedar_policy_codegen/
utils.rs1use cedar_policy_core::ast::Name;
2use cedar_policy_core::validator::RawName;
3use cedar_policy_core::validator::json_schema::{self, Fragment};
4use heck::{ToSnakeCase, ToUpperCamelCase};
5
6use crate::cedar_namespace::CedarNamespace;
7use crate::codegen::Codegen;
8use crate::error::CodegenError;
9use crate::types::{CedarRef, CedarType, CedarTypeStructField, NamespaceId};
10use crate::{CodegenResult, Config};
11
12pub(crate) fn create_namespace_id(id: Option<Name>) -> NamespaceId {
14 id.map(|id| {
15 let qualified_id = id.qualify_with(None);
16 NamespaceId {
17 items: qualified_id
18 .namespace_components()
19 .chain(std::iter::once(qualified_id.basename()))
20 .cloned()
21 .collect(),
22 }
23 })
24 .unwrap_or_default()
25}
26
27pub(crate) fn convert_record_type(record_type: &json_schema::RecordType<RawName>) -> CodegenResult<CedarType> {
29 let fields = record_type
30 .attributes
31 .iter()
32 .map(|(key, value)| {
33 let field = CedarTypeStructField {
34 optional: !value.required,
35 ty: convert_cedar_to_rust(&value.ty)?,
36 };
37 Ok((key.to_string(), field))
38 })
39 .collect::<CodegenResult<_>>()?;
40
41 Ok(CedarType::Record {
42 fields,
43 allows_additional: record_type.additional_attributes,
44 })
45}
46
47pub(crate) fn convert_cedar_to_rust(ty: &json_schema::Type<RawName>) -> CodegenResult<CedarType> {
49 match ty {
50 json_schema::Type::CommonTypeRef { type_name, .. } => Ok(CedarType::Reference(type_name.clone().into())),
51 json_schema::Type::Type { ty, .. } => convert_type_variant(ty),
52 }
53}
54
55pub(crate) fn convert_type_variant(ty: &json_schema::TypeVariant<RawName>) -> CodegenResult<CedarType> {
57 match ty {
58 json_schema::TypeVariant::Boolean => Ok(CedarType::Bool),
59 json_schema::TypeVariant::String => Ok(CedarType::String),
60 json_schema::TypeVariant::Long => Ok(CedarType::Long),
61 json_schema::TypeVariant::Entity { name } => Ok(CedarType::Reference(name.clone().into())),
62 json_schema::TypeVariant::EntityOrCommon { type_name } => Ok(CedarRef::from(type_name.clone()).into_cedar_ty()),
63 json_schema::TypeVariant::Extension { name } => Err(CodegenError::Unsupported(format!("extention type: {name}"))),
64 json_schema::TypeVariant::Record(record_type) => convert_record_type(record_type),
65 json_schema::TypeVariant::Set { element } => Ok(CedarType::Set(Box::new(convert_cedar_to_rust(element.as_ref())?))),
66 }
67}
68
69pub(crate) fn sanitize_identifier(s: impl AsRef<str>) -> String {
70 let ident = s.as_ref();
71 match ident {
72 "as" | "break" | "const" | "continue" | "else" | "enum" | "false" | "fn" | "for"
74 | "if" | "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" | "pub"
75 | "ref" | "return" | "static" | "struct" | "trait" | "true" | "type" | "unsafe"
76 | "use" | "where" | "while"
77 | "dyn"
79 | "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv"
81 | "typeof" | "unsized" | "virtual" | "yield"
82 | "async" | "await" | "try"
84 | "gen" => format!("r#{ident}"),
86 "_" | "super" | "self" | "Self" | "extern" | "crate" => format!("{ident}_"),
88 s if s.starts_with(|c: char| c.is_numeric()) => format!("_{ident}"),
90 _ => ident.to_string(),
91 }
92}
93
94pub(crate) fn to_snake_ident(s: impl AsRef<str>) -> syn::Ident {
96 syn::Ident::new(
97 &sanitize_identifier(s.as_ref().to_snake_case()),
98 proc_macro2::Span::call_site(),
99 )
100}
101
102pub(crate) fn to_upper_camel_ident(s: impl AsRef<str>) -> syn::Ident {
104 syn::Ident::new(
105 &sanitize_identifier(s.as_ref().to_upper_camel_case()),
106 proc_macro2::Span::call_site(),
107 )
108}
109
110pub(crate) fn find_relative_path(location: &[syn::Ident], dest: &[syn::Ident]) -> syn::Type {
112 let common_len = location.iter().zip(dest.iter()).take_while(|(a, b)| a == b).count();
113
114 let levels_up = location.len().saturating_sub(common_len);
115 let mut path_parts = Vec::new();
116 let super_ident = syn::Ident::new("super", proc_macro2::Span::call_site());
117
118 for _ in 0..levels_up {
120 path_parts.push(super_ident.clone());
121 }
122
123 path_parts.extend_from_slice(&dest[common_len..]);
125
126 if path_parts.is_empty() {
127 panic!("Invalid path calculation");
128 } else {
129 syn::parse_quote!(#(#path_parts)::*)
130 }
131}
132
133pub(crate) fn process_fragment(fragment: &Fragment<RawName>, config: &Config) -> CodegenResult<syn::File> {
134 let mut codegen = Codegen::new(config);
135
136 for (id, ns) in &fragment.0 {
137 let mut namespace = CedarNamespace::default();
138
139 for (id, ty) in &ns.common_types {
141 namespace.handle_common_type(id, ty)?;
142 }
143
144 for (id, ty) in &ns.entity_types {
146 namespace.handle_entity_type(id, ty)?;
147 }
148
149 for (action, ty) in &ns.actions {
151 namespace.handle_action(action, ty)?;
152 }
153
154 let namespace_id = create_namespace_id(id.clone());
155 codegen.add_namespace(namespace_id, namespace);
156 }
157
158 codegen.generate()
159}