native-proc 0.3.0

Procedural macro for parsing prost-build structs and generating local matching types
Documentation
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),
];

/**
 * Create conversions between generated types for struct types
 *  From <NapiType> for <TonicType>
 *  From <TonicType> for <NapiType>
 */

#[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) => {
                // Assume args.args.len() == 1 - proto types aren't more complex
                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();
}

/**
 * Create conversions between numeric simple enum types and their String names
 *  fn <NapiType>_to_str(val: <NapiType>)
 *  fn <NapiType>_from_str(val: String)
 */

#[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()
  );

  // Get enum variants
  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"),
  };

  // Map the variants into 'fields'.
  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");
  }

  // Map variants to match statements per value
  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)
}