allframe_macros/
lib.rs

1//! AllFrame Procedural Macros
2//!
3//! This crate provides compile-time code generation for AllFrame,
4//! including dependency injection, OpenAPI schema generation, and more.
5
6#![deny(warnings)]
7
8mod api;
9mod arch;
10mod cqrs;
11mod di;
12mod error;
13mod health;
14mod otel;
15mod resilience;
16mod security;
17
18use proc_macro::TokenStream;
19
20// Note: The `provide` attribute is handled directly by `di_container` macro
21// It doesn't need a separate proc_macro_attribute since it's consumed during
22// parsing
23
24/// Compile-time dependency injection container
25///
26/// Generates a container with automatic dependency resolution at compile time.
27///
28/// # Attributes
29///
30/// - `#[provide(expr)]` - Use custom expression for initialization
31/// - `#[provide(from_env)]` - Load from environment using `FromEnv` trait
32/// - `#[provide(singleton)]` - Shared instance (default)
33/// - `#[provide(transient)]` - New instance on each access
34/// - `#[provide(async)]` - Async initialization using `AsyncInit` trait
35/// - `#[depends(field1, field2)]` - Explicit dependencies
36///
37/// # Example (Sync)
38/// ```ignore
39/// #[di_container]
40/// struct AppContainer {
41///     database: DatabaseService,
42///     repository: UserRepository,
43/// }
44///
45/// let container = AppContainer::new();
46/// ```
47///
48/// # Example (Async)
49/// ```ignore
50/// #[di_container]
51/// struct AppContainer {
52///     #[provide(from_env)]
53///     config: Config,
54///
55///     #[provide(singleton, async)]
56///     #[depends(config)]
57///     database: DatabasePool,
58///
59///     #[provide(transient)]
60///     service: MyService,
61/// }
62///
63/// let container = AppContainer::build().await?;
64/// ```
65#[proc_macro_attribute]
66pub fn di_container(attr: TokenStream, item: TokenStream) -> TokenStream {
67    let attr = proc_macro2::TokenStream::from(attr);
68    let item = proc_macro2::TokenStream::from(item);
69
70    di::di_container_impl(attr, item)
71        .unwrap_or_else(|err| err.to_compile_error())
72        .into()
73}
74
75/// API handler with auto OpenAPI generation
76///
77/// Generates OpenAPI 3.1 schema for the annotated function.
78///
79/// # Example
80/// ```ignore
81/// #[api_handler(path = "/users", method = "POST", description = "Create user")]
82/// async fn create_user(req: CreateUserRequest) -> CreateUserResponse {
83///     // handler implementation
84/// }
85///
86/// // Generated function:
87/// // fn create_user_openapi_schema() -> String { /* JSON schema */ }
88/// ```
89#[proc_macro_attribute]
90pub fn api_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
91    let attr = proc_macro2::TokenStream::from(attr);
92    let item = proc_macro2::TokenStream::from(item);
93
94    api::api_handler_impl(attr, item)
95        .unwrap_or_else(|err| err.to_compile_error())
96        .into()
97}
98
99/// Marks a type as part of the Domain layer (Layer 1)
100///
101/// Domain entities contain pure business logic with no infrastructure
102/// dependencies.
103///
104/// # Example
105/// ```ignore
106/// #[domain]
107/// struct User {
108///     id: UserId,
109///     email: Email,
110/// }
111/// ```
112#[proc_macro_attribute]
113pub fn domain(attr: TokenStream, item: TokenStream) -> TokenStream {
114    let attr = proc_macro2::TokenStream::from(attr);
115    let item = proc_macro2::TokenStream::from(item);
116
117    arch::domain_impl(attr, item)
118        .unwrap_or_else(|err| err.to_compile_error())
119        .into()
120}
121
122/// Marks a type as part of the Repository layer (Layer 2)
123///
124/// Repositories handle data access and can depend on Domain entities.
125///
126/// # Example
127/// ```ignore
128/// #[repository]
129/// trait UserRepository: Send + Sync {
130///     async fn find(&self, id: UserId) -> Option<User>;
131/// }
132/// ```
133#[proc_macro_attribute]
134pub fn repository(attr: TokenStream, item: TokenStream) -> TokenStream {
135    let attr = proc_macro2::TokenStream::from(attr);
136    let item = proc_macro2::TokenStream::from(item);
137
138    arch::repository_impl(attr, item)
139        .unwrap_or_else(|err| err.to_compile_error())
140        .into()
141}
142
143/// Marks a type as part of the Use Case layer (Layer 3)
144///
145/// Use cases orchestrate application logic and can depend on Repositories and
146/// Domain.
147///
148/// # Example
149/// ```ignore
150/// #[use_case]
151/// struct GetUserUseCase {
152///     repo: Arc<dyn UserRepository>,
153/// }
154/// ```
155#[proc_macro_attribute]
156pub fn use_case(attr: TokenStream, item: TokenStream) -> TokenStream {
157    let attr = proc_macro2::TokenStream::from(attr);
158    let item = proc_macro2::TokenStream::from(item);
159
160    arch::use_case_impl(attr, item)
161        .unwrap_or_else(|err| err.to_compile_error())
162        .into()
163}
164
165/// Marks a type as part of the Handler layer (Layer 4)
166///
167/// Handlers are entry points (HTTP/gRPC/GraphQL) and can only depend on Use
168/// Cases. Handlers CANNOT depend on Repositories directly - they must go
169/// through Use Cases.
170///
171/// # Example
172/// ```ignore
173/// #[handler]
174/// struct GetUserHandler {
175///     use_case: Arc<GetUserUseCase>,
176/// }
177/// ```
178#[proc_macro_attribute]
179pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
180    let attr = proc_macro2::TokenStream::from(attr);
181    let item = proc_macro2::TokenStream::from(item);
182
183    arch::handler_impl(attr, item)
184        .unwrap_or_else(|err| err.to_compile_error())
185        .into()
186}
187
188/// Marks a struct as a Command (CQRS write operation)
189///
190/// Commands represent write operations that change state and produce events.
191///
192/// # Example
193/// ```ignore
194/// #[command]
195/// struct CreateUserCommand {
196///     email: String,
197///     name: String,
198/// }
199/// ```
200#[proc_macro_attribute]
201pub fn command(attr: TokenStream, item: TokenStream) -> TokenStream {
202    let attr = proc_macro2::TokenStream::from(attr);
203    let item = proc_macro2::TokenStream::from(item);
204
205    cqrs::command_impl(attr, item)
206        .unwrap_or_else(|err| err.to_compile_error())
207        .into()
208}
209
210/// Marks a struct as a Query (CQRS read operation)
211///
212/// Queries represent read operations that don't change state.
213///
214/// # Example
215/// ```ignore
216/// #[query]
217/// struct GetUserQuery {
218///     user_id: String,
219/// }
220/// ```
221#[proc_macro_attribute]
222pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
223    let attr = proc_macro2::TokenStream::from(attr);
224    let item = proc_macro2::TokenStream::from(item);
225
226    cqrs::query_impl(attr, item)
227        .unwrap_or_else(|err| err.to_compile_error())
228        .into()
229}
230
231/// Marks an enum or struct as an Event
232///
233/// Events represent immutable facts that have occurred in the system.
234///
235/// # Example
236/// ```ignore
237/// #[event]
238/// enum UserEvent {
239///     Created { user_id: String, email: String },
240///     Updated { user_id: String, email: String },
241/// }
242/// ```
243#[proc_macro_attribute]
244pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
245    let attr = proc_macro2::TokenStream::from(attr);
246    let item = proc_macro2::TokenStream::from(item);
247
248    cqrs::event_impl(attr, item)
249        .unwrap_or_else(|err| err.to_compile_error())
250        .into()
251}
252
253/// Marks a function as a Command Handler
254///
255/// Command handlers process commands and produce events.
256///
257/// # Example
258/// ```ignore
259/// #[command_handler]
260/// async fn handle_create_user(cmd: CreateUserCommand) -> Result<Vec<Event>, String> {
261///     Ok(vec![Event::UserCreated { ... }])
262/// }
263/// ```
264#[proc_macro_attribute]
265pub fn command_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
266    let attr = proc_macro2::TokenStream::from(attr);
267    let item = proc_macro2::TokenStream::from(item);
268
269    cqrs::command_handler_impl(attr, item)
270        .unwrap_or_else(|err| err.to_compile_error())
271        .into()
272}
273
274/// Marks a function as a Query Handler
275///
276/// Query handlers process queries and return data from projections.
277///
278/// # Example
279/// ```ignore
280/// #[query_handler]
281/// async fn handle_get_user(query: GetUserQuery) -> Result<Option<User>, String> {
282///     Ok(Some(user))
283/// }
284/// ```
285#[proc_macro_attribute]
286pub fn query_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
287    let attr = proc_macro2::TokenStream::from(attr);
288    let item = proc_macro2::TokenStream::from(item);
289
290    cqrs::query_handler_impl(attr, item)
291        .unwrap_or_else(|err| err.to_compile_error())
292        .into()
293}
294
295/// Marks a function to be automatically traced with OpenTelemetry
296///
297/// Automatically creates spans with proper context propagation.
298///
299/// # Example
300/// ```ignore
301/// #[traced]
302/// async fn fetch_user(user_id: String) -> Result<User, Error> {
303///     // Span created automatically with name "fetch_user"
304///     // Span includes function arguments as attributes
305///     Ok(user)
306/// }
307/// ```
308#[proc_macro_attribute]
309pub fn traced(attr: TokenStream, item: TokenStream) -> TokenStream {
310    let attr = proc_macro2::TokenStream::from(attr);
311    let item = proc_macro2::TokenStream::from(item);
312
313    otel::traced_impl(attr, item)
314        .unwrap_or_else(|err| err.to_compile_error())
315        .into()
316}
317
318/// Derive macro for automatic gRPC status conversion
319///
320/// Generates `From<Error> for tonic::Status` implementation.
321/// Use `#[grpc(CODE)]` on variants to specify the gRPC status code.
322///
323/// # Example
324/// ```ignore
325/// use allframe_macros::GrpcError;
326/// use thiserror::Error;
327///
328/// #[derive(Error, Debug, GrpcError)]
329/// pub enum AppError {
330///     #[error("Unauthenticated: {0}")]
331///     #[grpc(UNAUTHENTICATED)]
332///     Unauthenticated(String),
333///
334///     #[error("Rate limited")]
335///     #[grpc(RESOURCE_EXHAUSTED)]
336///     RateLimited,
337///
338///     #[error("Not found: {0}")]
339///     #[grpc(NOT_FOUND)]
340///     NotFound(String),
341///
342///     #[error("Internal error: {0}")]
343///     #[grpc(INTERNAL)]
344///     Internal(String),
345/// }
346///
347/// // Auto-generates: impl From<AppError> for tonic::Status
348/// ```
349///
350/// # Supported gRPC Codes
351/// - `OK`, `CANCELLED`, `UNKNOWN`, `INVALID_ARGUMENT`
352/// - `DEADLINE_EXCEEDED`, `NOT_FOUND`, `ALREADY_EXISTS`
353/// - `PERMISSION_DENIED`, `RESOURCE_EXHAUSTED`, `FAILED_PRECONDITION`
354/// - `ABORTED`, `OUT_OF_RANGE`, `UNIMPLEMENTED`, `INTERNAL`
355/// - `UNAVAILABLE`, `DATA_LOSS`, `UNAUTHENTICATED`
356#[proc_macro_derive(GrpcError, attributes(grpc))]
357pub fn grpc_error(input: TokenStream) -> TokenStream {
358    let input = proc_macro2::TokenStream::from(input);
359
360    error::grpc_error_impl(input)
361        .unwrap_or_else(|err| err.to_compile_error())
362        .into()
363}
364
365/// Derive macro for automatic HealthCheck implementation
366///
367/// Generates the `HealthCheck` trait implementation by collecting all fields
368/// that implement the `Dependency` trait.
369///
370/// # Example
371/// ```ignore
372/// use allframe_macros::HealthCheck;
373/// use allframe_core::health::{Dependency, DependencyStatus};
374///
375/// struct RedisDependency { /* ... */ }
376/// impl Dependency for RedisDependency { /* ... */ }
377///
378/// struct DatabaseDependency { /* ... */ }
379/// impl Dependency for DatabaseDependency { /* ... */ }
380///
381/// #[derive(HealthCheck)]
382/// struct AppHealth {
383///     #[health(timeout = "5s", critical = true)]
384///     redis: RedisDependency,
385///
386///     #[health(timeout = "10s", critical = false)]
387///     database: DatabaseDependency,
388///
389///     #[health(skip)]
390///     config: Config, // Not a dependency
391/// }
392///
393/// // Auto-generates:
394/// // impl HealthCheck for AppHealth { ... }
395/// ```
396///
397/// # Attributes
398/// - `#[health(skip)]` - Skip this field from health checks
399/// - `#[health(critical = true)]` - Mark dependency as critical (default: true)
400/// - `#[health(timeout = "5s")]` - Set check timeout (default: 5s)
401#[proc_macro_derive(HealthCheck, attributes(health))]
402pub fn health_check(input: TokenStream) -> TokenStream {
403    let input = proc_macro2::TokenStream::from(input);
404
405    health::health_check_impl(input)
406        .unwrap_or_else(|err| err.to_compile_error())
407        .into()
408}
409
410/// Derive macro for automatic Obfuscate implementation
411///
412/// Generates the `Obfuscate` trait implementation that safely logs struct
413/// fields, obfuscating those marked with `#[sensitive]`.
414///
415/// # Example
416/// ```ignore
417/// use allframe_macros::Obfuscate;
418///
419/// #[derive(Obfuscate)]
420/// struct DatabaseConfig {
421///     host: String,
422///     port: u16,
423///     #[sensitive]
424///     password: String,
425///     #[sensitive]
426///     api_key: String,
427/// }
428///
429/// let config = DatabaseConfig {
430///     host: "localhost".to_string(),
431///     port: 5432,
432///     password: "secret".to_string(),
433///     api_key: "sk_live_abc123".to_string(),
434/// };
435///
436/// // Output: "DatabaseConfig { host: "localhost", port: 5432, password: ***, api_key: *** }"
437/// println!("{}", config.obfuscate());
438/// ```
439///
440/// # Attributes
441/// - `#[sensitive]` - Mark field as sensitive, will be displayed as `***`
442/// - `#[obfuscate(with = "function_name")]` - Use custom function to obfuscate
443#[proc_macro_derive(Obfuscate, attributes(sensitive, obfuscate))]
444pub fn obfuscate(input: TokenStream) -> TokenStream {
445    let input = proc_macro2::TokenStream::from(input);
446
447    security::obfuscate_impl(input)
448        .unwrap_or_else(|err| err.to_compile_error())
449        .into()
450}
451
452/// Attribute macro for automatic retry with exponential backoff
453///
454/// Wraps an async function with retry logic using `RetryExecutor`.
455///
456/// # Example
457/// ```ignore
458/// use allframe_macros::retry;
459///
460/// #[retry(max_retries = 3, initial_interval_ms = 100)]
461/// async fn fetch_data() -> Result<String, std::io::Error> {
462///     // This will be retried up to 3 times on failure
463///     reqwest::get("https://api.example.com/data")
464///         .await?
465///         .text()
466///         .await
467/// }
468/// ```
469///
470/// # Parameters
471/// - `max_retries` - Maximum retry attempts (default: 3)
472/// - `initial_interval_ms` - Initial backoff in milliseconds (default: 500)
473/// - `max_interval_ms` - Maximum backoff in milliseconds (default: 30000)
474/// - `multiplier` - Backoff multiplier (default: 2.0)
475#[proc_macro_attribute]
476pub fn retry(attr: TokenStream, item: TokenStream) -> TokenStream {
477    let attr = proc_macro2::TokenStream::from(attr);
478    let item = proc_macro2::TokenStream::from(item);
479
480    resilience::retry_impl(attr, item)
481        .unwrap_or_else(|err| err.to_compile_error())
482        .into()
483}
484
485/// Attribute macro for circuit breaker pattern
486///
487/// Wraps a function with circuit breaker logic for fail-fast behavior.
488///
489/// # Example
490/// ```ignore
491/// use allframe_macros::circuit_breaker;
492///
493/// #[circuit_breaker(name = "external_api", failure_threshold = 5)]
494/// async fn call_external_api() -> Result<String, std::io::Error> {
495///     // After 5 failures, the circuit opens and calls fail fast
496///     external_service::call().await
497/// }
498/// ```
499///
500/// # Parameters
501/// - `name` - Circuit breaker name (default: function name)
502/// - `failure_threshold` - Failures before opening (default: 5)
503/// - `success_threshold` - Successes to close in half-open (default: 3)
504/// - `timeout_ms` - Time before half-open in milliseconds (default: 30000)
505#[proc_macro_attribute]
506pub fn circuit_breaker(attr: TokenStream, item: TokenStream) -> TokenStream {
507    let attr = proc_macro2::TokenStream::from(attr);
508    let item = proc_macro2::TokenStream::from(item);
509
510    resilience::circuit_breaker_impl(attr, item)
511        .unwrap_or_else(|err| err.to_compile_error())
512        .into()
513}
514
515/// Attribute macro for rate limiting
516///
517/// Wraps a function with rate limiting using token bucket algorithm.
518///
519/// # Example
520/// ```ignore
521/// use allframe_macros::rate_limited;
522///
523/// #[rate_limited(rps = 100, burst = 10)]
524/// fn handle_request() -> Result<Response, std::io::Error> {
525///     // Limited to 100 requests per second with burst of 10
526///     process_request()
527/// }
528/// ```
529///
530/// # Parameters
531/// - `rps` - Requests per second (default: 100)
532/// - `burst` - Burst capacity (default: 10)
533#[proc_macro_attribute]
534pub fn rate_limited(attr: TokenStream, item: TokenStream) -> TokenStream {
535    let attr = proc_macro2::TokenStream::from(attr);
536    let item = proc_macro2::TokenStream::from(item);
537
538    resilience::rate_limited_impl(attr, item)
539        .unwrap_or_else(|err| err.to_compile_error())
540        .into()
541}
542
543#[cfg(test)]
544mod tests {
545    #[test]
546    fn test_macros_crate_compiles() {
547        assert!(true);
548    }
549}