scuffle_cedar_policy_codegen/
utils.rs

1use 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
12/// Creates a NamespaceId from an optional raw name
13pub(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
27/// Converts record types from Cedar JSON schema
28pub(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
47/// Converts Cedar JSON schema types to internal CedarType representation
48pub(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
55/// Converts Cedar type variants to internal representation
56pub(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        // 2015 strict keywords
73        "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        // 2018 strict keywords
78        | "dyn"
79        // 2015 reserved keywords
80        | "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv"
81        | "typeof" | "unsized" | "virtual" | "yield"
82        // 2018 reserved keywords
83        | "async" | "await" | "try"
84        // 2024 reserved keywords
85        | "gen" => format!("r#{ident}"),
86        // Keywords not supported as raw identifiers
87        "_" | "super" | "self" | "Self" | "extern" | "crate" => format!("{ident}_"),
88        // Identifiers starting with numbers
89        s if s.starts_with(|c: char| c.is_numeric()) => format!("_{ident}"),
90        _ => ident.to_string(),
91    }
92}
93
94/// Converts an identifier to snake_case
95pub(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
102/// Converts an identifier to UpperCamelCase
103pub(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
110/// Finds the relative path between two locations
111pub(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    // Add super:: for each level up
119    for _ in 0..levels_up {
120        path_parts.push(super_ident.clone());
121    }
122
123    // Add remaining destination path parts
124    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        // Process common types
140        for (id, ty) in &ns.common_types {
141            namespace.handle_common_type(id, ty)?;
142        }
143
144        // Process entity types
145        for (id, ty) in &ns.entity_types {
146            namespace.handle_entity_type(id, ty)?;
147        }
148
149        // Process actions
150        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}