use proc_macro2::*;
use quote::{quote, ToTokens};
use syn;
use syn::{AttributeArgs, ItemFn};
use crate::helpers::*;
use crate::proc_macro::TokenStream;
pub(crate) fn impl_remote(target_fn: &ItemFn, _args: &AttributeArgs) -> TokenStream {
let return_ty = find_return_type(target_fn);
let func_name_ident = target_fn.sig.ident.to_token_stream();
let attrs = target_fn.attrs.clone();
let http_methods = ["get", "post", "put", "delete", "patch"];
let feign_http_attr_idx = attrs.iter().position(|attr| {
attr.path.segments.last()
.map(|seg| http_methods.contains(&seg.ident.to_string().as_str()))
.unwrap_or(false)
}).expect("[genies] #[remote] requires a feignhttp HTTP method attribute (e.g., #[get(...)])");
let mut feign_http_attr = attrs[feign_http_attr_idx].clone();
let feignhttp_segment = syn::PathSegment {
ident: Ident::new("feignhttp", Span::call_site()),
arguments: syn::PathArguments::None,
};
feign_http_attr.path.segments.insert(0, feignhttp_segment);
let feign_http_macro = feign_http_attr.to_token_stream();
let other_attrs: Vec<_> = attrs.iter().enumerate()
.filter(|(i, _)| *i != feign_http_attr_idx)
.map(|(_, a)| a)
.collect();
let func_args_stream = target_fn.sig.inputs.to_token_stream();
let mut item_fn = target_fn.clone();
let sig = &mut item_fn.sig;
let args = parse_args(sig).unwrap();
let mut func_args = quote! {};
let mut func_args_do = quote! {};
let mut first = true;
for arg in args {
let temp_var = arg.var.clone();
let temp_var_type = arg.var_type.clone();
if first {
func_args = quote! {
#temp_var:#temp_var_type
};
first = false;
} else {
func_args = quote! {
#func_args,#temp_var:#temp_var_type
};
}
func_args_do = quote! {
#func_args_do,#temp_var
};
}
let fn_body = target_fn.block.to_token_stream();
let is_async = target_fn.sig.asyncness.is_some();
if !is_async {
panic!(
"[genies] #[remote] 'fn {}({})' must be async fn! ",
func_name_ident, func_args_stream
);
}
let feignhttp_name = format!("{}_feignhttp", func_name_ident);
let feignhttp_ident = Ident::new(&feignhttp_name, Span::call_site());
let feignhttp_code = quote! {
#feign_http_macro
pub async fn #feignhttp_ident(#[header] Authorization: &str,#func_args_stream) -> #return_ty
#fn_body
};
let wrapper_feignhttp_code = quote! {
#(#other_attrs)*
pub async fn #func_name_ident(#func_args) -> #return_ty{
let is_user_token = genies::context::request_token::get_request_token().is_some();
let bearer = genies::context::request_token::get_request_token()
.unwrap_or_else(|| {
let token = genies::context::REMOTE_TOKEN.lock().unwrap().access_token.clone();
format!("Bearer {}", &token)
});
let mut feignhttp_return = #feignhttp_ident( &bearer #func_args_do).await;
if !is_user_token {
if let Err(ref e) = feignhttp_return {
if e.to_string().contains("401 Unauthorized") {
let refresh_result = if genies::context::CONTEXT.config.auth_mode == "local" {
genies::core::jwt::get_local_service_token(
&genies::context::CONTEXT.config.jwt_secret,
).await
} else {
genies::core::jwt::get_temp_access_token(
genies::context::CONTEXT.config.keycloak_auth_server_url.as_deref().unwrap_or(""),
genies::context::CONTEXT.config.keycloak_realm.as_deref().unwrap_or(""),
genies::context::CONTEXT.config.keycloak_resource.as_deref().unwrap_or(""),
genies::context::CONTEXT.config.keycloak_credentials_secret.as_deref().unwrap_or(""),
).await
};
if let Ok(remote_token_new) = refresh_result {
genies::context::REMOTE_TOKEN.lock().unwrap().access_token = remote_token_new.clone();
let bearer = format!("Bearer {}", &remote_token_new);
feignhttp_return = #feignhttp_ident( &bearer #func_args_do).await;
}
}
}
}
feignhttp_return
}
};
let gen_token_temple = quote! {
#feignhttp_code
#wrapper_feignhttp_code
};
return gen_token_temple.into();
}