use proc_macro2::TokenStream;
use quote::quote;
use super::seaorm::{
extract_sea_orm_default_value, has_sea_orm_primary_key, is_sql_function_default,
};
use super::type_utils;
use crate::parser::extract_default;
pub(super) fn generate_sea_orm_default_attrs(
original_attrs: &[syn::Attribute],
struct_name: &syn::Ident,
field_name: &str,
original_ty: &syn::Type,
field_ty: &dyn quote::ToTokens,
is_optional_or_partial: bool,
default_functions: &mut Vec<TokenStream>,
) -> (TokenStream, TokenStream) {
if is_optional_or_partial {
return (quote! {}, quote! {});
}
let default_value = extract_sea_orm_default_value(original_attrs);
let has_pk = has_sea_orm_primary_key(original_attrs);
if default_value.is_none() && !has_pk {
return (quote! {}, quote! {});
}
let has_existing_serde_default = extract_default(original_attrs).is_some();
match &default_value {
Some(value) if !is_sql_function_default(value) => {
let schema_default_attr = quote! { #[schema(default = #value)] };
if has_existing_serde_default {
return (quote! {}, schema_default_attr);
}
if !is_parseable_type(original_ty) {
return (quote! {}, schema_default_attr);
}
let fn_name = format!("default_{struct_name}_{field_name}");
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
let fn_body = match validate_literal_default(value, original_ty) {
Ok(()) => quote! { #value.parse().unwrap() },
Err(msg) => syn::Error::new_spanned(original_ty, msg).to_compile_error(),
};
default_functions.push(quote! {
#[allow(non_snake_case)]
fn #fn_ident() -> #field_ty {
#fn_body
}
});
let serde_default_attr = quote! { #[serde(default = #fn_name)] };
(serde_default_attr, schema_default_attr)
}
_ => {
let Some((default_expr, schema_default_str)) =
sql_function_default_for_type(original_ty)
else {
return (quote! {}, quote! {});
};
let schema_default_attr = quote! { #[schema(default = #schema_default_str)] };
if has_existing_serde_default {
return (quote! {}, schema_default_attr);
}
let fn_name = format!("default_{struct_name}_{field_name}");
let fn_ident = syn::Ident::new(&fn_name, proc_macro2::Span::call_site());
default_functions.push(quote! {
#[allow(non_snake_case)]
fn #fn_ident() -> #field_ty {
#default_expr
}
});
let serde_default_attr = quote! { #[serde(default = #fn_name)] };
(serde_default_attr, schema_default_attr)
}
}
}
fn sql_function_default_for_type(original_ty: &syn::Type) -> Option<(TokenStream, String)> {
let syn::Type::Path(type_path) = original_ty else {
return None;
};
let segment = type_path.path.segments.last()?;
let type_name = segment.ident.to_string();
match type_name.as_str() {
"DateTimeWithTimeZone" | "DateTimeUtc" | "DateTime" => {
let expr = quote! {
vespera::chrono::DateTime::<vespera::chrono::Utc>::UNIX_EPOCH.fixed_offset()
};
Some((expr, "1970-01-01T00:00:00+00:00".to_string()))
}
"NaiveDateTime" => {
let expr = quote! {
vespera::chrono::NaiveDateTime::UNIX_EPOCH
};
Some((expr, "1970-01-01T00:00:00".to_string()))
}
"NaiveDate" => {
let expr = quote! {
vespera::chrono::NaiveDate::default()
};
Some((expr, "1970-01-01".to_string()))
}
"NaiveTime" | "Time" => {
let expr = quote! {
vespera::chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap()
};
Some((expr, "00:00:00".to_string()))
}
"Uuid" => Some((
quote! { Default::default() },
"00000000-0000-0000-0000-000000000000".to_string(),
)),
"i8" | "i16" | "i32" | "i64" | "i128" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128"
| "usize" | "f32" | "f64" | "Decimal" => {
Some((quote! { Default::default() }, "0".to_string()))
}
"bool" => Some((quote! { Default::default() }, "false".to_string())),
"String" => Some((quote! { Default::default() }, String::new())),
_ => None,
}
}
pub(super) fn is_parseable_type(ty: &syn::Type) -> bool {
let syn::Type::Path(type_path) = ty else {
return false;
};
let Some(segment) = type_path.path.segments.last() else {
return false;
};
type_utils::PRIMITIVE_TYPE_NAMES.contains(&segment.ident.to_string().as_str())
}
fn validate_literal_default(value: &str, ty: &syn::Type) -> Result<(), String> {
let syn::Type::Path(type_path) = ty else {
return Ok(());
};
let Some(segment) = type_path.path.segments.last() else {
return Ok(());
};
let type_name = segment.ident.to_string();
macro_rules! check {
($t:ty) => {
value
.parse::<$t>()
.map(|_| ())
.map_err(|e| format!("invalid default_value {value:?} for `{type_name}`: {e}"))
};
}
match type_name.as_str() {
"i8" => check!(i8),
"i16" => check!(i16),
"i32" => check!(i32),
"i64" => check!(i64),
"i128" => check!(i128),
"isize" => check!(isize),
"u8" => check!(u8),
"u16" => check!(u16),
"u32" => check!(u32),
"u64" => check!(u64),
"u128" => check!(u128),
"usize" => check!(usize),
"f32" => check!(f32),
"f64" => check!(f64),
"bool" => check!(bool),
_ => Ok(()),
}
}
#[cfg(test)]
mod tests;