diff --git a/Cargo.toml b/Cargo.toml index 676567a..d10d6e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ include = [ [workspace.dependencies] prosa-utils = { version = "0.1.1", path = "prosa_utils" } -prosa-macros = { version = "0.1.1", path = "prosa_macros" } +prosa-macros = { version = "0.1.2", path = "prosa_macros" } thiserror = "1" aquamarine = "0.5" bytes = "1" diff --git a/prosa/src/core/proc.rs b/prosa/src/core/proc.rs index 5369076..c6de3dd 100644 --- a/prosa/src/core/proc.rs +++ b/prosa/src/core/proc.rs @@ -169,9 +169,18 @@ pub use prosa_macros::proc_settings; /// use prosa::core::proc::proc_settings; /// /// #[proc_settings] -/// #[derive(Default, Debug)] +/// #[derive(Debug)] /// pub struct MySettings { -/// my_param: Option, +/// my_param: String, +/// } +/// +/// #[proc_settings] +/// impl Default for MySettings { +/// fn default() -> Self { +/// MySettings { +/// my_param: "default param".into(), +/// } +/// } /// } /// ``` pub trait ProcSettings { @@ -432,3 +441,37 @@ where .unwrap(); } } + +#[cfg(test)] +mod tests { + use super::*; + use prosa_macros::proc_settings; + use serde::Serialize; + + extern crate self as prosa; + + #[test] + fn test_proc_settings() { + #[proc_settings] + #[derive(Debug, Serialize)] + struct TestProcSettings { + name: String, + } + + #[proc_settings] + impl Default for TestProcSettings { + fn default() -> Self { + let _test_settings = TestProcSettings { + name: "test".into(), + }; + + TestProcSettings { + name: "test".into(), + } + } + } + + let test_proc_settings = TestProcSettings::default(); + assert_eq!("test", test_proc_settings.name); + } +} diff --git a/prosa/src/core/settings.rs b/prosa/src/core/settings.rs index 8786259..9bf5c9c 100644 --- a/prosa/src/core/settings.rs +++ b/prosa/src/core/settings.rs @@ -15,19 +15,43 @@ pub use prosa_macros::settings; /// Need to be implemented by the top settings layer of a ProSA /// /// ``` +/// use prosa::core::settings::{settings, Settings}; +/// use serde::Serialize; +/// +/// // My ProSA setting structure +/// #[settings] +/// #[derive(Debug, Serialize)] +/// struct MySettings { +/// test_val: String +/// } +/// +/// #[settings] +/// impl Default for MySettings { +/// fn default() -> Self { +/// MySettings { +/// test_val: "test".into(), +/// } +/// } +/// } +/// +/// assert_eq!("test", MySettings::default().test_val); +/// ``` +/// +/// is equivalent to +/// +/// ``` /// use prosa::core::settings::Settings; /// use prosa_utils::config::observability::Observability; -/// use prosa::core::settings::settings; /// use serde::Serialize; /// -/// /// My ProSA setting structure -/// #[derive(Serialize)] -/// struct MySettings { +/// #[derive(Debug, Serialize)] +/// struct MySameSettings { +/// test_val: String, /// name: Option, /// observability: Observability, /// } /// -/// impl Settings for MySettings { +/// impl Settings for MySameSettings { /// fn get_prosa_name(&self) -> String { /// if let Some(name) = &self.name { /// name.clone() @@ -47,10 +71,17 @@ pub use prosa_macros::settings; /// } /// } /// -/// // Equivalent to -/// #[settings] -/// #[derive(Serialize)] -/// struct MySameSettings {} +/// impl Default for MySameSettings { +/// fn default() -> Self { +/// MySameSettings { +/// test_val: "test".into(), +/// name: None, +/// observability: Observability::default(), +/// } +/// } +/// } +/// +/// assert_eq!("test", MySameSettings::default().test_val); /// ``` pub trait Settings: Serialize { /// Getter of the ProSA running name @@ -80,3 +111,40 @@ pub trait Settings: Serialize { } } } + +#[cfg(test)] +mod tests { + use super::*; + use prosa_macros::settings; + + extern crate self as prosa; + + #[test] + fn test_settings() { + #[settings] + #[derive(Debug, Serialize)] + struct TestSettings { + name_test: String, + name_test2: String, + } + + #[settings] + impl Default for TestSettings { + fn default() -> Self { + let _test_settings = TestSettings { + name_test: "test".into(), + name_test2: "test2".into(), + }; + + TestSettings { + name_test: "test".into(), + name_test2: "test2".into(), + } + } + } + + let test_settings = TestSettings::default(); + assert_eq!("test", test_settings.name_test); + assert_eq!("test2", test_settings.name_test2); + } +} diff --git a/prosa_macros/Cargo.toml b/prosa_macros/Cargo.toml index 68513c7..adae131 100644 --- a/prosa_macros/Cargo.toml +++ b/prosa_macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prosa-macros" -version = "0.1.1" +version = "0.1.2" authors.workspace = true description = "ProSA macros" homepage.workspace = true diff --git a/prosa_macros/src/io.rs b/prosa_macros/src/io.rs index f98ac1c..be630d4 100644 --- a/prosa_macros/src/io.rs +++ b/prosa_macros/src/io.rs @@ -171,22 +171,24 @@ fn add_struct_impl(mut item_impl: syn::ItemImpl) -> syn::parse::Result syn::parse::Result { - if let syn::Item::Struct(item_struct) = item { - let struct_output = generate_struct(item_struct)?; - let struct_impl = generate_struct_impl(&struct_output)?; - Ok(quote! { - #struct_output - #struct_impl - }) - } else if let syn::Item::Impl(item_impl) = item { - let impl_output = add_struct_impl(item_impl)?; - Ok(quote! { - #impl_output - }) - } else { - Err(syn::Error::new( + match item { + syn::Item::Struct(item_struct) => { + let struct_output = generate_struct(item_struct)?; + let struct_impl = generate_struct_impl(&struct_output)?; + Ok(quote! { + #struct_output + #struct_impl + }) + } + syn::Item::Impl(item_impl) => { + let impl_output = add_struct_impl(item_impl)?; + Ok(quote! { + #impl_output + }) + } + _ => Err(syn::Error::new( proc_macro2::Span::call_site(), "expected struct or impl expression", - )) + )), } } diff --git a/prosa_macros/src/lib.rs b/prosa_macros/src/lib.rs index 0b2c736..a1d26c7 100644 --- a/prosa_macros/src/lib.rs +++ b/prosa_macros/src/lib.rs @@ -7,6 +7,7 @@ //! Base library to defines procedural macros #![warn(missing_docs)] +#![deny(unreachable_pub)] use proc_macro::TokenStream; use quote::quote; diff --git a/prosa_macros/src/proc.rs b/prosa_macros/src/proc.rs index 4fdc678..358f5a3 100644 --- a/prosa_macros/src/proc.rs +++ b/prosa_macros/src/proc.rs @@ -261,24 +261,26 @@ pub(crate) fn proc_impl( let mut proc_args: ProcParams = std::default::Default::default(); proc_args.parse_attr_args(args)?; - if let syn::Item::Struct(item_struct) = item { - let struct_output = generate_struct(item_struct, &proc_args)?; - let struct_impl_bus_param = generate_struct_impl_bus_param(&struct_output)?; - let struct_impl_config = generate_struct_impl_config(&struct_output, &proc_args)?; - Ok(quote! { - #struct_output - #struct_impl_bus_param - #struct_impl_config - }) - } else if let syn::Item::Impl(item_impl) = item { - let impl_output = add_struct_impl(item_impl)?; - Ok(quote! { - #impl_output - }) - } else { - Err(syn::Error::new( + match item { + syn::Item::Struct(item_struct) => { + let struct_output = generate_struct(item_struct, &proc_args)?; + let struct_impl_bus_param = generate_struct_impl_bus_param(&struct_output)?; + let struct_impl_config = generate_struct_impl_config(&struct_output, &proc_args)?; + Ok(quote! { + #struct_output + #struct_impl_bus_param + #struct_impl_config + }) + } + syn::Item::Impl(item_impl) => { + let impl_output = add_struct_impl(item_impl)?; + Ok(quote! { + #impl_output + }) + } + _ => Err(syn::Error::new( proc_macro2::Span::call_site(), "expected struct or impl expression", - )) + )), } } diff --git a/prosa_macros/src/settings.rs b/prosa_macros/src/settings.rs index 706b8b3..f70edb3 100644 --- a/prosa_macros/src/settings.rs +++ b/prosa_macros/src/settings.rs @@ -1,5 +1,80 @@ -use quote::quote; -use syn::parse::Parser; +use quote::{quote, ToTokens}; +use syn::{ + parse::{Parse, Parser}, + ItemImpl, +}; + +/// Function to add default member to Default trait impl +fn add_default_member(mut item_impl: ItemImpl, func: F) -> syn::parse::Result +where + F: Fn(&mut syn::ExprStruct), +{ + if let (Some((_, trait_path, _)), syn::Type::Path(self_path)) = + (&item_impl.trait_, item_impl.self_ty.as_ref()) + { + // Only consider Default trait impl + if trait_path.get_ident().is_some_and(|i| i == "Default") { + // Get self object ident and `fn default() -> Self` method + if let (Some(self_ident), Some(syn::ImplItem::Fn(item_fn))) = + (self_path.path.get_ident(), item_impl.items.first_mut()) + { + // Iterate over statements (code) + for stmt in &mut item_fn.block.stmts { + match stmt { + // Local statement (let = ...) + syn::Stmt::Local(syn::Local { + init: Some(syn::LocalInit { expr, .. }), + .. + }) => { + if let syn::Expr::Struct(expr) = expr.as_mut() { + if expr.path.is_ident(self_ident) { + if !expr.fields.trailing_punct() { + expr.fields.push_punct(syn::token::Comma::default()); + } + + func(expr); + } + } + } + // Direct Expr return (Self {..}) + syn::Stmt::Expr(syn::Expr::Struct(expr), _) => { + if expr.path.is_ident(self_ident) { + if !expr.fields.trailing_punct() { + expr.fields.push_punct(syn::token::Comma::default()); + } + + func(expr); + } + } + _ => {} + } + } + + Ok(item_impl) + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + "Wrong Default impl, missing `fn default() -> Self`", + )) + } + } else if let Some(ident) = trait_path.get_ident() { + Err(syn::Error::new( + ident.span(), + format!("expected Default impl expression instead of {}", ident), + )) + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + "expected Default impl expression", + )) + } + } else { + Err(syn::Error::new( + proc_macro2::Span::call_site(), + "expected Default impl expression", + )) + } +} fn generate_proc_settings_struct( mut item_struct: syn::ItemStruct, @@ -33,18 +108,28 @@ fn generate_struct_impl_proc_settings( /// Implementation of the procedural proc_settings macro pub(crate) fn proc_settings_impl(item: syn::Item) -> syn::parse::Result { - if let syn::Item::Struct(item_struct) = item { - let struct_output = generate_proc_settings_struct(item_struct)?; - let struct_impl_proc_settings = generate_struct_impl_proc_settings(&struct_output)?; - Ok(quote! { - #struct_output - #struct_impl_proc_settings - }) - } else { - Err(syn::Error::new( + match item { + syn::Item::Struct(item_struct) => { + let struct_output = generate_proc_settings_struct(item_struct)?; + let struct_impl_proc_settings = generate_struct_impl_proc_settings(&struct_output)?; + Ok(quote! { + #struct_output + #struct_impl_proc_settings + }) + } + syn::Item::Impl(item_impl) => Ok(add_default_member(item_impl, |x| { + x.fields.push_value( + syn::FieldValue::parse + .parse2(quote! { adaptor_config_path: None }) + .unwrap(), + ); + x.fields.push_punct(syn::token::Comma::default()); + })? + .into_token_stream()), + _ => Err(syn::Error::new( proc_macro2::Span::call_site(), "expected struct expression", - )) + )), } } @@ -103,17 +188,34 @@ fn generate_struct_impl_settings( /// Implementation of the procedural proc macro pub(crate) fn settings_impl(item: syn::Item) -> syn::parse::Result { - if let syn::Item::Struct(item_struct) = item { - let struct_output = generate_settings_struct(item_struct)?; - let struct_impl_settings = generate_struct_impl_settings(&struct_output)?; - Ok(quote! { - #struct_output - #struct_impl_settings - }) - } else { - Err(syn::Error::new( + match item { + syn::Item::Struct(item_struct) => { + let struct_output = generate_settings_struct(item_struct)?; + let struct_impl_settings = generate_struct_impl_settings(&struct_output)?; + Ok(quote! { + #struct_output + #struct_impl_settings + }) + } + syn::Item::Impl(item_impl) => Ok(add_default_member(item_impl, |x| { + x.fields.push_value( + syn::FieldValue::parse + .parse2(quote! { name: None }) + .unwrap(), + ); + x.fields.push_punct(syn::token::Comma::default()); + + x.fields.push_value( + syn::FieldValue::parse + .parse2(quote! { observability: prosa_utils::config::observability::Observability::default() }) + .unwrap(), + ); + x.fields.push_punct(syn::token::Comma::default()); + })? + .into_token_stream()), + _ => Err(syn::Error::new( proc_macro2::Span::call_site(), - "expected struct expression", - )) + "expected struct/Default impl expression", + )), } }