use alibi_schema_registry::{self as registry, EntityRole};
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use syn::{FieldsNamed, Type};
#[must_use]
pub fn has_field(fields: &FieldsNamed, name: &str) -> bool {
fields
.named
.iter()
.any(|field| field.ident.as_ref().is_some_and(|ident| ident == name))
}
#[must_use]
pub fn is_option(ty: &Type) -> bool {
matches!(ty, Type::Path(path) if path.path.segments.last().is_some_and(|segment| segment.ident == "Option"))
}
#[must_use]
pub fn optional_field(fields: &FieldsNamed, name: &str) -> bool {
fields.named.iter().any(|field| {
field.ident.as_ref().is_some_and(|ident| ident == name) && is_option(&field.ty)
})
}
#[must_use]
pub fn is_known_field(role: EntityRole, name: &Ident) -> bool {
registry::core_field_names(role)
.iter()
.chain(registry::plugin_field_names(role).iter())
.any(|known| name == known)
}
#[must_use]
pub fn camel_case(name: &Ident) -> String {
let identifier = name.to_string();
let text = identifier.trim_start_matches("r#");
let mut components = text.split('_');
let mut camel = components.next().unwrap_or_default().to_owned();
for component in components {
let mut letters = component.chars();
if let Some(first) = letters.next() {
camel.extend(first.to_uppercase());
}
camel.extend(letters);
}
camel
}
#[derive(Clone)]
pub struct EntityField {
pub ident: Ident,
pub camel: String,
pub physical: Option<String>,
}
#[must_use]
pub fn additional_output(
role: EntityRole,
fields: &[EntityField],
core_root: &TokenStream,
) -> TokenStream {
let mut output = Vec::new();
for field in fields {
let name = &field.ident;
let camel = &field.camel;
let physical_output = !is_known_field(role, name)
|| matches!(role, EntityRole::User)
&& (name == "username" || name == "display_username");
if physical_output {
output.push(quote! {
if let Ok(value) = #core_root::utils::json::to_value(&self.#name) {
_ = fields.insert(#camel.into(), value);
}
});
}
if let Some(physical) = field
.physical
.as_ref()
.filter(|physical| *physical != camel)
&& physical_output
{
output.push(quote! {
if let Ok(value) = #core_root::utils::json::to_value(&self.#name) {
_ = fields.insert(#physical.into(), value);
}
});
}
}
quote! {
fn additional_fields(&self) -> #core_root::field_policy::FieldOutput {
let mut fields = #core_root::field_policy::FieldOutput::new();
#(#output)*
fields
}
}
}
#[must_use]
pub fn is_auth_timestamp(name: &str) -> bool {
matches!(
name,
"created_at"
| "updated_at"
| "expires_at"
| "ban_expires"
| "access_token_expires_at"
| "refresh_token_expires_at"
)
}