alibi_entity_codegen/
fields.rs1use alibi_schema_registry::{self as registry, EntityRole};
2use proc_macro2::{Ident, TokenStream};
3use quote::quote;
4use syn::{FieldsNamed, Type};
5
6#[must_use]
7pub fn has_field(fields: &FieldsNamed, name: &str) -> bool {
8 fields
9 .named
10 .iter()
11 .any(|field| field.ident.as_ref().is_some_and(|ident| ident == name))
12}
13
14#[must_use]
15pub fn is_option(ty: &Type) -> bool {
16 matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "Option"))
17}
18
19#[must_use]
21pub fn optional_field(fields: &FieldsNamed, name: &str) -> bool {
22 fields.named.iter().any(|field| {
23 field.ident.as_ref().is_some_and(|ident| ident == name) && is_option(&field.ty)
24 })
25}
26
27#[must_use]
29pub fn is_known_field(role: EntityRole, name: &Ident) -> bool {
30 registry::core_field_names(role)
31 .iter()
32 .chain(registry::plugin_field_names(role).iter())
33 .any(|known| name == known)
34}
35
36#[must_use]
38pub fn camel_case(name: &Ident) -> String {
39 let identifier = name.to_string();
40 let text = identifier.trim_start_matches("r#");
41 let mut components = text.split('_');
42 let mut camel = components.next().unwrap_or_default().to_owned();
43 for component in components {
44 let mut letters = component.chars();
45 if let Some(first) = letters.next() {
46 camel.extend(first.to_uppercase());
47 }
48 camel.extend(letters);
49 }
50 camel
51}
52
53#[derive(Clone)]
55pub struct EntityField {
56 pub ident: Ident,
57 pub camel: String,
58 pub physical: Option<String>,
60}
61
62#[must_use]
66pub fn additional_output(
67 role: EntityRole,
68 fields: &[EntityField],
69 core_root: &TokenStream,
70) -> TokenStream {
71 let mut output = Vec::new();
72 for field in fields {
73 let name = &field.ident;
74 let camel = &field.camel;
75 let physical_output = !is_known_field(role, name)
79 || matches!(role, EntityRole::User)
80 && (name == "username" || name == "display_username");
81 if physical_output {
82 output.push(quote! {
83 if let Ok(value) = #core_root::utils::json::to_value(&self.#name) {
84 _ = fields.insert(#camel.into(), value);
85 }
86 });
87 }
88 if let Some(physical) = field
89 .physical
90 .as_ref()
91 .filter(|physical| *physical != camel)
92 && physical_output
93 {
94 output.push(quote! {
95 if let Ok(value) = #core_root::utils::json::to_value(&self.#name) {
96 _ = fields.insert(#physical.into(), value);
97 }
98 });
99 }
100 }
101 quote! {
102 fn additional_fields(&self) -> #core_root::field_policy::FieldOutput {
103 let mut fields = #core_root::field_policy::FieldOutput::new();
104 #(#output)*
105 fields
106 }
107 }
108}
109
110#[must_use]
112pub fn is_auth_timestamp(name: &str) -> bool {
113 matches!(
114 name,
115 "created_at"
116 | "updated_at"
117 | "expires_at"
118 | "ban_expires"
119 | "access_token_expires_at"
120 | "refresh_token_expires_at"
121 )
122}