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