service_rs_proc_macro/lib.rs
1use proc_macro::TokenStream;
2use syn::{Data, DeriveInput};
3
4/// Derives the `Injectable` trait for automatic dependency injection.
5///
6/// This procedural macro generates a constructor and implements the `InjectableExtension` trait,
7/// enabling automatic dependency resolution through the service container.
8///
9/// # Requirements
10///
11/// - All fields must be wrapped in `Arc<T>` where `T` is a registered service type
12/// - The struct must have named fields or be a unit struct
13/// - All dependency types must be resolvable from the service provider
14///
15/// # Generated Code
16///
17/// The macro generates:
18/// 1. A `new` constructor that accepts all dependencies as parameters
19/// 2. An `InjectableExtension` implementation with a factory function that:
20/// - Resolves each dependency from the service provider context
21/// - Constructs the type with resolved dependencies
22/// - Returns a boxed instance
23///
24/// # Examples
25///
26/// ## Basic Usage with Dependencies
27///
28/// ```rust,ignore
29/// use service_rs::{Injectable, ServiceCollection};
30/// use std::sync::Arc;
31///
32/// // Mock types for demonstration
33/// struct ConnectionPool;
34/// struct Logger;
35///
36/// #[derive(Injectable)]
37/// struct DatabaseService {
38/// connection_pool: Arc<ConnectionPool>,
39/// logger: Arc<Logger>,
40/// }
41///
42/// // Generated code (conceptual):
43/// // impl DatabaseService {
44/// // pub fn new(connection_pool: Arc<ConnectionPool>, logger: Arc<Logger>) -> Self {
45/// // Self { connection_pool, logger }
46/// // }
47/// // }
48/// ```
49///
50/// ## Unit Struct (No Dependencies)
51///
52/// ```rust,ignore
53/// use service_rs::Injectable;
54///
55/// #[derive(Injectable)]
56/// struct SimpleService;
57///
58/// // Generated code (conceptual):
59/// // impl SimpleService {
60/// // pub fn new() -> Self {
61/// // Self
62/// // }
63/// // }
64/// ```
65///
66/// ## Registration with Service Collection
67///
68/// ```rust,ignore
69/// use service_rs::{Injectable, ServiceCollection};
70/// use std::sync::Arc;
71///
72/// #[derive(Injectable)]
73/// struct MyService {
74/// dependency: Arc<i32>,
75/// }
76///
77/// let collection = ServiceCollection::new()
78/// .add_singleton_with_factory::<i32, _, _>(|_| async {
79/// Ok(Box::new(42) as Box<dyn std::any::Any + Send + Sync>)
80/// })
81/// .add_singleton::<MyService>(); // Uses Injectable
82/// ```
83///
84/// # Compile-Time Errors
85///
86/// The macro will produce compile errors if:
87///
88/// - Fields are not wrapped in `Arc<T>`:
89/// ```compile_fail
90/// #[derive(Injectable)]
91/// struct BadService {
92/// field: String, // Error: must be Arc<String>
93/// }
94/// ```
95///
96/// - Used on enums:
97/// ```compile_fail
98/// #[derive(Injectable)]
99/// enum BadEnum { Variant } // Error: Injectable only works with structs
100/// ```
101///
102/// - Used with unnamed fields:
103/// ```compile_fail
104/// #[derive(Injectable)]
105/// struct BadService(Arc<String>); // Error: must use named fields
106/// ```
107///
108/// # Runtime Behavior
109///
110/// When a service is resolved:
111/// 1. The factory function is called with a `ServiceProviderContext`
112/// 2. Each dependency is resolved asynchronously via `ctx.get::<T>()`
113/// 3. If any dependency fails to resolve, an error is propagated
114/// 4. On success, the constructor is called with all resolved dependencies
115///
116/// # Performance Notes
117///
118/// - Dependencies are resolved lazily when the service is first requested
119/// - `Arc<T>` provides efficient reference counting for shared ownership
120/// - Async resolution allows for non-blocking initialization
121#[proc_macro_derive(Injectable)]
122pub fn derive_injectable(input: TokenStream) -> TokenStream {
123 let input = syn::parse_macro_input!(input as DeriveInput);
124 let name = input.ident;
125
126 let expanded = match input.data {
127 Data::Struct(data_struct) => match data_struct.fields {
128 syn::Fields::Named(fields_named) => {
129 let mut params = Vec::new();
130 let mut inits = Vec::new();
131 let mut inner_types = Vec::new();
132
133 for field in fields_named.named {
134 let ident = field.ident.unwrap();
135 let ty = field.ty;
136
137 let inner_ty = match &ty {
138 syn::Type::Path(type_path) => {
139 if let Some(seg) = type_path.path.segments.first() {
140 if seg.ident == "Arc" {
141 if let syn::PathArguments::AngleBracketed(args) = &seg.arguments
142 {
143 if let Some(syn::GenericArgument::Type(inner)) =
144 args.args.first()
145 {
146 Some(inner.clone())
147 } else {
148 None
149 }
150 } else {
151 None
152 }
153 } else {
154 None
155 }
156 } else {
157 None
158 }
159 }
160 _ => None,
161 };
162
163 if inner_ty.is_none() {
164 return quote::quote! {
165 compile_error!(concat!("Field `", stringify!(#ident), "` in `", stringify!(#name), "` must be wrapped in Arc<T>!. But if you want to make this object Injectable, consider using `ServiceCollection::add_singleton_with_factory`, `ServiceCollection::add_scoped_with_factory` or `ServiceCollection::add_transient_with_factory` instead."));
166 }.into();
167 }
168
169 params.push(quote::quote! { #ident: #ty });
170 inits.push(quote::quote! { #ident });
171 inner_types.push(inner_ty.unwrap());
172 }
173
174 let field_idents = inits.clone();
175
176 quote::quote! {
177 impl #name {
178 pub fn new(#(#params),*) -> Self {
179 Self {
180 #(#inits),*
181 }
182 }
183
184 pub async fn from_service_provider(provider: std::sync::Arc<service_rs::ServiceProvider>) -> Result<Self, service_rs::ServiceError> {
185 #(
186 let #field_idents = provider.get::<#inner_types>().await?;
187 )*
188 Ok(#name::new(#(#field_idents),*))
189 }
190 }
191
192 impl service_rs::InjectableExtension for #name {
193 fn create_factory() -> service_rs::ServiceFactory {
194 Box::new(|ctx: service_rs::ServiceProviderContext| {
195 Box::pin(async move {
196 #(
197 let #field_idents = ctx.get::<#inner_types>().await
198 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
199 )*
200
201 Ok(Box::new(#name::new(#(#field_idents),*)) as Box<dyn std::any::Any + Send + Sync>)
202 })
203 })
204 }
205 }
206 }
207 }
208 syn::Fields::Unit => {
209 quote::quote! {
210 impl #name {
211 pub fn new() -> Self {
212 Self
213 }
214
215 pub async fn from_service_provider(_provider: std::sync::Arc<service_rs::ServiceProvider>) -> Self {
216 #name::new()
217 }
218 }
219
220 impl service_rs::InjectableExtension for #name {
221 fn create_factory() -> service_rs::ServiceFactory {
222 Box::new(|_ctx: service_rs::ServiceProviderContext| {
223 Box::pin(async move {
224 Ok(Box::new(#name::new()) as Box<dyn std::any::Any + Send + Sync>)
225 })
226 })
227 }
228 }
229 }
230 }
231 _ => quote::quote! {
232 compile_error!("Injectable can only be used with named fields or unit struct!");
233 },
234 },
235 _ => quote::quote! {
236 compile_error!("Injectable can only be used with struct!");
237 },
238 };
239
240 expanded.into()
241}