Skip to main content

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 saga;
17mod security;
18
19use proc_macro::TokenStream;
20
21// Note: The `provide` attribute is handled directly by `di_container` macro
22// It doesn't need a separate proc_macro_attribute since it's consumed during
23// parsing
24
25/// Compile-time dependency injection container
26///
27/// Generates a container with automatic dependency resolution at compile time.
28///
29/// # Attributes
30///
31/// - `#[provide(expr)]` - Use custom expression for initialization
32/// - `#[provide(from_env)]` - Load from environment using `FromEnv` trait
33/// - `#[provide(singleton)]` - Shared instance (default)
34/// - `#[provide(transient)]` - New instance on each access
35/// - `#[provide(async)]` - Async initialization using `AsyncInit` trait
36/// - `#[depends(field1, field2)]` - Explicit dependencies
37///
38/// # Example (Sync)
39/// ```ignore
40/// #[di_container]
41/// struct AppContainer {
42///     database: DatabaseService,
43///     repository: UserRepository,
44/// }
45///
46/// let container = AppContainer::new();
47/// ```
48///
49/// # Example (Async)
50/// ```ignore
51/// #[di_container]
52/// struct AppContainer {
53///     #[provide(from_env)]
54///     config: Config,
55///
56///     #[provide(singleton, async)]
57///     #[depends(config)]
58///     database: DatabasePool,
59///
60///     #[provide(transient)]
61///     service: MyService,
62/// }
63///
64/// let container = AppContainer::build().await?;
65/// ```
66#[proc_macro_attribute]
67pub fn di_container(attr: TokenStream, item: TokenStream) -> TokenStream {
68    let attr = proc_macro2::TokenStream::from(attr);
69    let item = proc_macro2::TokenStream::from(item);
70
71    di::di_container_impl(attr, item)
72        .unwrap_or_else(|err| err.to_compile_error())
73        .into()
74}
75
76/// API handler with auto OpenAPI generation
77///
78/// Generates OpenAPI 3.1 schema for the annotated function.
79///
80/// # Example
81/// ```ignore
82/// #[api_handler(path = "/users", method = "POST", description = "Create user")]
83/// async fn create_user(req: CreateUserRequest) -> CreateUserResponse {
84///     // handler implementation
85/// }
86///
87/// // Generated function:
88/// // fn create_user_openapi_schema() -> String { /* JSON schema */ }
89/// ```
90#[proc_macro_attribute]
91pub fn api_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
92    let attr = proc_macro2::TokenStream::from(attr);
93    let item = proc_macro2::TokenStream::from(item);
94
95    api::api_handler_impl(attr, item)
96        .unwrap_or_else(|err| err.to_compile_error())
97        .into()
98}
99
100/// Marks a type as part of the Domain layer (Layer 1)
101///
102/// Domain entities contain pure business logic with no infrastructure
103/// dependencies.
104///
105/// # Example
106/// ```ignore
107/// #[domain]
108/// struct User {
109///     id: UserId,
110///     email: Email,
111/// }
112/// ```
113#[proc_macro_attribute]
114pub fn domain(attr: TokenStream, item: TokenStream) -> TokenStream {
115    let attr = proc_macro2::TokenStream::from(attr);
116    let item = proc_macro2::TokenStream::from(item);
117
118    arch::domain_impl(attr, item)
119        .unwrap_or_else(|err| err.to_compile_error())
120        .into()
121}
122
123/// Marks a type as part of the Repository layer (Layer 2)
124///
125/// Repositories handle data access and can depend on Domain entities.
126///
127/// # Example
128/// ```ignore
129/// #[repository]
130/// trait UserRepository: Send + Sync {
131///     async fn find(&self, id: UserId) -> Option<User>;
132/// }
133/// ```
134#[proc_macro_attribute]
135pub fn repository(attr: TokenStream, item: TokenStream) -> TokenStream {
136    let attr = proc_macro2::TokenStream::from(attr);
137    let item = proc_macro2::TokenStream::from(item);
138
139    arch::repository_impl(attr, item)
140        .unwrap_or_else(|err| err.to_compile_error())
141        .into()
142}
143
144/// Marks a type as part of the Use Case layer (Layer 3)
145///
146/// Use cases orchestrate application logic and can depend on Repositories and
147/// Domain.
148///
149/// # Example
150/// ```ignore
151/// #[use_case]
152/// struct GetUserUseCase {
153///     repo: Arc<dyn UserRepository>,
154/// }
155/// ```
156#[proc_macro_attribute]
157pub fn use_case(attr: TokenStream, item: TokenStream) -> TokenStream {
158    let attr = proc_macro2::TokenStream::from(attr);
159    let item = proc_macro2::TokenStream::from(item);
160
161    arch::use_case_impl(attr, item)
162        .unwrap_or_else(|err| err.to_compile_error())
163        .into()
164}
165
166/// Marks a type as part of the Handler layer (Layer 4)
167///
168/// Handlers are entry points (HTTP/gRPC/GraphQL) and can only depend on Use
169/// Cases. Handlers CANNOT depend on Repositories directly - they must go
170/// through Use Cases.
171///
172/// # Example
173/// ```ignore
174/// #[handler]
175/// struct GetUserHandler {
176///     use_case: Arc<GetUserUseCase>,
177/// }
178/// ```
179#[proc_macro_attribute]
180pub fn handler(attr: TokenStream, item: TokenStream) -> TokenStream {
181    let attr = proc_macro2::TokenStream::from(attr);
182    let item = proc_macro2::TokenStream::from(item);
183
184    arch::handler_impl(attr, item)
185        .unwrap_or_else(|err| err.to_compile_error())
186        .into()
187}
188
189/// Marks a struct as a Command (CQRS write operation)
190///
191/// Commands represent write operations that change state and produce events.
192///
193/// # Example
194/// ```ignore
195/// #[command]
196/// struct CreateUserCommand {
197///     email: String,
198///     name: String,
199/// }
200/// ```
201#[proc_macro_attribute]
202pub fn command(attr: TokenStream, item: TokenStream) -> TokenStream {
203    let attr = proc_macro2::TokenStream::from(attr);
204    let item = proc_macro2::TokenStream::from(item);
205
206    cqrs::command_impl(attr, item)
207        .unwrap_or_else(|err| err.to_compile_error())
208        .into()
209}
210
211/// Marks a struct as a Query (CQRS read operation)
212///
213/// Queries represent read operations that don't change state.
214///
215/// # Example
216/// ```ignore
217/// #[query]
218/// struct GetUserQuery {
219///     user_id: String,
220/// }
221/// ```
222#[proc_macro_attribute]
223pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream {
224    let attr = proc_macro2::TokenStream::from(attr);
225    let item = proc_macro2::TokenStream::from(item);
226
227    cqrs::query_impl(attr, item)
228        .unwrap_or_else(|err| err.to_compile_error())
229        .into()
230}
231
232/// Marks an enum or struct as an Event
233///
234/// Events represent immutable facts that have occurred in the system.
235///
236/// # Example
237/// ```ignore
238/// #[event]
239/// enum UserEvent {
240///     Created { user_id: String, email: String },
241///     Updated { user_id: String, email: String },
242/// }
243/// ```
244#[proc_macro_attribute]
245pub fn event(attr: TokenStream, item: TokenStream) -> TokenStream {
246    let attr = proc_macro2::TokenStream::from(attr);
247    let item = proc_macro2::TokenStream::from(item);
248
249    cqrs::event_impl(attr, item)
250        .unwrap_or_else(|err| err.to_compile_error())
251        .into()
252}
253
254/// Marks a function as a Command Handler
255///
256/// Command handlers process commands and produce events.
257///
258/// # Example
259/// ```ignore
260/// #[command_handler]
261/// async fn handle_create_user(cmd: CreateUserCommand) -> Result<Vec<Event>, String> {
262///     Ok(vec![Event::UserCreated { ... }])
263/// }
264/// ```
265#[proc_macro_attribute]
266pub fn command_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
267    let attr = proc_macro2::TokenStream::from(attr);
268    let item = proc_macro2::TokenStream::from(item);
269
270    cqrs::command_handler_impl(attr, item)
271        .unwrap_or_else(|err| err.to_compile_error())
272        .into()
273}
274
275/// Marks a function as a Query Handler
276///
277/// Query handlers process queries and return data from projections.
278///
279/// # Example
280/// ```ignore
281/// #[query_handler]
282/// async fn handle_get_user(query: GetUserQuery) -> Result<Option<User>, String> {
283///     Ok(Some(user))
284/// }
285/// ```
286#[proc_macro_attribute]
287pub fn query_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
288    let attr = proc_macro2::TokenStream::from(attr);
289    let item = proc_macro2::TokenStream::from(item);
290
291    cqrs::query_handler_impl(attr, item)
292        .unwrap_or_else(|err| err.to_compile_error())
293        .into()
294}
295
296/// Marks a function to be automatically traced with OpenTelemetry
297///
298/// Automatically creates spans with proper context propagation.
299///
300/// # Example
301/// ```ignore
302/// #[traced]
303/// async fn fetch_user(user_id: String) -> Result<User, Error> {
304///     // Span created automatically with name "fetch_user"
305///     // Span includes function arguments as attributes
306///     Ok(user)
307/// }
308/// ```
309#[proc_macro_attribute]
310pub fn traced(attr: TokenStream, item: TokenStream) -> TokenStream {
311    let attr = proc_macro2::TokenStream::from(attr);
312    let item = proc_macro2::TokenStream::from(item);
313
314    otel::traced_impl(attr, item)
315        .unwrap_or_else(|err| err.to_compile_error())
316        .into()
317}
318
319/// Derive macro for automatic gRPC status conversion
320///
321/// Generates `From<Error> for tonic::Status` implementation.
322/// Use `#[grpc(CODE)]` on variants to specify the gRPC status code.
323///
324/// # Example
325/// ```ignore
326/// use allframe_macros::GrpcError;
327/// use thiserror::Error;
328///
329/// #[derive(Error, Debug, GrpcError)]
330/// pub enum AppError {
331///     #[error("Unauthenticated: {0}")]
332///     #[grpc(UNAUTHENTICATED)]
333///     Unauthenticated(String),
334///
335///     #[error("Rate limited")]
336///     #[grpc(RESOURCE_EXHAUSTED)]
337///     RateLimited,
338///
339///     #[error("Not found: {0}")]
340///     #[grpc(NOT_FOUND)]
341///     NotFound(String),
342///
343///     #[error("Internal error: {0}")]
344///     #[grpc(INTERNAL)]
345///     Internal(String),
346/// }
347///
348/// // Auto-generates: impl From<AppError> for tonic::Status
349/// ```
350///
351/// # Supported gRPC Codes
352/// - `OK`, `CANCELLED`, `UNKNOWN`, `INVALID_ARGUMENT`
353/// - `DEADLINE_EXCEEDED`, `NOT_FOUND`, `ALREADY_EXISTS`
354/// - `PERMISSION_DENIED`, `RESOURCE_EXHAUSTED`, `FAILED_PRECONDITION`
355/// - `ABORTED`, `OUT_OF_RANGE`, `UNIMPLEMENTED`, `INTERNAL`
356/// - `UNAVAILABLE`, `DATA_LOSS`, `UNAUTHENTICATED`
357#[proc_macro_derive(GrpcError, attributes(grpc))]
358pub fn grpc_error(input: TokenStream) -> TokenStream {
359    let input = proc_macro2::TokenStream::from(input);
360
361    error::grpc_error_impl(input)
362        .unwrap_or_else(|err| err.to_compile_error())
363        .into()
364}
365
366/// Derive macro for automatic HealthCheck implementation
367///
368/// Generates the `HealthCheck` trait implementation by collecting all fields
369/// that implement the `Dependency` trait.
370///
371/// # Example
372/// ```ignore
373/// use allframe_macros::HealthCheck;
374/// use allframe_core::health::{Dependency, DependencyStatus};
375///
376/// struct RedisDependency { /* ... */ }
377/// impl Dependency for RedisDependency { /* ... */ }
378///
379/// struct DatabaseDependency { /* ... */ }
380/// impl Dependency for DatabaseDependency { /* ... */ }
381///
382/// #[derive(HealthCheck)]
383/// struct AppHealth {
384///     #[health(timeout = "5s", critical = true)]
385///     redis: RedisDependency,
386///
387///     #[health(timeout = "10s", critical = false)]
388///     database: DatabaseDependency,
389///
390///     #[health(skip)]
391///     config: Config, // Not a dependency
392/// }
393///
394/// // Auto-generates:
395/// // impl HealthCheck for AppHealth { ... }
396/// ```
397///
398/// # Attributes
399/// - `#[health(skip)]` - Skip this field from health checks
400/// - `#[health(critical = true)]` - Mark dependency as critical (default: true)
401/// - `#[health(timeout = "5s")]` - Set check timeout (default: 5s)
402#[proc_macro_derive(HealthCheck, attributes(health))]
403pub fn health_check(input: TokenStream) -> TokenStream {
404    let input = proc_macro2::TokenStream::from(input);
405
406    health::health_check_impl(input)
407        .unwrap_or_else(|err| err.to_compile_error())
408        .into()
409}
410
411/// Derive macro for automatic Obfuscate implementation
412///
413/// Generates the `Obfuscate` trait implementation that safely logs struct
414/// fields, obfuscating those marked with `#[sensitive]`.
415///
416/// # Example
417/// ```ignore
418/// use allframe_macros::Obfuscate;
419///
420/// #[derive(Obfuscate)]
421/// struct DatabaseConfig {
422///     host: String,
423///     port: u16,
424///     #[sensitive]
425///     password: String,
426///     #[sensitive]
427///     api_key: String,
428/// }
429///
430/// let config = DatabaseConfig {
431///     host: "localhost".to_string(),
432///     port: 5432,
433///     password: "secret".to_string(),
434///     api_key: "sk_live_abc123".to_string(),
435/// };
436///
437/// // Output: "DatabaseConfig { host: "localhost", port: 5432, password: ***, api_key: *** }"
438/// println!("{}", config.obfuscate());
439/// ```
440///
441/// # Attributes
442/// - `#[sensitive]` - Mark field as sensitive, will be displayed as `***`
443/// - `#[obfuscate(with = "function_name")]` - Use custom function to obfuscate
444#[proc_macro_derive(Obfuscate, attributes(sensitive, obfuscate))]
445pub fn obfuscate(input: TokenStream) -> TokenStream {
446    let input = proc_macro2::TokenStream::from(input);
447
448    security::obfuscate_impl(input)
449        .unwrap_or_else(|err| err.to_compile_error())
450        .into()
451}
452
453/// Attribute macro for automatic retry with exponential backoff
454///
455/// Wraps an async function with retry logic using `RetryExecutor`.
456///
457/// # Example
458/// ```ignore
459/// use allframe_macros::retry;
460///
461/// #[retry(max_retries = 3, initial_interval_ms = 100)]
462/// async fn fetch_data() -> Result<String, std::io::Error> {
463///     // This will be retried up to 3 times on failure
464///     reqwest::get("https://api.example.com/data")
465///         .await?
466///         .text()
467///         .await
468/// }
469/// ```
470///
471/// # Parameters
472/// - `max_retries` - Maximum retry attempts (default: 3)
473/// - `initial_interval_ms` - Initial backoff in milliseconds (default: 500)
474/// - `max_interval_ms` - Maximum backoff in milliseconds (default: 30000)
475/// - `multiplier` - Backoff multiplier (default: 2.0)
476#[proc_macro_attribute]
477pub fn retry(attr: TokenStream, item: TokenStream) -> TokenStream {
478    let attr = proc_macro2::TokenStream::from(attr);
479    let item = proc_macro2::TokenStream::from(item);
480
481    resilience::retry_impl(attr, item)
482        .unwrap_or_else(|err| err.to_compile_error())
483        .into()
484}
485
486/// Attribute macro for circuit breaker pattern
487///
488/// Wraps a function with circuit breaker logic for fail-fast behavior.
489///
490/// # Example
491/// ```ignore
492/// use allframe_macros::circuit_breaker;
493///
494/// #[circuit_breaker(name = "external_api", failure_threshold = 5)]
495/// async fn call_external_api() -> Result<String, std::io::Error> {
496///     // After 5 failures, the circuit opens and calls fail fast
497///     external_service::call().await
498/// }
499/// ```
500///
501/// # Parameters
502/// - `name` - Circuit breaker name (default: function name)
503/// - `failure_threshold` - Failures before opening (default: 5)
504/// - `success_threshold` - Successes to close in half-open (default: 3)
505/// - `timeout_ms` - Time before half-open in milliseconds (default: 30000)
506#[proc_macro_attribute]
507pub fn circuit_breaker(attr: TokenStream, item: TokenStream) -> TokenStream {
508    let attr = proc_macro2::TokenStream::from(attr);
509    let item = proc_macro2::TokenStream::from(item);
510
511    resilience::circuit_breaker_impl(attr, item)
512        .unwrap_or_else(|err| err.to_compile_error())
513        .into()
514}
515
516/// Attribute macro for rate limiting
517///
518/// Wraps a function with rate limiting using token bucket algorithm.
519///
520/// # Example
521/// ```ignore
522/// use allframe_macros::rate_limited;
523///
524/// #[rate_limited(rps = 100, burst = 10)]
525/// fn handle_request() -> Result<Response, std::io::Error> {
526///     // Limited to 100 requests per second with burst of 10
527///     process_request()
528/// }
529/// ```
530///
531/// # Parameters
532/// - `rps` - Requests per second (default: 100)
533/// - `burst` - Burst capacity (default: 10)
534#[proc_macro_attribute]
535pub fn rate_limited(attr: TokenStream, item: TokenStream) -> TokenStream {
536    let attr = proc_macro2::TokenStream::from(attr);
537    let item = proc_macro2::TokenStream::from(item);
538
539    resilience::rate_limited_impl(attr, item)
540        .unwrap_or_else(|err| err.to_compile_error())
541        .into()
542}
543
544/// Marks a struct as a saga step with automatic Debug and SagaStep trait
545/// implementation
546///
547/// Reduces boilerplate by generating:
548/// - Debug impl (skipping #[inject] fields)
549/// - SagaStep trait impl with metadata methods
550/// - Placeholder execute/compensate methods (user implements in separate impl
551///   block)
552///
553/// # Attributes
554///
555/// - `#[inject]` - Mark fields that should be dependency injected (skipped in
556///   Debug)
557///
558/// # Example
559/// ```ignore
560/// #[saga_step(name = "ValidateIndex", timeout_seconds = 10, requires_compensation = false)]
561/// struct ValidateIndexStep {
562///     #[inject] index_repository: Arc<dyn IndexRepository>,
563///     user_id: String,
564///     index_id: Uuid,
565/// }
566///
567/// impl ValidateIndexStep {
568///     async fn execute(&self, ctx: &SagaContext) -> StepExecutionResult {
569///         // actual logic
570///         Ok(StepExecutionResult::success())
571///     }
572/// }
573/// ```
574#[proc_macro_attribute]
575pub fn saga_step(attr: TokenStream, item: TokenStream) -> TokenStream {
576    let attr = proc_macro2::TokenStream::from(attr);
577    let item = proc_macro2::TokenStream::from(item);
578
579    saga::saga_step_impl(attr, item)
580        .unwrap_or_else(|err| err.to_compile_error())
581        .into()
582}
583
584/// Marks a struct as a saga container with automatic Saga trait implementation
585///
586/// Reduces boilerplate by generating:
587/// - Constructor accepting all #[inject] dependencies
588/// - Saga trait implementation
589/// - Automatic dependency injection setup
590///
591/// # Attributes
592///
593/// - `#[saga_data]` - Mark the field containing saga data (one per struct)
594/// - `#[inject]` - Mark fields that should be dependency injected
595///
596/// # Example
597/// ```ignore
598/// #[saga(name = "RebalancingSaga")]
599/// struct RebalancingSaga {
600///     #[saga_data]
601///     data: RebalancingSagaData,
602///
603///     #[inject]
604///     index_repository: Arc<dyn IndexRepository>,
605///     #[inject]
606///     trade_service: Arc<TradeService>,
607/// }
608/// ```
609#[proc_macro_attribute]
610pub fn saga(attr: TokenStream, item: TokenStream) -> TokenStream {
611    let attr = proc_macro2::TokenStream::from(attr);
612    let item = proc_macro2::TokenStream::from(item);
613
614    saga::saga_impl(attr, item)
615        .unwrap_or_else(|err| err.to_compile_error())
616        .into()
617}
618
619/// Defines the workflow for a saga using an enum
620///
621/// Generates step vector construction from enum variants.
622/// Each enum variant corresponds to a step struct (e.g., `ValidateIndex` ->
623/// `ValidateIndexStep`).
624///
625/// # Example
626/// ```ignore
627/// #[saga_workflow(RebalancingSaga)]
628/// enum RebalancingWorkflow {
629///     ValidateIndex,
630///     FetchBalances,
631///     ExecuteTrades,
632///     UpdateBalances,
633///     EmitCompletionEvent,
634/// }
635/// ```
636#[proc_macro_attribute]
637pub fn saga_workflow(attr: TokenStream, item: TokenStream) -> TokenStream {
638    let attr = proc_macro2::TokenStream::from(attr);
639    let item = proc_macro2::TokenStream::from(item);
640
641    saga::saga_workflow_impl(attr, item)
642        .unwrap_or_else(|err| err.to_compile_error())
643        .into()
644}
645
646/// Derive macro for type-safe step output extraction
647///
648/// Generates:
649/// - `from_context(ctx, step_name)` method for extracting typed output
650/// - `Into<StepExecutionResult>` impl for returning typed output
651///
652/// # Example
653/// ```ignore
654/// #[derive(StepOutput)]
655/// struct ValidateAndCreateTradeOutput {
656///     trade_id: Uuid,
657///     idempotency_key: String,
658/// }
659///
660/// // In step execute method:
661/// let output: ValidateAndCreateTradeOutput = ValidateAndCreateTradeOutput {
662///     trade_id: trade.id,
663///     idempotency_key: trade.idempotency_key,
664/// };
665/// return output.into(); // Converts to StepExecutionResult
666///
667/// // In another step:
668/// let output = ValidateAndCreateTradeOutput::from_context(ctx, "ValidateAndCreateTrade")?;
669/// let trade_id = output.trade_id;
670/// ```
671#[proc_macro_derive(StepOutput)]
672pub fn derive_step_output(input: TokenStream) -> TokenStream {
673    let input = proc_macro2::TokenStream::from(input);
674
675    saga::derive_step_output(input)
676        .unwrap_or_else(|err| err.to_compile_error())
677        .into()
678}
679
680/// Extract step output with type safety and error handling
681///
682/// Note: This proc-macro provides guidance but doesn't implement a declarative
683/// macro. Use the StepOutput trait for type-safe extraction instead.
684///
685/// # Example
686/// ```ignore
687/// // Type-safe extraction using StepOutput derive
688/// #[derive(StepOutput)]
689/// struct ValidateAndCreateTradeOutput {
690///     trade_id: Uuid,
691///     amount: f64,
692/// }
693///
694/// let output = ValidateAndCreateTradeOutput::from_context(ctx, "ValidateAndCreateTrade")?;
695/// let trade_id = output.trade_id;
696///
697/// // Or access raw JSON
698/// let raw_output = ctx.get_step_output("ValidateAndCreateTrade");
699/// ```
700#[proc_macro]
701pub fn extract_output(input: TokenStream) -> TokenStream {
702    let input = proc_macro2::TokenStream::from(input);
703
704    saga::extract_output_impl(input)
705        .unwrap_or_else(|err| err.to_compile_error())
706        .into()
707}
708
709#[cfg(test)]
710mod tests {
711    #[test]
712    fn test_macros_crate_compiles() {
713        assert!(true);
714    }
715}