use from_attr::FromAttr;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{Fields, ItemStruct, LitStr};
use crate::{
util::{extract_type::extract_option_inner_type, field_type::FieldType, logic::Logic},
web::attrs::properties_attr::PropertiesAttr,
};
pub fn impl_macro_properties(attr: TokenStream, mut item_struct: ItemStruct) -> TokenStream {
let expanded = Logic::generate(|| {
let attr = PropertiesAttr::from_tokens(attr.to_owned().into())?;
let required_derives = vec!["Debug", "Clone", "Deserialize"];
let existing_derives = &item_struct
.attrs
.iter()
.filter(|attr| attr.path().is_ident("derive"))
.filter_map(|attr| {
let meta = attr.meta.require_list().ok()?;
Some(meta.tokens.to_string())
})
.collect::<Vec<_>>();
for required in required_derives {
if !existing_derives.iter().any(|d| d.contains(required)) {
return Err(syn::Error::new(
item_struct.ident.span(),
format!("Missing required derive: {}", required),
));
}
}
let prefix_expr = attr.prefix;
let prefix = prefix_expr.to_token_stream().to_string().replace("\"", "");
let dynamic = attr.dynamic;
let fields = match &item_struct.fields {
Fields::Named(fields_named) => &fields_named.named,
_ => {
return Err(syn::Error::new(
item_struct.ident.span(),
"Only named fields are supported",
))
}
};
let common_fields = fields
.iter()
.enumerate()
.filter(|(_, field)| field.ident.is_some())
.filter_map(|(index, field)| {
if dynamic && index == 0 {
return None;
}
let field_name = field.ident.as_ref()?;
let field_type = &field.ty;
let (is_option, inner_type) = match extract_option_inner_type(field_type) {
Some(ty) => (true, ty),
None => (false, field_type.clone()),
};
let key_name = field
.attrs
.iter()
.find(|attr| attr.path().is_ident("key"))
.and_then(|attr| {
attr.meta.require_name_value().ok().and_then(|meta| {
if let syn::Expr::Lit(expr_lit) = &meta.value {
if let syn::Lit::Str(lit_str) = &expr_lit.lit {
return Some(lit_str.value());
}
}
None
})
})
.unwrap_or_else(|| field_name.to_string());
let key = if prefix.is_empty() {
key_name
} else {
format!("{}.{}", prefix, key_name)
};
let key_str = LitStr::new(&key, field_name.span());
let is_string_type = FieldType::is_string(&inner_type);
let extract_value_expr = if is_string_type {
quote! {
|| -> Option<String> {
if let Some(s) = properties.one_value::<String>(#key_str) {
return Some(s);
}
match properties.one_value::<String>(#key_str) {
Some(s) => Some(s),
None => {
match properties.one_value::<i64>(#key_str) {
Some(s) => Some(s.to_string()),
None => match properties.one_value::<f64>(#key_str) {
Some(s) => Some(s.to_string()),
None => None,
}
}
}
}
}()
}
} else {
quote! {
properties.one_value::<#inner_type>(#key_str)
}
};
let field_init = if is_option {
quote! { #field_name: #extract_value_expr, }
} else {
quote! {
#field_name: #extract_value_expr.unwrap_or_else(|| {
noting = true;
Default::default()
}),
}
};
Some(field_init)
})
.collect::<Vec<_>>();
let dynamic_field = if dynamic {
quote! {
base: if let Some(values) = properties.dynamic_value(#prefix_expr) { values } else { Default::default() },
}
} else {
quote! {}
};
if let Fields::Named(fields_named) = &mut item_struct.fields {
for field in &mut fields_named.named {
field.attrs.retain(|attr| !attr.path().is_ident("key"));
}
}
let struct_ident = &item_struct.ident;
let singleton_name = item_struct.attrs.iter().find_map(|attr| {
if !attr.path().is_ident("Singleton") && !attr.path().is_ident("SingleOwner") {
return None;
}
let mut name = None;
let mut binds_exist = false;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("name") {
let value = meta.value()?;
let string_value = value.parse::<syn::LitStr>()?;
name = Some(string_value.value());
return Ok(());
}
if meta.path.is_ident("binds") {
binds_exist = true;
let value = meta.value()?;
let bind_array = value.parse::<syn::ExprArray>()?;
let binds = bind_array.to_token_stream().to_string();
if !binds.contains("into_properties") {
return Err(syn::Error::new(Span::call_site(), "Singleton or SingleOwner macro must contain ::into_properties"));
}
}
Ok(())
});
if !binds_exist {
panic!("Singleton or SingleOwner macro must support binds `#[singleton(binds = [Self::into_properties])]`");
}
name
});
let singleton_name = if let Some(name) = singleton_name {
name
} else {
crate::util::name::singleton_name(&struct_ident.to_string())
};
let singleton_name = LitStr::new(&singleton_name, struct_ident.span());
let expanded = quote! {
#item_struct
#[next_web_core::async_trait]
impl ::next_web_core::AutoRegister for #struct_ident {
async fn register(
&self,
ctx: &mut ::next_web_core::context::application_context::ApplicationContext,
properties: & ::next_web_core::context::properties::ApplicationProperties,
) -> ::std::result::Result<(), ::std::boxed::Box<dyn ::std::error::Error>> {
let mut noting = false;
let instance = Self {
#dynamic_field
#(#common_fields)*
};
if noting {
panic!("\nIncorrect assembly of properties! Struct: {} \n", stringify!(#struct_ident));
}
ctx.insert_singleton_with_name(instance, #singleton_name);
Ok(())
}
fn registered_name(&self) -> &'static str {
#singleton_name
}
}
impl ::next_web_core::context::properties::Properties for #struct_ident {}
impl #struct_ident {
fn into_properties(self) -> ::std::boxed::Box<dyn ::next_web_core::context::properties::Properties> {
::std::boxed::Box::new(self)
}
}
};
Ok(expanded.into())
});
expanded
}