fusen-macro 0.6.3

RPC framework for service registration and discovery through API exposure, compatible with Dubbo3 protocol, intertunable with Java projects
Documentation
use crate::{get_asset_by_attrs, FusenAttr};
use fusen_common::MethodResource;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use std::collections::HashMap;
use syn::{parse_macro_input, FnArg, ItemTrait, ReturnType, TraitItem};

pub fn fusen_trait(attr: FusenAttr, item: TokenStream) -> TokenStream {
    let group = match attr.group {
        Some(group) => quote!(Some(&#group)),
        None => quote!(None),
    };
    let version = match attr.version {
        Some(version) => quote!(Some(&#version)),
        None => quote!(None),
    };
    let input = parse_macro_input!(item as ItemTrait);
    let mut methods_cache = HashMap::new();
    let methods_info = match get_resource_by_trait(input.clone()) {
        Ok(methods_info) => {
            methods_info.into_iter().fold(vec![], |mut vec, e| {
                vec.push(e.to_json_str());
                let MethodResource { path, name, method } = e;
                methods_cache.insert(name, (path, method));
                vec
            })
        }
        Err(err) => return err.into_compile_error().into(),
    };
    let id = match attr.id {
        Some(trait_id) => {
            quote!(#trait_id)
        }
        None => {
            let id = input.ident.to_string();
            quote!(#id)
        }
    };
    let item_trait = get_item_trait(input.clone());
    let trait_ident = &input.ident;
    let vis = &input.vis;
    let items = &input.items;
    let mut sig_item = vec![];
    for item in items {
        if let TraitItem::Fn(item) = item {
            sig_item.push(item.sig.clone());
        }
    }
    let mut fn_quote = vec![];
    for item in sig_item {
        let asyncable = item.asyncness;
        let ident = item.ident;
        let inputs = item.inputs;
        let mut fields_ty = vec![];
        let req = inputs.iter().fold(vec![], |mut vec, e| {
            if let FnArg::Typed(req) = e {
                vec.push(req.pat.clone());
                fields_ty.push(req.pat.to_token_stream().to_string());
            }
            vec
        });
        let output = item.output;
        let output_type = match &output {
            ReturnType::Default => {
                quote! {()}
            }
            ReturnType::Type(_, res_type) => res_type.to_token_stream(),
        };
        let (methos_path, methos_type) = methods_cache.get(&ident.to_string()).unwrap();
        fn_quote.push(
            quote! {
                    #[allow(non_snake_case)]
                    pub #asyncable fn #ident (#inputs) -> Result<#output_type,fusen_rs::fusen_common::error::FusenError> {
                    let mut req_vec : Vec<String> = vec![];
                    let fields_ty = vec![
                    #(
                        #fields_ty.to_string(),
                    )*];
                    #(
                        let mut res_poi_str = serde_json::to_string(&#req);
                        if let Err(err) = res_poi_str {
                            return Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()));
                        }
                        req_vec.push(res_poi_str.unwrap());
                    )*
                    let version : Option<&str> = #version;
                    let group : Option<&str> = #group;
                    let mut mate_data = fusen_rs::fusen_common::MetaData::new();
                    let mut request = fusen_rs::fusen_common::FusenRequest::new_for_client(#methos_type,fields_ty,req_vec);
                    let mut context = fusen_rs::fusen_common::FusenContext::new(
                        fusen_rs::fusen_common::logs::get_uuid(),
                        fusen_rs::fusen_common::ContextInfo::default()
                            .path(fusen_rs::fusen_common::Path::new(#methos_type,#methos_path.to_string()))
                            .version(version.map(|e|e.to_string()))
                            .class_name(#id.to_owned())
                            .method_name(stringify!(#ident).to_string())
                            .group(group.map(|e|e.to_string())),
                        request,
                        mate_data,
                    );
                    context.response.insert_return_ty(stringify!(#output_type));
                    let res : Result<#output_type,fusen_rs::fusen_common::error::FusenError> = self.client.invoke::<#output_type>(context).await;
                    return res;
                }
            }
        );
    }
    let rpc_client = syn::Ident::new(&format!("{}Client", trait_ident), trait_ident.span());

    let expanded = quote! {
        #item_trait

        #[derive(Clone)]
        #vis struct #rpc_client {
            client : std::sync::Arc<fusen_rs::client::FusenClient>
        }
        impl #rpc_client {
        #(
            #fn_quote
        )*
        pub fn new(client : std::sync::Arc<fusen_rs::client::FusenClient>) -> #rpc_client {
            #rpc_client {client}
        }

        pub fn get_info(&self) -> fusen_rs::fusen_common::server::ServerInfo {
            let mut methods : Vec<fusen_rs::fusen_common::MethodResource> = vec![];
            #(
                methods.push(fusen_rs::fusen_common::MethodResource::form_json_str(#methods_info));
            )*
            fusen_rs::fusen_common::server::ServerInfo::new(#id,#version,#group,methods)
        }

       }

    };
    TokenStream::from(expanded)
}

fn get_item_trait(item: ItemTrait) -> proc_macro2::TokenStream {
    let trait_ident = &item.ident;
    let item_fn = item.items.iter().fold(vec![], |mut vec, e| {
        if let TraitItem::Fn(item_fn) = e {
            let asyncable = &item_fn.sig.asyncness;
            let ident = &item_fn.sig.ident;
            let inputs = &item_fn.sig.inputs;
            let attrs = &item_fn.attrs;
            let output_type = match &item_fn.sig.output {
                ReturnType::Default => {
                    quote! {()}
                }
                ReturnType::Type(_, res_type) => res_type.to_token_stream(),
            };
            vec.push(quote! {
                   #(#attrs)*
                   #asyncable fn #ident (#inputs) -> fusen_rs::fusen_common::FusenResult<#output_type>;
            });
        }
        vec
    });
    quote! {
        pub trait #trait_ident {
           #(
               #[allow(async_fn_in_trait)]
               #[allow(non_snake_case)]
               #item_fn
            )*
        }
    }
}

fn get_resource_by_trait(item: ItemTrait) -> Result<Vec<MethodResource>, syn::Error> {
    let mut res = vec![];
    let attrs = &item.attrs;
    let resource = get_asset_by_attrs(attrs)?;
    let parent_path = match resource.path {
        Some(path) => path,
        None => "/".to_owned() + &item.ident.to_string(),
    };
    let parent_method = match resource.method {
        Some(method) => method,
        None => "POST".to_string(),
    };
    for fn_item in item.items.iter() {
        if let TraitItem::Fn(item_fn) = fn_item {
            let resource = get_asset_by_attrs(&item_fn.attrs)?;
            let path = match resource.path {
                Some(path) => path,
                None => "/".to_owned() + &item_fn.sig.ident.to_string(),
            };
            let method = match resource.method {
                Some(method) => method,
                None => parent_method.clone(),
            };
            let mut parent_path = parent_path.clone();
            parent_path.push_str(&path);
            res.push(MethodResource::new(
                item_fn.sig.ident.to_string(),
                parent_path,
                method,
            ));
        }
    }
    Ok(res)
}