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 fusen_common::MethodResource;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, FnArg, ImplItem, ItemImpl};

use crate::{get_asset_by_attrs, FusenAttr};

pub fn fusen_server(attr: FusenAttr, item: TokenStream) -> TokenStream {
    let version = match attr.version {
        Some(version) => quote!(Some(&#version)),
        None => quote!(None),
    };
    let group = match attr.group {
        Some(group) => quote!(Some(&#group)),
        None => quote!(None),
    };
    let org_item = parse_macro_input!(item as ItemImpl);
    let methods_info = match get_resource_by_server(org_item.clone()) {
        Ok(methods_info) => methods_info.iter().fold(vec![], |mut vec, e| {
            vec.push(e.to_json_str());
            vec
        }),
        Err(err) => return err.into_compile_error().into(),
    };
    let id = match attr.id {
        Some(id) => {
            quote!(#id)
        }
        None => {
            let id = org_item.trait_.as_ref().unwrap().1.segments[0]
                .ident
                .to_string();
            quote!(#id)
        }
    };
    let item = org_item.clone();
    let org_item = get_server_item(org_item);
    let item_self = item.self_ty;
    let items_fn = item.items.iter().fold(vec![], |mut vec, e| {
        if let ImplItem::Fn(fn_item) = e {
            let method = &fn_item.sig.ident;
            let mut req_pat = vec![];
            let mut req_type = vec![];
            let request = fn_item.sig.inputs.iter().fold(vec![], |mut vec, e| {
                if let FnArg::Typed(input) = e {
                    let request = &input.pat;
                    let request_type = &input.ty;
                    let token = quote! {
                            let result : Result<#request_type,_>  = serde_json::from_slice(req_poi_param[idx].as_bytes());
                            if let Err(err) = result {
                                param.response.response = Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()));
                                return param;
                            }
                            let #request : #request_type = result.unwrap();
                            idx += 1;
                    };
                    req_pat.push(request);
                    req_type.push(request_type);
                    vec.push(token);
                }
                vec
            },
            );
            vec.push(quote! {
                if &param.context_info.method_name[..] == stringify!(#method) {
                    let fields_name = vec![#(
                        stringify!(#req_pat),
                    )*];
                    let fields_ty = vec![#(
                        stringify!(#req_type),
                    )*];
                let req_poi_param = match param.request.get_fields(fields_name,fields_ty) {
                     Ok(res) => res,
                     Err(err) => {
                        param.response.response = Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()));
                        return param;
                     }
                };
                let mut idx = 0;
                #(
                    #request
                )*
                let res = self.#method(
                    #(
                        #req_pat,
                    )*
                ).await;
                param.response.response = match res {
                    Ok(res) => {
                        let res = fusen_rs::fusen_common::codec::object_to_bytes(&res);
                        match res {
                            Ok(res) => Ok(res),
                            Err(err) => Err(fusen_rs::fusen_common::error::FusenError::from(err.to_string()))
                        }
                    },
                    Err(info) => Err(info)
                };
                return param;
            }
            }
            )
        }
        vec
    });
    let expanded = quote! {

        #org_item

        impl fusen_rs::fusen_common::server::RpcServer for #item_self {
            fn invoke (&'static self, param : fusen_rs::fusen_common::FusenContext) -> fusen_rs::fusen_common::FusenFuture<fusen_rs::fusen_common::FusenContext> {
                let rpc = self;
                Box::pin(async move {rpc.prv_invoke(param).await})
            }
            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)
            }
        }

        impl #item_self {
            async fn prv_invoke (&self, mut param : fusen_rs::fusen_common::FusenContext) -> fusen_rs::fusen_common::FusenContext {
                #(#items_fn)*
                param.response.response = Err(fusen_rs::fusen_common::error::FusenError::from(format!("not find method by {}",param.context_info.method_name)));
                return param;
            }
        }
    };
    expanded.into()
}

fn get_server_item(item: ItemImpl) -> proc_macro2::TokenStream {
    let impl_item = item.impl_token;
    let trait_ident = item.trait_.unwrap().1;
    let ident = item.self_ty.to_token_stream();
    let fn_items = item.items.iter().fold(vec![], |mut vec, e| {
        if let ImplItem::Fn(fn_item) = e {
            vec.push(fn_item);
        }
        vec
    });
    quote! {
        #impl_item #trait_ident for #ident {
            #(
                #[allow(non_snake_case)]
                #fn_items
            )*
        }
    }
}

fn get_resource_by_server(item: ItemImpl) -> 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(id) => id,
        None => "/".to_owned() + &item.trait_.unwrap().1.segments[0].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 ImplItem::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)
}