use proc_macro::{TokenStream, TokenTree};
use quote::{quote};
use syn::{parse_macro_input, DeriveInput, Data, DataEnum, Ident, Variant, Fields};
static BASE_TYPES: &'static [&str] = &[
"i8", "i16", "i32", "i64", "isize", "u8", "u16", "u32", "usize", "f32",
"bool", "String", "&str", "char",
];
enum ConvertType {
As,
BigInt,
}
static CONVERT_TYPES: &'static [(&str, ConvertType)] = &[
("f64", ConvertType::As),
("f32", ConvertType::As),
("BigInt", ConvertType::BigInt),
];
#[proc_macro_derive(FromToNapi, attributes(module_path_str, proto_oneof, native_proc))]
pub fn from_type_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let napi_type = &ast.ident;
let napi_str = napi_type.to_string();
let last_split = &napi_str.split("_").last().expect("Type cannot be found");
let original_type = &syn::Ident::new(
last_split,
ast.ident.span()
);
let mut fields_from: Vec<quote::__private::TokenStream> = Vec::new();
let mut fields_to: Vec<quote::__private::TokenStream> = Vec::new();
let mut types: Vec<quote::__private::TokenStream> = Vec::new();
match ast.data {
syn::Data::Struct(struct_data) => {
for field in struct_data.fields {
let field_ident = field.ident.expect("No ident in field");
match field.ty {
syn::Type::Path(type_path) => {
let last_segment = type_path.path.segments.last().expect("Failed to get last segment");
let last_segment_name = last_segment.ident.to_string();
let last_segment_str = last_segment_name.as_str();
let mut is_base_type = false;
match &last_segment.arguments {
syn::PathArguments::AngleBracketed(bracketed) => {
match &bracketed.args[0] {
syn::GenericArgument::Type(generic_type) => {
match generic_type {
syn::Type::Path(path_type) => {
let final_name = path_type.path.segments.last().expect("Failed to get last segment").ident.to_string();
if BASE_TYPES.iter().any(|&i| i == final_name) {
is_base_type = true;
}
}
_ => todo!("Only implemented Type::Path")
}
}
_ => todo!("Only implemented GenericArgument::Type")
}
}
_ => { }
}
let value_assign_from = quote! { value.#field_ident.into() };
let mut value_assign_to = quote! { value.#field_ident.into() };
let convert_type = CONVERT_TYPES.iter().find(|&i| i.0 == last_segment_str);
if let Some(val) = convert_type {
let type_stream = get_original_type_attr(&field.attrs);
match val.1 {
ConvertType::As => {
value_assign_to = quote! { (value.#field_ident as #type_stream).into() };
}
ConvertType::BigInt => {
value_assign_to = quote! { (value.#field_ident.get_i128().1 as #type_stream).into() };
}
}
}
let base_field_from = quote! {
#field_ident: #value_assign_from,
};
let base_field_to = quote! {
#field_ident: #value_assign_to,
};
if !is_base_type {
match last_segment_str {
"Option" => {
let convert = quote! {
#field_ident: prost_types::convert_option(value.#field_ident),
};
fields_from.push(convert.clone());
fields_to.push(convert);
}
"Vec" => {
let convert = quote! {
#field_ident: prost_types::convert_vec(value.#field_ident),
};
fields_from.push(convert.clone());
fields_to.push(convert);
}
_ => {
fields_from.push(base_field_from);
fields_to.push(base_field_to);
}
}
} else {
fields_from.push(base_field_from);
fields_to.push(base_field_to);
}
types.push(quote! {#field_ident});
}
_ => todo!("Field type can only be of Type::Path")
}
}
}
_ => panic!("FromToNapi can only be applied to Struct types")
}
let is_proto_oneof = get_oneof_attr(&ast.attrs);
let expanded_struct_from = if is_proto_oneof {
impl_enum_from_to(&ast.attrs, original_type, napi_type, types)
} else {
impl_struct_from_to(&ast.attrs, original_type, napi_type, fields_from, fields_to)
};
expanded_struct_from
}
fn impl_struct_from_to(attrs: &Vec<syn::Attribute>, original_type: &syn::Ident, napi_type: &syn::Ident,
fields_from: Vec<quote::__private::TokenStream>, fields_to: Vec<quote::__private::TokenStream>) -> TokenStream {
let module_str = get_module_attr(attrs);
let original_path = format!("{}{}", module_str, quote!(#original_type));
let original_type: quote::__private::TokenStream = original_path.parse().unwrap();
let expanded = quote! {
impl From<#original_type> for #napi_type {
fn from(value: #original_type) -> #napi_type {
#napi_type {
#(#fields_from)*
}
}
}
impl From<#napi_type> for #original_type {
fn from(value: #napi_type) -> #original_type {
#original_type {
#(#fields_to)*
}
}
}
};
TokenStream::from(expanded)
}
fn impl_enum_from_to(attrs: &Vec<syn::Attribute>, original_type: &syn::Ident, napi_type: &syn::Ident, types: Vec<quote::__private::TokenStream>) -> TokenStream {
let module_str = get_module_attr(attrs);
let original_path = format!("{}{}", module_str, quote!(#original_type));
let original_type: quote::__private::TokenStream = original_path.parse().unwrap();
let expanded = quote! {
impl From<#original_type> for #napi_type {
fn from(value: #original_type) -> Self {
match value {
#(#original_type::#types(value) => {
let mut ret = #napi_type::default();
ret.#types = Some(value.into());
ret
})*
}
}
}
impl From<#napi_type> for #original_type {
fn from(value: #napi_type) -> Self {
#(if value.#types.is_some() {
return #original_type::#types(value.#types.expect("???").into());
})*
return #napi_type::default().into();
}
}
};
TokenStream::from(expanded)
}
fn get_oneof_attr(attrs: &Vec<syn::Attribute>) -> bool {
for attr in attrs.iter() {
match &attr.meta {
syn::Meta::NameValue(name) => {
for segment in name.path.segments.iter() {
if segment.ident == "proto_oneof" {
match &name.value {
syn::Expr::Lit(value) => {
match &value.lit {
syn::Lit::Bool(bool_val) => {
return bool_val.value();
}
_ => panic!("Expected a bool in proto_oneof value")
}
}
_ => panic!("Expected a literal expression in proto_oneof")
}
}
}
}
_ => { }
}
}
false
}
fn get_module_attr(attrs: &Vec<syn::Attribute>) -> String {
for attr in attrs.iter() {
match &attr.meta {
syn::Meta::NameValue(name) => {
for segment in name.path.segments.iter() {
if segment.ident == "module_path_str" {
match &name.value {
syn::Expr::Lit(value) => {
match &value.lit {
syn::Lit::Str(string_val) => {
let token = format!("crate::{}", string_val.value());
return token;
}
_ => panic!("Expected a str in module_path_str value")
}
}
_ => panic!("Expected a literal expression in module_path_str")
}
}
}
}
_ => { }
}
}
return "crate::".to_string();
}
fn get_original_type_attr(attrs: &Vec<syn::Attribute>) -> quote::__private::TokenStream {
for attr in attrs.iter() {
match &attr.meta {
syn::Meta::List(list) => {
let mut is_field_og = false;
let tokenstream = TokenStream::from(list.tokens.clone());
for token in tokenstream {
match token {
TokenTree::Ident(ident) => {
if is_field_og {
return ident.to_string().parse().unwrap();
} else if ident.to_string() == "field_og" {
is_field_og = true;
}
}
_ => { }
}
}
}
_ => { }
}
}
return "".parse().unwrap();
}
#[proc_macro_derive(EnumConversions, attributes(module_path_str))]
pub fn enum_conversion(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let napi_type = &ast.ident;
let napi_str = napi_type.to_string();
let last_split = &napi_str.split("_").last().expect("Type cannot be found");
let original_type = &syn::Ident::new(
last_split,
ast.ident.span()
);
let punctuated_variants = match ast.data {
Data::Enum(DataEnum { variants, .. }) => variants,
Data::Struct(_) => panic!("Enumeration can not be derived for a struct"),
Data::Union(..) => panic!("Enumeration can not be derived for a union"),
};
let mut variants: Vec<(Ident, i32)> = Vec::new();
for (i, Variant {
ident,
fields,
..
}) in punctuated_variants.iter().enumerate()
{
match fields {
Fields::Unit => (),
Fields::Named(_) | Fields::Unnamed(_) => {
panic!("Enumeration variants may not have fields")
}
}
variants.push((ident.clone(), i as i32))
}
if variants.is_empty() {
panic!("Enumeration must have at least one variant");
}
let from = variants.iter().map(
|&(ref variant, ref value)| quote!(#value => ::core::option::Option::Some(#napi_type::#variant)),
);
let module_str = get_module_attr(&ast.attrs);
let original_path = format!("{}{}", module_str, quote!(#original_type));
let original_type: quote::__private::TokenStream = original_path.parse().unwrap();
let to_str = &syn::Ident::new(&format!("{}_to_str", quote!(#napi_type)), ast.ident.span());
let from_str = &syn::Ident::new(&format!("{}_from_str", quote!(#napi_type)), ast.ident.span());
let expanded = quote! {
impl #napi_type {
fn from_i32(val: i32) -> std::option::Option<Self> {
match val {
#(#from,)*
_ => None,
}
}
}
#[napi_derive::napi]
pub fn #to_str(val: #napi_type) -> String {
match #original_type::from_i32(val as i32) {
None => "UNKNOWN".to_string(),
Some(og_val) => og_val.as_str_name().to_string(),
}
}
#[napi_derive::napi]
pub fn #from_str(val: String) -> std::option::Option<#napi_type> {
match #original_type::from_str_name(val.as_str()) {
None => None,
Some(og_val) => #napi_type::from_i32(og_val as i32),
}
}
};
TokenStream::from(expanded)
}