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}