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}