sea-orm-verify 0.1.0

verify sea-orm entities with sqlx compile time macros
Documentation
use proc_macro2::Ident;
use quote::quote;
use syn::{punctuated::Punctuated, token::Comma, DeriveInput, Fields, GenericArgument, Meta, PathArguments, Type};

/// Derive to verify sea-orm Entity with sqlx at compile time against your db
/// needs to have sqlx in your project dependencies as set DATABASE_URL or generate offline sqlx json data
#[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;
    //let DeriveInput { ident, data, attrs, .. } = parse_macro_input!(input as DeriveInput);

    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()
}
/// Parse field attributes
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 }
    }
}