From 60c6ce824c1b19adc86d893053010e1de52c3265 Mon Sep 17 00:00:00 2001 From: HampusM Date: Fri, 22 Dec 2023 12:33:35 +0100 Subject: feat: add support for async constructors --- examples/async/animals/human.rs | 12 ++++++++++-- macros/Cargo.toml | 2 +- macros/src/injectable/implementation.rs | 34 ++++++++++++++++++++++++--------- macros/src/lib.rs | 27 +++++++++++++++++++++++++- 4 files changed, 62 insertions(+), 13 deletions(-) diff --git a/examples/async/animals/human.rs b/examples/async/animals/human.rs index fc98ed8..5bc4903 100644 --- a/examples/async/animals/human.rs +++ b/examples/async/animals/human.rs @@ -1,5 +1,8 @@ +use std::time::Duration; + use syrette::injectable; use syrette::ptr::{ThreadsafeSingletonPtr, TransientPtr}; +use tokio::time::sleep; use crate::interfaces::cat::ICat; use crate::interfaces::dog::IDog; @@ -14,9 +17,14 @@ pub struct Human #[injectable(IHuman, async = true)] impl Human { - pub fn new(dog: ThreadsafeSingletonPtr, cat: TransientPtr) - -> Self + pub async fn new( + dog: ThreadsafeSingletonPtr, + cat: TransientPtr, + ) -> Self { + // The human needs some rest first + sleep(Duration::from_secs(1)).await; + Self { dog, cat } } } diff --git a/macros/Cargo.toml b/macros/Cargo.toml index f35729a..e883316 100644 --- a/macros/Cargo.toml +++ b/macros/Cargo.toml @@ -18,7 +18,7 @@ rustdoc-args = ["--cfg", "doc_cfg", "--html-in-header", "docs-style.html"] [features] factory = ["syrette/factory"] prevent-circular = [] -async = [] +async = ["syrette/async"] [dependencies] syn = { version = "1.0.96", features = ["full"] } diff --git a/macros/src/injectable/implementation.rs b/macros/src/injectable/implementation.rs index e2bcd3e..a98d1ce 100644 --- a/macros/src/injectable/implementation.rs +++ b/macros/src/injectable/implementation.rs @@ -83,7 +83,7 @@ impl InjectableImpl }) } - pub fn validate(&self) -> Result<(), InjectableImplError> + pub fn validate(&self, is_async: bool) -> Result<(), InjectableImplError> { if matches!(self.constructor_method.sig.output, ReturnType::Default) { return Err(InjectableImplError::InvalidConstructorMethodReturnType { @@ -127,10 +127,14 @@ impl InjectableImpl }); } - if let Some(asyncness) = self.constructor_method.sig.asyncness { - return Err(InjectableImplError::ConstructorMethodAsync { - asyncness_span: asyncness.span, - }); + if !is_async { + if let Some(asyncness) = self.constructor_method.sig.asyncness { + return Err( + InjectableImplError::ConstructorMethodAsyncWithMissingAsyncAttr { + asyncness_span: asyncness.span, + }, + ); + } } if !self.constructor_method.sig.generics.params.is_empty() { @@ -235,6 +239,12 @@ impl InjectableImpl .map(|index| format_ident!("dependency_{index}")) .collect::>(); + let maybe_await_constructor = if self.constructor_method.sig.asyncness.is_some() { + quote! { .await } + } else { + quote! {} + }; + quote! { #maybe_doc_hidden impl #generics syrette::interfaces::async_injectable::AsyncInjectable< @@ -273,7 +283,7 @@ impl InjectableImpl Ok(syrette::ptr::TransientPtr::new(Self::#constructor( #(#dependency_idents),* - ))) + )#maybe_await_constructor)) }) } } @@ -522,9 +532,15 @@ pub enum InjectableImplError unsafety_span: Span }, - #[error("Constructor method is not allowed to be async"), span = asyncness_span] - #[note("Required by the 'injectable' attribute macro")] - ConstructorMethodAsync { + #[ + error(concat!( + "Constructor method is not allowed to be async when the 'async' flag of the ", + "'injectable' macro is not set to true" + )), + span = asyncness_span + ] + #[help("Enable the 'async' flag here")] + ConstructorMethodAsyncWithMissingAsyncAttr { asyncness_span: Span }, diff --git a/macros/src/lib.rs b/macros/src/lib.rs index fa84e0e..e55e23f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -100,6 +100,30 @@ const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); /// } /// } /// ``` +///
+/// +/// When the `async` crate feature is enabled, the constructor can be [`async`]. +/// ``` +/// # #[cfg(feature = "async")] +/// # mod example { +/// # use syrette::injectable; +/// # +/// # struct PasswordManager { value: u32} +/// # +/// # async fn some_async_computation() -> u32 { 1 } +/// # +/// #[injectable(async = true)] +/// impl PasswordManager +/// { +/// pub async fn new() -> Self +/// { +/// let value = some_async_computation().await; +/// +/// Self { value } +/// } +/// } +/// # } +/// ``` /// /// # Attributes /// Attributes specific to impls with this attribute macro. @@ -149,6 +173,7 @@ const PACKAGE_VERSION: &str = env!("CARGO_PKG_VERSION"); /// [`Injectable`]: ../syrette/interfaces/injectable/trait.Injectable.html /// [`AsyncInjectable`]: ../syrette/interfaces/async_injectable/trait.AsyncInjectable.html /// [`di_container_bind`]: ../syrette/macro.di_container_bind.html +/// [`async`]: https://doc.rust-lang.org/std/keyword.async.html #[cfg(not(tarpaulin_include))] #[proc_macro_error] #[proc_macro_attribute] @@ -233,7 +258,7 @@ pub fn injectable(args_stream: TokenStream, input_stream: TokenStream) -> TokenS let injectable_impl = InjectableImpl::::new(item_impl, &constructor).unwrap_or_abort(); - injectable_impl.validate().unwrap_or_abort(); + injectable_impl.validate(is_async).unwrap_or_abort(); let expanded_injectable_impl = injectable_impl.expand(no_doc_hidden, is_async); -- cgit v1.2.3-18-g5258