use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields, GenericArgument, PathArguments, Type};
use crate::create_crate_ident;
pub fn derive_to_option(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident.clone();
let option_name = format_ident!("{}Option", struct_name);
let crate_ident = create_crate_ident("db_cores");
let root = if crate_ident == "crate" {
quote!(crate)
} else {
quote!(::#crate_ident)
};
let fields: Vec<&syn::Field> = if let Data::Struct(ds) = &input.data {
if let Fields::Named(named) = &ds.fields {
named.named.iter().collect()
} else {
panic!("DeriveOption only supports named fields")
}
} else {
panic!("DeriveOption only supports structs")
};
let option_fields = fields.iter().map(|f| {
let ident = &f.ident;
let ty = &f.ty;
let is_option = match ty {
| Type::Path(tp) => tp.path.segments.first().map(|seg| seg.ident == "Option").unwrap_or(false),
| _ => false,
};
let opt_ty = if is_option {
quote! { #ty } } else {
quote! { Option<#ty> } };
quote! { pub #ident: #opt_ty } });
let update_inserts: Vec<_> = fields
.iter()
.map(|f| {
let key = f.ident.as_ref().unwrap().to_string();
let ident = f.ident.as_ref().unwrap();
quote! {
if let Some(v) = t.#ident {
let sql_value: #root::SqlValue= v.into();
map.insert(#key.to_string(), sql_value);
}
}
})
.collect();
let primary_key_field = fields
.iter()
.find(|f| f.attrs.iter().any(|attr| attr.path().is_ident("primary_key")))
.and_then(|f| f.ident.as_ref().map(|i| i.to_string()));
let primary_key_name = primary_key_field.unwrap_or_else(|| "id".to_string());
let insert_inserts = fields.iter().map(|f| {
let key = f.ident.as_ref().unwrap().to_string();
let ident = f.ident.as_ref().unwrap();
if key == primary_key_name {
quote! {
let id_value = #root::utlis::nanoid2(None).into();
map.insert(#key.to_string(), id_value);
}
} else {
quote! {
let sql_value: #root::SqlValue = t.#ident.into();
map.insert(#key.to_string(), sql_value);
}
}
});
let match_key = fields.iter().map(|f| {
let ident = f.ident.as_ref().unwrap();
let field_name_str = ident.to_string();
let key_str = syn::LitStr::new(&field_name_str, ident.span());
let inner_type = get_inner_type(&f.ty);
quote! {
#key_str => {
let v: #inner_type = #root::serde_json::from_value(value.clone())?;
let sql_value: #root::SqlValue = v.into();
Ok(sql_value)
}
}
});
let expanded = quote! {
#[derive(Debug, #root::serde::Serialize, #root::serde::Deserialize, Clone, Default)]
pub struct #option_name {
#(#option_fields,)*
}
impl #struct_name {
pub fn to_sqlvalue(key_str:&str,value:&#root::serde_json::Value)->::anyhow::Result<#root::SqlValue> {
match key_str {
#(#match_key,)*
_ =>{
Err(::anyhow::anyhow!("ERROR to_sqlvalue 没有该字段 {}",key_str))
}
}
}
pub fn insert_map(value: &serde_json::Value)->anyhow::Result<std::collections::HashMap<String, #root::SqlValue>> {
let t = #root::serde_json::from_value::<#struct_name>(value.clone())?;
let mut map = std::collections::HashMap::new();
#(#insert_inserts)*
Ok(map)
}
pub fn update_map(value: &mut #root::serde_json::Value)->anyhow::Result<std::collections::HashMap<String, #root::SqlValue>> {
let mut map = std::collections::HashMap::new();
let mut null_fields:Vec<String> = Vec::new();
if let #root::serde_json::Value::Object(obj) = value {
obj.retain(|key, v| {
let is_null_like = v.is_null() || (v.is_string() && v.as_str() == Some("NULL"));
if is_null_like {
null_fields.push(key.clone());
}
!is_null_like
});
}
let t = #root::serde_json::from_value::<#option_name>(value.clone())?;
#(#update_inserts)*
for i in null_fields {
map.insert(i, #root::SqlValue::Null);
}
Ok(map)
}
pub fn where_map(value: &#root::serde_json::Value) -> ::anyhow::Result<Vec<#root::BuildConditionItem>> {
use #root::serde_json::Value;
#[derive(Debug, #root::serde::Serialize, #root::serde::Deserialize, PartialEq, Clone)]
struct ConditionItem {
column: String,
values: Value,
operator: #root::Operator,
logical: #root::Logical,
}
let conditions: Vec<ConditionItem> = serde_json::from_value(value.clone())
.map_err(|e| ::anyhow::anyhow!("where_map: 解析 JSON 失败: {e}"))?;
let mut result = Vec::with_capacity(conditions.len());
for i in conditions {
let field_name = i.column;
let where_value = if let Value::Array(arr) = i.values {
let mut values = vec![];
for v in arr {
let sql_value = Self::to_sqlvalue(&field_name, &v)?;
values.push(sql_value);
}
#root::WhereValue::List(values)
} else {
let sql_value = Self::to_sqlvalue(&field_name, &i.values)?;
#root::WhereValue::Value(sql_value)
};
let item = #root::BuildConditionItem {
column: field_name,
values: where_value,
operator: i.operator,
logical: i.logical
};
result.push(item);
}
Ok(result)
}
}
};
expanded.into()
}
fn get_inner_type(ty: &Type) -> &Type {
if let Type::Path(type_path) = ty {
if let Some(seg) = type_path.path.segments.last() {
if seg.ident == "Option" {
if let PathArguments::AngleBracketed(angle_bracketed) = &seg.arguments {
if let Some(GenericArgument::Type(inner_ty)) = angle_bracketed.args.first() {
return inner_ty;
}
}
}
}
}
ty
}