mod attr; mod error; pub mod iden_static; mod path; mod write_arm; use darling::FromMeta; use heck::ToSnakeCase; use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use std::convert::{TryFrom, TryInto}; use syn::{Attribute, DataEnum, DataStruct, DeriveInput, Fields, Ident, Variant}; use attr::IdenAttr; use error::ErrorMsg; use path::IdenPath; use write_arm::IdenVariant; struct DeriveIden; struct DeriveIdenStatic; pub fn expand(input: DeriveInput) -> TokenStream { let DeriveInput { ident, data, attrs, .. } = input; let table_name = match get_table_name(&ident, attrs) { Ok(v) => v, Err(e) => return e.to_compile_error().into(), }; // Currently we only support enums and unit structs let variants = match data { syn::Data::Enum(DataEnum { variants, .. }) => variants, syn::Data::Struct(DataStruct { fields: Fields::Unit, .. }) => return impl_iden_for_unit_struct(&ident, &table_name).into(), _ => return quote_spanned! { ident.span() => compile_error!("you can only derive Iden on enums or unit structs"); } .into(), }; if variants.is_empty() { return TokenStream::new(); } let output = impl_iden_for_enum(&ident, &table_name, variants.iter()); output.into() } fn impl_iden_for_unit_struct( ident: &proc_macro2::Ident, table_name: &str, ) -> proc_macro2::TokenStream { let sea_query_path = sea_query_path(); if is_static_iden(table_name) { quote! { impl #sea_query_path::Iden for #ident { fn quoted(&self) -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(#table_name) } fn unquoted(&self) -> &str { #table_name } } } } else { quote! { impl #sea_query_path::Iden for #ident { fn unquoted(&self) -> &str { #table_name } } } } } fn impl_iden_for_enum<'a, T>( ident: &proc_macro2::Ident, table_name: &str, variants: T, ) -> proc_macro2::TokenStream where T: Iterator, { let sea_query_path = sea_query_path(); let mut is_all_static_iden = true; let match_arms = match variants .map(|v| { let v = IdenVariant::::try_from((table_name, v))?; is_all_static_iden &= v.is_static_iden(); Ok(v) }) .collect::>>() { Ok(v) => v, Err(e) => return e.to_compile_error(), }; if is_all_static_iden { quote! { impl #sea_query_path::Iden for #ident { fn quoted(&self) -> std::borrow::Cow<'static, str> { std::borrow::Cow::Borrowed(match self { #(#match_arms),* }) } fn unquoted(&self) -> &str { match self { #(#match_arms),* } } } } } else { quote! { impl #sea_query_path::Iden for #ident { fn unquoted(&self) -> &str { match self { #(#match_arms),* } } } } } } fn sea_query_path() -> proc_macro2::TokenStream { if cfg!(feature = "sea-orm") { quote!(sea_orm::sea_query) } else { quote!(sea_query) } } pub struct NamingHolder { pub default: Ident, pub pascal: Ident, } #[derive(Debug, FromMeta)] pub struct GenEnumArgs { #[darling(default)] pub prefix: Option, #[darling(default)] pub suffix: Option, #[darling(default)] pub crate_name: Option, #[darling(default)] pub table_name: Option, } pub const DEFAULT_PREFIX: &str = ""; pub const DEFAULT_SUFFIX: &str = "Iden"; pub const DEFAULT_CRATE_NAME: &str = "sea_query"; impl Default for GenEnumArgs { fn default() -> Self { Self { prefix: Some(DEFAULT_PREFIX.to_string()), suffix: Some(DEFAULT_SUFFIX.to_string()), crate_name: Some(DEFAULT_CRATE_NAME.to_string()), table_name: None, } } } fn get_table_name(ident: &proc_macro2::Ident, attrs: Vec) -> Result { let table_name = match find_attr(&attrs) { Some(att) => match att.try_into()? { IdenAttr::Rename(lit) => lit, _ => return Err(syn::Error::new_spanned(att, ErrorMsg::ContainerAttr)), }, None => ident.to_string().to_snake_case(), }; Ok(table_name) } fn find_attr(attrs: &[Attribute]) -> Option<&Attribute> { attrs.iter().find(|attr| { attr.path().is_ident(&IdenPath::Iden) || attr.path().is_ident(&IdenPath::Method) }) } pub fn is_static_iden(name: &str) -> bool { // can only begin with [a-z_] name.chars() .take(1) .all(|c| c == '_' || c.is_ascii_alphabetic()) && name.chars().all(|c| c == '_' || c.is_ascii_alphanumeric()) }