use proc_macro2::Ident;
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, DeriveInput, Fields, GenericArgument, Meta, PathArguments, Type};
#[proc_macro_derive(Verify, attributes(verify))]
pub fn verify(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let DeriveInput { ident: ty, generics, data, attrs, .. } = syn::parse(input).unwrap();
let fields = filter_fields(match data {
syn::Data::Struct(ref s) => &s.fields,
_ => panic!("Field can only be derived for structs"),
});
let mut table_name = None;
let mut schema_name = None;
let mut table_iden = false;
attrs.iter().for_each(|attr| {
if attr.path().get_ident().map(|i| i == "sea_orm") != Some(true) {
return;
}
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
for meta in list.iter() {
if let Meta::NameValue(nv) = meta {
if let Some(ident) = nv.path.get_ident() {
if ident == "table_name" {
table_name = Some(nv.value.clone());
} else if ident == "schema_name" {
schema_name = Some(nv.value.clone());
}
}
} else if let Meta::Path(path) = meta {
if let Some(ident) = path.get_ident() {
if ident == "table_iden" {
table_iden = true;
}
}
}
}
}
});
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let mut fields_sql = vec![];
for field in &fields {
let name_str = field.ident.to_string();
let name = name_str.trim_start_matches("r#");
let r#type = &field.r#type;
let mut null_override = "";
if field.not_null {
null_override = "!";
} else if field.null {
null_override = "?";
}
if field.type_override {
if let Type::Path(type_path) = r#type {
if let Some(path) = type_path.path.get_ident() {
fields_sql.push(format!(r#"{} as "{}{}: {}""#, name, name, null_override, path));
} else {
let outer_type = type_path.path.segments[0].ident.to_string();
match outer_type.as_str() {
"Option" | "Vec" => fields_sql.push(format!(
r#"{} as "{}{}: {}""#,
name,
name,
null_override,
if let PathArguments::AngleBracketed(type_path) = &type_path.path.segments[0].arguments {
if let GenericArgument::Type(Type::Path(type_path)) = type_path.args.first().unwrap() {
if outer_type == "Vec" {
format!("Vec<{}>", type_path.path.get_ident().unwrap())
} else {
type_path.path.get_ident().unwrap().to_string()
}
} else {
panic!("unsupported type patch: {:?}", type_path);
}
} else {
panic!("unsupported type patch: {:?}", type_path);
}
)),
_ => panic!("unsupported type patch: {:?}", type_path),
}
}
} else {
panic!("unsupported field type: {:?}", r#type);
}
} else if !null_override.is_empty() {
fields_sql.push(format!(r#""{}" as "{}{}""#, name, name, null_override));
} else {
fields_sql.push(format!(r#""{}""#, name));
}
}
let fields_sql = fields_sql.join(", ");
let sql = if let Some(schema_name) = schema_name {
format!("SELECT {} FROM {}.{}", fields_sql, quote! { #schema_name }, quote! { #table_name })
} else {
format!("SELECT {} FROM {}", fields_sql, quote! { #table_name })
};
let tokens = quote! {
impl #impl_generics #ty #ty_generics
#where_clause
{
#[allow(unused_must_use)]
async fn _verify() {
sqlx::query_as!(Self, #sql);
}
}
};
tokens.into()
}
fn filter_fields(fields: &Fields) -> Vec<Field> {
fields
.iter()
.filter_map(|field| {
if field.ident.is_some() {
let mut type_override = false;
let mut not_null = false;
let mut null = false;
for attr in &field.attrs {
if attr.path().get_ident().map(|i| i == "verify") == Some(true) {
if let Ok(list) = attr.parse_args_with(Punctuated::<Meta, Comma>::parse_terminated) {
for meta in list.iter() {
if let Meta::Path(path) = meta {
if let Some(ident) = path.get_ident() {
if ident == "type_override" {
type_override = true;
} else if ident == "not_null" {
not_null = true;
} else if ident == "null" {
null = true;
}
}
}
}
}
}
}
let field_ident = field.ident.as_ref().unwrap().clone();
let field_ty = field.ty.clone();
if not_null && null {
panic!("not_null and null can not be set at the same time");
}
Some(Field::new(field_ident, field_ty, type_override, not_null, null))
} else {
None
}
})
.collect::<Vec<_>>()
}
#[derive(Debug)]
struct Field {
pub ident: Ident,
pub r#type: Type,
pub type_override: bool,
pub not_null: bool,
pub null: bool,
}
impl Field {
pub fn new(ident: Ident, r#type: Type, type_override: bool, not_null: bool, null: bool) -> Self {
Self { ident, r#type, type_override, not_null, null }
}
}