use proc_macro2::TokenStream as TokenStream2;
use quote::ToTokens;
use syn::{Error, Ident, PathArguments};
use syn::spanned::Spanned;
use crate::component::type_to_inject::TypeToInject;
pub(crate) trait Injector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
container: &Ident,
) -> Option<TokenStream2>;
}
pub(crate) struct WrcInjector;
impl Injector for WrcInjector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
container: &Ident,
) -> Option<TokenStream2> {
#[cfg(feature = "async")]
const RC_FULL_TYPE: &str = "std :: sync :: Arc <";
#[cfg(not(feature = "async"))]
const RC_FULL_TYPE: &str = "std :: rc :: Rc <";
#[cfg(feature = "async")]
const RC_SHORT_TYPE: &str = "Arc <";
#[cfg(not(feature = "async"))]
const RC_SHORT_TYPE: &str = "Rc <";
let referenced_type_opt = if to_inject.type_name.starts_with("waiter_di :: Wrc <")
|| to_inject.type_name.starts_with(RC_FULL_TYPE) {
Some(get_type_arg(&to_inject.type_path.segments[2].arguments))
} else if to_inject.type_name.starts_with("Wrc <") ||
to_inject.type_name.starts_with(RC_SHORT_TYPE) {
Some(get_type_arg(&to_inject.type_path.segments[0].arguments))
} else {
None
};
referenced_type_opt.map(|ref_type| quote::quote! {
waiter_di::Provider::<#ref_type>::get(#container)
})
}
}
pub(crate) struct BoxInjector;
impl Injector for BoxInjector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
container: &Ident,
) -> Option<TokenStream2> {
if to_inject.type_name.starts_with("Box <") {
let referenced_type = get_type_arg(&to_inject.type_path.segments[0].arguments);
return Some(quote::quote! {
Box::new(waiter_di::Provider::<#referenced_type>::create(#container))
});
}
None
}
}
pub(crate) struct DeferredInjector;
impl Injector for DeferredInjector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
_container: &Ident,
) -> Option<TokenStream2> {
let referenced_type_opt = if to_inject.type_name.starts_with("waiter_di :: Deferred <") {
Some(get_type_arg(&to_inject.type_path.segments[1].arguments))
} else if to_inject.type_name.starts_with("Deferred <") {
Some(get_type_arg(&to_inject.type_path.segments[0].arguments))
} else {
None
};
referenced_type_opt.map(|ref_type| quote::quote! {
waiter_di::Deferred::<#ref_type>::new()
})
}
}
pub(crate) struct ConfigInjector;
impl Injector for ConfigInjector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
container: &Ident,
) -> Option<TokenStream2> {
if to_inject.type_name == *"Config"
|| to_inject.type_name == *"config :: Config" {
return Some(quote::quote! { #container.config.clone() });
}
None
}
}
pub(crate) struct PropInjector;
impl Injector for PropInjector {
fn generate_inject_code(
&self,
to_inject: &TypeToInject,
container: &Ident,
) -> Option<TokenStream2> {
let (prop_name_opt, default_value_code) = if to_inject.prop_attr.is_some() {
let prop_attr = to_inject.prop_attr.clone().unwrap();
let prop_name_opt = prop_attr.name.clone()
.or(to_inject.arg_name.clone()
.map(|name_ts| name_ts.to_string())
);
let default_value_code = prop_attr.default_value.clone()
.map(|default_value| quote::quote! { .or_else(|| Some(#default_value)) })
.unwrap_or_default();
(prop_name_opt, default_value_code)
} else {
(to_inject.arg_name.clone()
.map(|name_ts| name_ts.to_string()),
quote::quote! {}
)
};
let base_types_extracted = prop_name_opt.and_then(|prop_name_tokens| {
let prop_name = prop_name_tokens.to_string();
let (type_path, opt_extractor) = if to_inject.type_name.starts_with("Option <") {
(get_type_arg(&to_inject.type_path.segments[0].arguments),
quote::quote! { }
)
} else {
(to_inject.type_path.to_token_stream(),
quote::quote! { .expect(format!("Property \"{}\" not found", #prop_name).as_str()) }
)
};
let type_name = type_path.to_string();
let mut extractors: Vec<Box<dyn PropExtractor>> = Vec::new();
extractors.push(Box::new(SafeCastPropExtractor));
extractors.push(Box::new(UnsafeCastPropExtractor));
extractors.push(Box::new(AsCastPropExtractor));
extractors.iter()
.find_map(|extractor| extractor
.generate_extract_method(type_name.clone())
.map(|extract_method| {
let convert_code = extractor.generate_convert_code(
type_path.clone(),
type_name.clone(),
prop_name.to_string(),
quote::quote! { value },
);
quote::quote! {
#container.config.#extract_method(#prop_name)
.map(|value| #convert_code)
.ok()
#default_value_code
#opt_extractor
}
})
)
});
base_types_extracted
.or_else(|| {
if to_inject.prop_attr.is_some() {
let type_name = to_inject.type_name.clone();
let type_path = to_inject.type_path.clone();
Some(quote::quote! {
#container.config.clone().try_deserialize::<#type_path>()
.expect(format!("Can't parse config as '{}'", #type_name).as_str())
})
} else {
None
}
})
}
}
trait PropExtractor {
fn generate_extract_method(&self, type_name: String) -> Option<TokenStream2>;
fn generate_convert_code(
&self,
_type_path: TokenStream2,
_type_name: String,
_prop_name: String,
extract_code: TokenStream2,
) -> TokenStream2 {
extract_code
}
}
struct SafeCastPropExtractor;
impl PropExtractor for SafeCastPropExtractor {
fn generate_extract_method(&self, type_name: String) -> Option<TokenStream2> {
match type_name.as_str() {
"i128" | "u128" => Some(quote::quote! { get_int }),
_ => None
}
}
fn generate_convert_code(
&self,
type_path: TokenStream2,
_type_name: String,
_prop_name: String,
value: TokenStream2,
) -> TokenStream2 {
quote::quote! { #type_path::from(#value) }
}
}
struct UnsafeCastPropExtractor;
impl PropExtractor for UnsafeCastPropExtractor {
fn generate_extract_method(&self, type_name: String) -> Option<TokenStream2> {
match type_name.as_str() {
"i8" | "i16" | "i32" | "isize" | "u8" | "u16" | "u32" | "u64" | "u128" | "usize" =>
Some(quote::quote! { get_int }),
_ => None
}
}
fn generate_convert_code(
&self,
type_path: TokenStream2,
type_name: String,
prop_name: String,
value: TokenStream2,
) -> TokenStream2 {
quote::quote! {
<#type_path as std::convert::TryFrom<i64>>::try_from(#value)
.expect(format!("Can't parse prop '{}' as '{}'", #prop_name, #type_name).as_str())
}
}
}
struct AsCastPropExtractor;
impl PropExtractor for AsCastPropExtractor {
fn generate_extract_method(&self, type_name: String) -> Option<TokenStream2> {
match type_name.as_str() {
"i64" => Some(quote::quote! { get_int }),
"f64" | "f32" => Some(quote::quote! { get_float }),
"String" => Some(quote::quote! { get_string }),
"bool" => Some(quote::quote! { get_bool }),
_ => None
}
}
fn generate_convert_code(
&self,
type_path: TokenStream2,
_type_name: String,
_prop_name: String,
value: TokenStream2,
) -> TokenStream2 {
quote::quote! {
#value as #type_path
}
}
}
fn get_type_arg(arguments: &PathArguments) -> TokenStream2 {
if let PathArguments::AngleBracketed(ab) = arguments {
ab.args.to_token_stream()
} else {
Error::new(arguments.span(), "Unsupported type argument").to_compile_error()
}
}