use quote::quote;
use syn::{GenericArgument, LitStr, PathArguments, Type};
pub(crate) enum NavigationDiscriminant {
BelongsTo,
HasOne,
HasMany,
ManyToMany,
}
pub(crate) struct NavTypeInfo {
pub(crate) kind: NavigationDiscriminant,
pub(crate) related: syn::Type,
pub(crate) join: Option<syn::Type>,
}
pub(crate) fn type_ident_string(ty: &syn::Type) -> String {
if let syn::Type::Path(p) = ty {
p.path
.segments
.last()
.map(|s| s.ident.to_string())
.unwrap_or_else(|| "Entity".to_string())
} else {
"Entity".to_string()
}
}
fn is_unit_type(ty: &syn::Type) -> bool {
quote!(#ty).to_string().replace(' ', "") == "()"
}
pub(crate) fn detect_navigation_type(ty: &syn::Type) -> NavTypeInfo {
let unit_type: syn::Type = syn::parse_quote! { () };
if let Type::Path(type_path) = ty {
if let Some(segment) = type_path.path.segments.last() {
let ident = segment.ident.to_string();
let kind = if ident.starts_with("BelongsTo") {
NavigationDiscriminant::BelongsTo
} else if ident.starts_with("HasOne") {
NavigationDiscriminant::HasOne
} else if ident.starts_with("HasMany") {
NavigationDiscriminant::HasMany
} else {
NavigationDiscriminant::BelongsTo
};
let mut related_ty = unit_type.clone();
let mut join_ty = None;
if let PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(GenericArgument::Type(first)) = args.args.first() {
related_ty = first.clone();
}
if ident.starts_with("HasMany") {
if let Some(GenericArgument::Type(second)) = args.args.get(1) {
if !is_unit_type(second) {
join_ty = Some(second.clone());
}
}
}
}
let final_kind = if ident.starts_with("HasMany") && join_ty.is_some() {
NavigationDiscriminant::ManyToMany
} else {
kind
};
return NavTypeInfo {
kind: final_kind,
related: related_ty,
join: join_ty,
};
}
}
NavTypeInfo {
kind: NavigationDiscriminant::BelongsTo,
related: unit_type,
join: None,
}
}
pub(crate) fn extract_foreign_key_target(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("foreign_key") {
if let syn::Meta::List(list) = &attr.meta {
return Some(list.tokens.to_string().trim().to_string());
}
}
}
None
}
pub(crate) fn extract_through_type(attrs: &[syn::Attribute]) -> Option<syn::Type> {
for attr in attrs {
if attr.path().is_ident("through") {
if let syn::Meta::List(list) = &attr.meta {
let tokens = list.tokens.to_string();
return syn::parse_str::<syn::Type>(&tokens).ok();
}
}
}
None
}
pub(crate) fn extract_foreign_key_field_name(
_attrs: &[syn::Attribute],
) -> proc_macro2::TokenStream {
quote! { None }
}
pub(crate) fn extract_table_name(attrs: &[syn::Attribute]) -> String {
for attr in attrs {
if attr.path().is_ident("table") {
if let Ok(lit_str) = attr.parse_args::<LitStr>() {
return lit_str.value();
}
}
}
String::new()
}
pub(crate) fn extract_context_key(attrs: &[syn::Attribute]) -> Option<proc_macro2::TokenStream> {
for attr in attrs {
if attr.path().is_ident("context") {
if let Ok(lit_str) = attr.parse_args::<LitStr>() {
let key = lit_str.value();
return Some(quote! { ::core::option::Option::Some(#key) });
}
}
}
Some(quote! { ::core::option::Option::None })
}
pub(crate) fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
attrs.iter().any(|attr| attr.path().is_ident(name))
}
pub(crate) fn extract_column_name(attrs: &[syn::Attribute], field_name: &str) -> String {
for attr in attrs {
if attr.path().is_ident("column") {
if let Ok(lit_str) = attr.parse_args::<LitStr>() {
return lit_str.value();
}
}
}
field_name.to_string()
}
pub(crate) fn extract_sequence_name(attrs: &[syn::Attribute]) -> Option<String> {
for attr in attrs {
if attr.path().is_ident("sequence") {
if let Ok(lit_str) = attr.parse_args::<LitStr>() {
return Some(lit_str.value());
}
}
}
None
}
pub(crate) fn extract_on_delete(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
for attr in attrs {
if attr.path().is_ident("on_delete") {
let tokens = &attr.meta;
if let syn::Meta::List(list) = tokens {
let behavior: String = list.tokens.to_string().trim().to_string();
return match behavior.as_str() {
"Cascade" => quote! { Some(rust_ef::relations::DeleteBehavior::Cascade) },
"Restrict" => quote! { Some(rust_ef::relations::DeleteBehavior::Restrict) },
"SetNull" => quote! { Some(rust_ef::relations::DeleteBehavior::SetNull) },
"NoAction" => quote! { Some(rust_ef::relations::DeleteBehavior::NoAction) },
_ => quote! { None },
};
}
}
}
quote! { None }
}
pub(crate) fn extract_max_length(attrs: &[syn::Attribute]) -> proc_macro2::TokenStream {
for attr in attrs {
if attr.path().is_ident("max_length") {
if let Ok(lit_int) = attr.parse_args::<syn::LitInt>() {
let n: usize = lit_int.base10_parse().unwrap_or(0);
return quote! { Some(#n) };
}
}
}
quote! { None }
}
pub(crate) fn is_navigation_field(ty: &Type) -> bool {
let type_str = quote!(#ty).to_string();
type_str.contains("BelongsTo") || type_str.contains("HasMany") || type_str.contains("HasOne")
}
pub(crate) fn generate_parse_expr(
ty: &Type,
type_str: &str,
idx: syn::Index,
) -> proc_macro2::TokenStream {
if type_str.starts_with("Option <") || type_str.starts_with("Option<") {
let inner_str = if let Type::Path(type_path) = ty {
if let Some(seg) = type_path.path.segments.last() {
if let PathArguments::AngleBracketed(args) = &seg.arguments {
if let Some(GenericArgument::Type(inner_ty)) = args.args.first() {
let inner_type_str = quote!(#inner_ty).to_string();
let inner_parse =
generate_scalar_parse(&inner_type_str, inner_ty, idx.clone());
return quote! {
{
let v = &values[#idx];
if matches!(v, rust_ef::provider::DbValue::Null) {
None
} else {
Some(#inner_parse)
}
}
};
}
}
}
quote! { None }
} else {
quote! { None }
};
return inner_str;
}
generate_scalar_parse(type_str, ty, idx)
}
fn generate_scalar_parse(type_str: &str, _ty: &Type, idx: syn::Index) -> proc_macro2::TokenStream {
match type_str {
"i32" | "i 32" => quote! {
values[#idx].clone().try_into().unwrap_or(0i32)
},
"i64" | "i 64" => quote! {
values[#idx].clone().try_into().unwrap_or(0i64)
},
"i16" | "i 16" => quote! {
values[#idx].clone().try_into().unwrap_or(0i16)
},
"i8" | "i 8" => quote! {
values[#idx].clone().try_into().unwrap_or(0i8)
},
"u32" | "u 32" => quote! {
values[#idx].clone().try_into().unwrap_or(0u32)
},
"u64" | "u 64" => quote! {
values[#idx].clone().try_into().unwrap_or(0u64)
},
"f64" | "f 64" => quote! {
values[#idx].clone().try_into().unwrap_or(0.0f64)
},
"f32" | "f 32" => quote! {
values[#idx].clone().try_into().unwrap_or(0.0f32)
},
"bool" => quote! {
values[#idx].clone().try_into().unwrap_or(false)
},
"String" => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
"Vec < u8 >" | "Vec<u8>" => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ if cfg!(feature = "chrono") && type_str.contains("NaiveDateTime") => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ if cfg!(feature = "chrono") && type_str.contains("NaiveDate") => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ if cfg!(feature = "chrono") && type_str.contains("DateTime") => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ if cfg!(feature = "uuid") && type_str.contains("Uuid") => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ if cfg!(feature = "decimal") && type_str.contains("Decimal") => quote! {
values[#idx].clone().try_into().unwrap_or_default()
},
_ => {
quote! {
values[#idx].clone().try_into().unwrap_or_default()
}
}
}
}