use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use server_less_parse::{MethodInfo, ParamInfo};
use syn::Type;
pub fn is_qualified_context(ty: &Type) -> bool {
if let Type::Path(type_path) = ty {
let path = &type_path.path;
let segments: Vec<_> = path.segments.iter().collect();
if segments.len() >= 2 {
for i in 0..segments.len() - 1 {
if segments[i].ident == "server_less" && segments[i + 1].ident == "Context" {
return true;
}
}
}
}
false
}
pub fn is_bare_context(ty: &Type) -> bool {
if let Type::Path(type_path) = ty
&& type_path.path.segments.len() == 1
{
return type_path.path.segments[0].ident == "Context";
}
false
}
pub fn should_inject_context(ty: &Type, method_params: &[ParamInfo]) -> bool {
if is_qualified_context(ty) {
true
} else if is_bare_context(ty) {
let method_has_qualified = method_params.iter().any(|p| is_qualified_context(&p.ty));
!method_has_qualified
} else {
false
}
}
pub fn should_inject_special_param(
ty: &Type,
is_qualified: fn(&Type) -> bool,
is_bare: fn(&Type) -> bool,
has_qualified_in_impl: bool,
) -> bool {
if is_qualified(ty) {
true
} else if is_bare(ty) {
!has_qualified_in_impl
} else {
false
}
}
pub fn has_qualified_special_param(
methods: &[MethodInfo],
is_qualified: fn(&Type) -> bool,
) -> bool {
methods.iter().any(|method| {
method
.params
.iter()
.any(|param| is_qualified(¶m.ty))
})
}
pub fn partition_context_params(
params: &[ParamInfo],
) -> syn::Result<(Option<&ParamInfo>, Vec<&ParamInfo>)> {
let mut context_param: Option<&ParamInfo> = None;
let mut other_params = Vec::new();
for param in params {
if should_inject_context(¶m.ty, params) {
if context_param.is_some() {
return Err(syn::Error::new_spanned(
¶m.ty,
"only one Context parameter allowed per method\n\
\n\
Hint: server_less::Context is automatically injected from request metadata.\n\
Remove the duplicate Context parameter.",
));
}
context_param = Some(param);
} else {
other_params.push(param);
}
}
Ok((context_param, other_params))
}
pub fn generate_http_context_extraction() -> (TokenStream2, TokenStream2) {
let extraction = quote! {
__context_headers: ::server_less::axum::http::HeaderMap
};
let call = quote! {
{
let mut __ctx = ::server_less::Context::new();
for (name, value) in __context_headers.iter() {
if let Ok(value_str) = value.to_str() {
__ctx.set(name.as_str(), value_str);
}
}
if let Some(request_id) = __context_headers.get("x-request-id")
.and_then(|v| v.to_str().ok())
{
__ctx.set_request_id(request_id);
}
__ctx
}
};
(extraction, call)
}
pub fn generate_cli_context_extraction() -> (TokenStream2, TokenStream2) {
let extraction = quote! {};
let call = quote! {
{
let mut __ctx = ::server_less::Context::new();
for (key, value) in ::std::env::vars() {
__ctx.set(format!("env:{}", key), value);
}
__ctx
}
};
(extraction, call)
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn test_is_qualified_context() {
let ty: Type = parse_quote! { server_less::Context };
assert!(is_qualified_context(&ty));
let ty: Type = parse_quote! { ::server_less::Context };
assert!(is_qualified_context(&ty));
}
#[test]
fn test_is_bare_context() {
let ty: Type = parse_quote! { Context };
assert!(is_bare_context(&ty));
let ty: Type = parse_quote! { server_less::Context };
assert!(!is_bare_context(&ty));
}
#[test]
fn test_should_inject_context() {
let bare_ctx: Type = parse_quote! { Context };
let qualified_ctx: Type = parse_quote! { server_less::Context };
let no_qualified_params: &[ParamInfo] = &[];
assert!(should_inject_context(&bare_ctx, no_qualified_params));
assert!(should_inject_context(&qualified_ctx, no_qualified_params));
let qualified_ty: Type = parse_quote! { server_less::Context };
let method_has_qualified = [ParamInfo {
name: parse_quote! { ctx },
ty: qualified_ty,
is_optional: false,
is_bool: false,
is_vec: false,
vec_inner: None,
is_id: false,
wire_name: None,
location: None,
default_value: None,
short_flag: None,
help_text: None,
is_positional: false,
}];
assert!(!should_inject_context(&bare_ctx, &method_has_qualified));
assert!(should_inject_context(&qualified_ctx, &method_has_qualified));
}
}