163 lines
4.9 KiB
Rust
163 lines
4.9 KiB
Rust
extern crate proc_macro;
|
|
|
|
use convert_case::{Case, Casing};
|
|
use darling::{FromDeriveInput, FromField, ast::Data, util::Ignored};
|
|
use proc_macro::TokenStream;
|
|
use quote::{format_ident, quote};
|
|
use syn::{Attribute, DeriveInput, Generics, Ident, parse_macro_input};
|
|
|
|
#[derive(snafu::Snafu, Debug)]
|
|
enum GeneratorError {
|
|
#[snafu(transparent)]
|
|
Syn { source: syn::Error },
|
|
|
|
#[snafu(transparent)]
|
|
Darling { source: darling::Error },
|
|
}
|
|
|
|
impl GeneratorError {
|
|
fn write_errors(self) -> proc_macro2::TokenStream {
|
|
match self {
|
|
GeneratorError::Syn { source } => source.to_compile_error(),
|
|
GeneratorError::Darling { source } => source.write_errors(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, FromField)]
|
|
#[darling(attributes(dyngql))]
|
|
#[allow(dead_code)]
|
|
struct DynamicGraphqlFieldInfo {
|
|
ident: Option<Ident>,
|
|
ty: syn::Type,
|
|
}
|
|
|
|
#[derive(FromDeriveInput)]
|
|
#[darling(attributes(dyngql), forward_attrs(doc))]
|
|
#[allow(dead_code)]
|
|
struct DynamicGraphqlInfo {
|
|
pub ident: Ident,
|
|
pub attrs: Vec<Attribute>,
|
|
pub generics: Generics,
|
|
pub data: Data<Ignored, DynamicGraphqlFieldInfo>,
|
|
}
|
|
|
|
impl DynamicGraphqlInfo {
|
|
fn expand(&self) -> Result<TokenStream, GeneratorError> {
|
|
let struct_name = &self.ident;
|
|
let enum_name = format_ident!("{}FieldEnum", struct_name);
|
|
|
|
let fields = self.data.as_ref().take_struct().unwrap();
|
|
|
|
let enum_variants = fields
|
|
.iter()
|
|
.filter_map(|field| field.ident.as_ref())
|
|
.map(|field_ident| {
|
|
let variant_name = Ident::new(
|
|
&field_ident.to_string().to_case(Case::Pascal),
|
|
field_ident.span(),
|
|
);
|
|
quote! { #variant_name }
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let as_str_arms: Vec<_> = fields
|
|
.iter()
|
|
.filter_map(|field| field.ident.as_ref())
|
|
.map(|field_ident| {
|
|
let variant_name = Ident::new(
|
|
&field_ident.to_string().to_case(Case::Pascal),
|
|
field_ident.span(),
|
|
);
|
|
let field_name_str = field_ident.to_string().to_case(Case::Camel);
|
|
quote! {
|
|
Self::#variant_name => #field_name_str,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let from_str_arms: Vec<_> = fields
|
|
.iter()
|
|
.filter_map(|field| field.ident.as_ref())
|
|
.map(|field_ident| {
|
|
let variant_name = Ident::new(
|
|
&field_ident.to_string().to_case(Case::Pascal),
|
|
field_ident.span(),
|
|
);
|
|
let field_name_str = field_ident.to_string().to_case(Case::Camel);
|
|
quote! {
|
|
#field_name_str => Some(Self::#variant_name)
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
let all_field_names: Vec<_> = fields
|
|
.iter()
|
|
.filter_map(|field| field.ident.as_ref())
|
|
.map(|field_ident| field_ident.to_string().to_case(Case::Camel))
|
|
.collect();
|
|
|
|
let result = quote! {
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum #enum_name {
|
|
#(#enum_variants),*
|
|
}
|
|
|
|
impl #enum_name {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
#(#as_str_arms),*
|
|
}
|
|
}
|
|
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
match s {
|
|
#(#from_str_arms),* ,
|
|
_ => None
|
|
}
|
|
}
|
|
|
|
pub fn all_field_names() -> Vec<&'static str> {
|
|
vec![#(#all_field_names),*]
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Display for #enum_name {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
write!(f, "{}", self.as_str())
|
|
}
|
|
}
|
|
|
|
impl From<#enum_name> for String {
|
|
fn from(value: #enum_name) -> Self {
|
|
value.as_str().to_string()
|
|
}
|
|
}
|
|
|
|
impl std::str::FromStr for #enum_name {
|
|
type Err = String;
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
Self::from_str(s).ok_or_else(|| format!("Unknown field name: {s}"))
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
Ok(result.into())
|
|
}
|
|
}
|
|
|
|
#[proc_macro_derive(DynamicGraphql, attributes(dyngql))]
|
|
pub fn derive_dynamic_graphql(input: TokenStream) -> TokenStream {
|
|
let opts =
|
|
match DynamicGraphqlInfo::from_derive_input(&parse_macro_input!(input as DeriveInput)) {
|
|
Ok(opts) => opts,
|
|
Err(err) => return TokenStream::from(err.write_errors()),
|
|
};
|
|
match opts.expand() {
|
|
Ok(token_stream) => token_stream,
|
|
Err(err) => err.write_errors().into(),
|
|
}
|
|
}
|