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}