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