sword_macros/lib.rs
1#![allow(irrefutable_let_patterns)]
2
3mod controllers;
4
5mod core;
6mod errors;
7mod interceptor_derive;
8mod shared;
9mod interceptor {
10 mod parse;
11 pub use parse::InterceptorArgs;
12}
13
14use proc_macro::TokenStream;
15use quote::quote;
16use syn::{DeriveInput, parse_macro_input};
17
18#[cfg(feature = "web-controllers")]
19#[proc_macro_attribute]
20pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
21 controllers::web::attributes::attribute("GET", attr, item)
22}
23
24#[cfg(feature = "web-controllers")]
25#[proc_macro_attribute]
26pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
27 controllers::web::attributes::attribute("POST", attr, item)
28}
29
30#[cfg(feature = "web-controllers")]
31#[proc_macro_attribute]
32pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
33 controllers::web::attributes::attribute("PUT", attr, item)
34}
35
36#[cfg(feature = "web-controllers")]
37#[proc_macro_attribute]
38pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
39 controllers::web::attributes::attribute("DELETE", attr, item)
40}
41
42#[cfg(feature = "web-controllers")]
43#[proc_macro_attribute]
44pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
45 controllers::web::attributes::attribute("PATCH", attr, item)
46}
47
48#[cfg(feature = "web-controllers")]
49#[proc_macro_attribute]
50pub fn head(attr: TokenStream, item: TokenStream) -> TokenStream {
51 controllers::web::attributes::attribute("HEAD", attr, item)
52}
53
54#[cfg(feature = "web-controllers")]
55#[proc_macro_attribute]
56pub fn options(attr: TokenStream, item: TokenStream) -> TokenStream {
57 controllers::web::attributes::attribute("OPTIONS", attr, item)
58}
59
60#[cfg(feature = "web-controllers")]
61#[proc_macro_attribute]
62pub fn trace(attr: TokenStream, item: TokenStream) -> TokenStream {
63 controllers::web::attributes::attribute("TRACE", attr, item)
64}
65
66#[cfg(feature = "web-controllers")]
67#[proc_macro_attribute]
68pub fn connect(attr: TokenStream, item: TokenStream) -> TokenStream {
69 controllers::web::attributes::attribute("CONNECT", attr, item)
70}
71
72/// Defines a Sword controller.
73/// Route handlers are declared directly inside the `impl` block using method attributes
74/// such as `#[get]`, `#[post]`, `#[put]`, `#[patch]`, `#[delete]`, `#[head]`, `#[options]`, `#[trace]`, and `#[connect]`.
75///
76/// ### Parameters
77/// - `kind`: Controller kind. Use `Controller::Web`, `Controller::SocketIo`, `Controller::Grpc`, or `Controller::EventHandler`.
78/// - `path`: Required when `kind = Controller::Web`.
79/// - `namespace`: Required when `kind = Controller::SocketIo`.
80/// - `service`: Required when `kind = Controller::Grpc`.
81/// - `source`: Required when `kind = Controller::EventHandler`. Use `EventSource::Memory`.
82///
83/// ### Usage
84/// ```rust,ignore
85/// #[controller(kind = Controller::Web, path = "/base_path")]
86/// struct MyController {}
87///
88/// impl MyController {
89/// #[get("/sub_path")]
90/// async fn my_handler(&self) -> WebResult {
91/// Ok(JsonResponse::Ok().message("Hello from MyController"))
92/// }
93/// }
94/// ```
95///
96/// ```rust,ignore
97/// #[controller(kind = Controller::SocketIo, namespace = "/chat")]
98/// struct ChatController;
99///
100/// impl ChatController {
101/// #[on("connection")]
102/// async fn on_connect(&self, _ctx: SocketContext) {}
103/// }
104/// ```
105#[proc_macro_attribute]
106pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
107 controllers::expand_controller(attr, item).unwrap_or_else(|err| err.to_compile_error().into())
108}
109
110/// Derive macro for creating interceptors.
111///
112/// Generates implementations for the `Interceptor` trait.
113///
114/// # Usage
115/// ```rust,ignore
116/// use sword::prelude::*;
117///
118/// #[derive(Interceptor)]
119/// struct MyInterceptor;
120///
121/// // then implement some Interceptor trait variants
122/// // depending on the controller kind (e.g. OnRequest, OnConnect.)
123/// ```
124#[proc_macro_derive(Interceptor)]
125pub fn derive_interceptor(input: TokenStream) -> TokenStream {
126 interceptor_derive::derive_interceptor(input)
127 .unwrap_or_else(|err| err.to_compile_error().into())
128}
129
130/// Marks a route or controller with one or more interceptors.
131/// This macro can be used to apply an `Interceptor` to different controller kinds,
132/// such as web controllers or Socket.IO controllers.
133#[proc_macro_attribute]
134pub fn interceptor(attr: TokenStream, item: TokenStream) -> TokenStream {
135 let _ = attr;
136 item
137}
138
139/// Defines a configuration struct for the application.
140/// This macro generates the necessary code to deserialize the struct from
141/// the configuration toml file.
142///
143/// The struct must derive `Deserialize` from `serde`.
144///
145/// ### Parameters
146/// - `key`: The key in the configuration file where the struct is located.
147///
148/// ### Usage
149///
150/// ```rust,ignore
151/// #[config(key = "my-section")]
152/// #[derive(Debug, Deserialize)]
153/// struct MyConfig {
154/// my_key: String,
155/// }
156/// ```
157#[proc_macro_attribute]
158pub fn config(args: TokenStream, item: TokenStream) -> TokenStream {
159 let input = parse_macro_input!(item as DeriveInput);
160
161 match core::config::expand_config_struct(args, &input) {
162 Ok(tokens) => tokens,
163 Err(err) => err.to_compile_error().into(),
164 }
165}
166
167/// Marks a struct as injectable.
168///
169/// This macro generates the necessary code to register the struct
170/// in the dependency injection container. It can be used with or without
171/// parameters.
172///
173/// ### Parameters
174///
175/// - `kind`: (Optional) Specifies the kind of injectable.
176/// It can be either `provider` or `component`.
177///
178/// `provider`: The struct that has to be instantiated manually and
179/// registered in the container. The struct will be treated as a singleton by default.
180///
181/// `component`: The struct will be instantiated automatically by the container
182/// based on its dependencies. It's also treated as a singleton by default.
183///
184/// By default, if no kind is provided, it will be treated as `component`.
185///
186/// - `no_derive_clone`: (Optional) If provided, the struct will not derive the `Clone` automatically.
187/// By default, the struct will derive `Clone` if all its fields implement `Clone`.
188///
189/// ### Usage of `#[injectable]` without parameters (same as #[injectable(component)])
190///
191/// ```rust,ignore
192/// #[injectable]
193/// pub struct TaskRepository {
194/// db: Database,
195/// }
196///
197/// impl TaskRepository {
198/// pub async fn create(&self, task: Value) {
199/// self.db.insert("tasks", task).await;
200/// }
201///
202/// pub async fn find_all(&self) -> Option<Vec<Value>> {
203/// self.db.get_all("tasks").await
204/// }
205/// }
206/// ```
207///
208/// ### Usage of `#[injectable(provider)]` with parameters
209///
210/// ```rust,ignore
211/// #[injectable(provider)]
212/// pub struct Database {
213/// db: Store,
214/// }
215///
216/// impl Database {
217/// pub async fn new(db_conf: DatabaseConfig) -> Self {
218/// let db = Arc::new(RwLock::new(HashMap::new()));
219///
220/// db.write().await.insert(db_conf.collection_name, Vec::new());
221///
222/// Self { db }
223/// }
224///
225/// pub async fn insert(&self, table: &'static str, record: Value) {
226/// let mut db = self.db.write().await;
227///
228/// if let Some(table_data) = db.get_mut(table) {
229/// table_data.push(record);
230/// }
231/// }
232///
233/// pub async fn get_all(&self, table: &'static str) -> Option<Vec<Value>> {
234/// let db = self.db.read().await;
235///
236/// db.get(table).cloned()
237/// }
238/// }
239/// ```
240#[proc_macro_attribute]
241pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
242 core::injectable::expand_injectable(attr, item)
243 .unwrap_or_else(|err| err.to_compile_error().into())
244}
245
246/// Derive macro for HTTP error enums.
247///
248/// Generates implementations for:
249/// - `From<Self> for JsonResponse` - Converts error to JSON response
250/// - `IntoResponse` - Allows returning error directly from handlers
251///
252/// **Note**: Use with `thiserror::Error` for `Display`, `Error`, and `#[from]`.
253///
254/// # Attributes
255///
256/// Enum-level defaults can be declared with `#[http_error(...)]` and overridden per
257/// variant with `#[http(...)]`.
258///
259/// **For direct responses:**
260/// - `code = <u16>`: HTTP status code (required)
261/// - `message = "<string>"`: Static client message (optional)
262/// - `message = <field>`: Uses a named field as the client message (optional)
263/// - `error = <field>`: Single error field to include (optional, named fields only)
264/// - `errors = <field>`: Multiple errors field to include (optional, named fields only)
265///
266/// **For delegation:**
267/// - `transparent`: Delegate to inner type's `From<T> for Json` (for wrapping other `HttpError` types)
268///
269/// **Tracing:**
270/// - `tracing = <level>` inside `#[http_error(...)]` or `#[http(...)]`
271/// - `#[tracing(level)]`: Backward-compatible shorthand at variant level
272/// - `level`: One of `trace`, `debug`, `info`, `warn`, `error`
273/// - Uses the internal `thiserror::Error` display for the `error` log field
274/// - Logs variant fields as structured tracing fields when available
275/// - Compatible with `RUST_LOG` for filtering
276/// - Not allowed with `transparent` variants
277///
278/// ### Tracing Output
279/// The generated logs include:
280/// - `error`: The internal `thiserror` display string
281/// - `error_type`: The variant name as string
282/// - `status_code`: The HTTP status code
283/// - For named variants: Each field as `field_name = ?field_value`
284/// - For unnamed variants (single field): `inner = ?field`
285/// - Unit variants: `error`, `error_type`, and `status_code`
286///
287/// # Example
288///
289/// ```rust,ignore
290/// use sword::prelude::*;
291/// use thiserror::Error;
292///
293/// #[derive(Debug, Error, HttpError)]
294/// #[http_error(code = 500, tracing = error, message = "Internal server error")]
295/// pub enum ApiError {
296/// #[error("Not found")]
297/// #[http(code = 404, message = "Not found", tracing = info)]
298/// NotFound,
299///
300/// #[error("Conflict on field {field}: {value}")]
301/// #[http(code = 409, message = client_message, error = detail)]
302/// Conflict {
303/// client_message: String,
304/// field: String,
305/// value: String,
306/// detail: serde_json::Value,
307/// },
308///
309/// #[error("IO Error: {0}")]
310/// Io(#[from] std::io::Error),
311///
312/// #[error("Auth Error: {0}")]
313/// #[http(transparent)] // Delegates to other "HttpError" derivation
314/// Auth(#[from] AuthError),
315/// }
316/// ```
317#[proc_macro_derive(HttpError, attributes(http, http_error, tracing))]
318#[cfg(feature = "web-controllers")]
319pub fn derive_http_error(input: TokenStream) -> TokenStream {
320 let input = parse_macro_input!(input as DeriveInput);
321
322 match errors::derive_http_error(input) {
323 Ok(tokens) => tokens.into(),
324 Err(err) => err.to_compile_error().into(),
325 }
326}
327
328/// Derive macro for gRPC error enums.
329///
330/// Generates:
331/// - `From<Self> for tonic::Status`
332///
333/// Enum-level defaults can be declared with `#[grpc_error(...)]` and overridden per
334/// variant with `#[grpc(...)]`.
335///
336/// Supported attributes:
337/// - `code = "invalid_argument"`
338/// - `message = "custom text"`
339/// - `message = field_name`
340/// - `transparent` (variant-only)
341/// - `tracing = <level>` inside `#[grpc_error(...)]` or `#[grpc(...)]`
342/// - `#[tracing(level)]`: backward-compatible shorthand at variant level
343///
344/// gRPC code values accepted by `#[grpc(code = "...")]`:
345///
346/// - `ok`
347/// - `cancelled`
348/// - `unknown`
349/// - `invalid_argument`
350/// - `deadline_exceeded`
351/// - `not_found`
352/// - `already_exists`
353/// - `permission_denied`
354/// - `resource_exhausted`
355/// - `failed_precondition`
356/// - `aborted`
357/// - `out_of_range`
358/// - `unimplemented`
359/// - `internal`
360/// - `unavailable`
361/// - `data_loss`
362/// - `unauthenticated`
363///
364/// # Example
365///
366/// ```rust,ignore
367/// use sword::prelude::*;
368/// use thiserror::Error;
369///
370/// #[derive(Debug, Error, GrpcError)]
371/// #[grpc_error(code = "internal", tracing = error)]
372/// enum UserError {
373/// #[grpc(code = "not_found", tracing = info)]
374/// #[error("User not found: {id}")]
375/// NotFound { id: String },
376///
377/// #[grpc(code = "invalid_argument", message = client_message)]
378/// #[error("Validation error: {internal}")]
379/// Validation {
380/// client_message: String,
381/// internal: String,
382/// },
383///
384/// #[grpc(transparent)]
385/// #[error("Database error: {0}")]
386/// Database(#[from] anyhow::Error),
387/// }
388/// ```
389#[proc_macro_derive(GrpcError, attributes(grpc, grpc_error, tracing))]
390pub fn derive_grpc_error(input: TokenStream) -> TokenStream {
391 let input = parse_macro_input!(input as DeriveInput);
392
393 match errors::derive_grpc_error(input) {
394 Ok(tokens) => tokens.into(),
395 Err(err) => err.to_compile_error().into(),
396 }
397}
398
399/// ### This is just a re-export of `tokio::main` to simplify the initial setup of
400/// ### Sword, you can use your own version of tokio adding it to your
401/// ### `Cargo.toml`, we are providing this initial base by default
402///
403/// ---
404///
405/// Marks async function to be executed by the selected runtime. This macro
406/// helps set up a `Runtime` without requiring the user to use
407/// [Runtime](../tokio/runtime/struct.Runtime.html) or
408/// [Builder](../tokio/runtime/struct.Builder.html) directly.
409///
410/// Note: This macro is designed to be simplistic and targets applications that
411/// do not require a complex setup. If the provided functionality is not
412/// sufficient, you may be interested in using
413/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
414/// powerful interface.
415///
416/// Note: This macro can be used on any function and not just the `main`
417/// function. Using it on a non-main function makes the function behave as if it
418/// was synchronous by starting a new runtime each time it is called. If the
419/// function is called often, it is preferable to create the runtime using the
420/// runtime builder so the runtime can be reused across calls.
421///
422/// # Non-worker async function
423///
424/// Note that the async function marked with this macro does not run as a
425/// worker. The expectation is that other tasks are spawned by the function here.
426/// Awaiting on other futures from the function provided here will not
427/// perform as fast as those spawned as workers.
428///
429/// # Multi-threaded runtime
430///
431/// To use the multi-threaded runtime, the macro can be configured using
432///
433/// ```rust,ignore
434/// #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
435/// # async fn main() {}
436/// ```
437///
438/// The `worker_threads` option configures the number of worker threads, and
439/// defaults to the number of cpus on the system. This is the default flavor.
440///
441/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
442/// flag.
443///
444/// # Current thread runtime
445///
446/// To use the single-threaded runtime known as the `current_thread` runtime,
447/// the macro can be configured using
448///
449/// ```rust,ignore
450/// #[tokio::main(flavor = "current_thread")]
451/// # async fn main() {}
452/// ```
453///
454/// ## Function arguments:
455///
456/// Arguments are allowed for any functions aside from `main` which is special
457///
458/// ## Usage
459///
460/// ### Using the multi-thread runtime
461///
462/// ```ignore
463/// #[tokio::main]
464/// async fn main() {
465/// println!("Hello world");
466/// }
467/// ```
468///
469/// Equivalent code not using `#[tokio::main]`
470///
471/// ```ignore
472/// fn main() {
473/// tokio::runtime::Builder::new_multi_thread()
474/// .enable_all()
475/// .build()
476/// .unwrap()
477/// .block_on(async {
478/// println!("Hello world");
479/// })
480/// }
481/// ```
482///
483/// ### Using current thread runtime
484///
485/// The basic scheduler is single-threaded.
486///
487/// ```ignore
488/// #[tokio::main(flavor = "current_thread")]
489/// async fn main() {
490/// println!("Hello world");
491/// }
492/// ```
493///
494/// Equivalent code not using `#[tokio::main]`
495///
496/// ```ignore
497/// fn main() {
498/// tokio::runtime::Builder::new_current_thread()
499/// .enable_all()
500/// .build()
501/// .unwrap()
502/// .block_on(async {
503/// println!("Hello world");
504/// })
505/// }
506/// ```
507///
508/// ### Set number of worker threads
509///
510/// ```ignore
511/// #[tokio::main(worker_threads = 2)]
512/// async fn main() {
513/// println!("Hello world");
514/// }
515/// ```
516///
517/// Equivalent code not using `#[tokio::main]`
518///
519/// ```ignore
520/// fn main() {
521/// tokio::runtime::Builder::new_multi_thread()
522/// .worker_threads(2)
523/// .enable_all()
524/// .build()
525/// .unwrap()
526/// .block_on(async {
527/// println!("Hello world");
528/// })
529/// }
530/// ```
531///
532/// ### Configure the runtime to start with time paused
533///
534/// ```ignore
535/// #[tokio::main(flavor = "current_thread", start_paused = true)]
536/// async fn main() {
537/// println!("Hello world");
538/// }
539/// ```
540///
541/// Equivalent code not using `#[tokio::main]`
542///
543/// ```ignore
544/// fn main() {
545/// tokio::runtime::Builder::new_current_thread()
546/// .enable_all()
547/// .start_paused(true)
548/// .build()
549/// .unwrap()
550/// .block_on(async {
551/// println!("Hello world");
552/// })
553/// }
554/// ```
555///
556/// Note that `start_paused` requires the `test-util` feature to be enabled.
557///
558/// ### Rename package
559///
560/// ```ignore
561/// use tokio as tokio1;
562///
563/// #[tokio1::main(crate = "tokio1")]
564/// async fn main() {
565/// println!("Hello world");
566/// }
567/// ```
568///
569/// Equivalent code not using `#[tokio::main]`
570///
571/// ```ignore
572/// use tokio as tokio1;
573///
574/// fn main() {
575/// tokio1::runtime::Builder::new_multi_thread()
576/// .enable_all()
577/// .build()
578/// .unwrap()
579/// .block_on(async {
580/// println!("Hello world");
581/// })
582/// }
583/// ```
584///
585/// ### Configure unhandled panic behavior
586///
587/// Available options are `shutdown_runtime` and `ignore`. For more details, see
588/// [`Builder::unhandled_panic`].
589///
590/// This option is only compatible with the `current_thread` runtime.
591///
592/// ```no_run, ignore
593/// # #![allow(unknown_lints, unexpected_cfgs)]
594/// #[cfg(tokio_unstable)]
595/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
596/// async fn main() {
597/// let _ = tokio::spawn(async {
598/// panic!("This panic will shutdown the runtime.");
599/// }).await;
600/// }
601/// # #[cfg(not(tokio_unstable))]
602/// # fn main() { }
603/// ```
604///
605/// Equivalent code not using `#[tokio::main]`
606///
607/// ```no_run, ignore
608/// # #![allow(unknown_lints, unexpected_cfgs)]
609/// #[cfg(tokio_unstable)]
610/// fn main() {
611/// tokio::runtime::Builder::new_current_thread()
612/// .enable_all()
613/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
614/// .build()
615/// .unwrap()
616/// .block_on(async {
617/// let _ = tokio::spawn(async {
618/// panic!("This panic will shutdown the runtime.");
619/// }).await;
620/// })
621/// }
622/// # #[cfg(not(tokio_unstable))]
623/// # fn main() { }
624/// ```
625///
626/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
627/// documentation on unstable features][unstable] for details on how to enable
628/// Tokio's unstable features.
629///
630/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
631/// [unstable]: ../tokio/index.html#unstable-features
632#[proc_macro_attribute]
633pub fn main(_args: TokenStream, item: TokenStream) -> TokenStream {
634 let input = parse_macro_input!(item as syn::ItemFn);
635
636 let fn_body = input.block.clone();
637 let fn_attrs = input.attrs.clone();
638 let fn_vis = input.vis.clone();
639 let _fn_sig = input.sig;
640
641 #[allow(unused)]
642 let mut output = quote! {};
643
644 if cfg!(feature = "hot-reload") {
645 output = quote! {
646 async fn __internal_main() {
647 #fn_body
648 }
649
650 #(#fn_attrs)*
651 #fn_vis fn main() {
652 ::sword::internal::tokio_runtime::Builder::new_multi_thread()
653 .enable_all()
654 .build()
655 .unwrap_or_else(|err| {
656 ::sword::internal::core::sword_error!(
657 title: "Failed to build Tokio runtime",
658 reason: err,
659 context: {
660 "source" => "#[sword::main]",
661 },
662 )
663 })
664 .block_on(::sword::internal::dioxus_devtools::serve_subsecond(__internal_main))
665 }
666 };
667 } else {
668 output = quote! {
669 #(#fn_attrs)*
670 #fn_vis fn main() {
671 ::sword::internal::tokio_runtime::Builder::new_multi_thread()
672 .enable_all()
673 .build()
674 .unwrap_or_else(|err| {
675 ::sword::internal::core::sword_error!(
676 title: "Failed to build Tokio runtime",
677 reason: err,
678 context: {
679 "source" => "#[sword::main]",
680 },
681 )
682 })
683 .block_on( async #fn_body )
684 }
685 };
686 }
687
688 output.into()
689}
690
691#[cfg(feature = "event-handlers")]
692/// Defines an event struct for use with the event queue.
693///
694/// Generates the `Event` trait implementation and derives `Clone`.
695///
696/// # Example
697///
698/// ```rust,ignore
699/// #[event(key = "mail.send.failed")]
700/// struct MailFailedEvent {
701/// to: String,
702/// error: String,
703/// }
704/// ```
705#[proc_macro_attribute]
706pub fn event(args: TokenStream, item: TokenStream) -> TokenStream {
707 core::event::expand_event_struct(args, item).unwrap_or_else(|err| err.to_compile_error().into())
708}
709
710#[cfg(feature = "event-handlers")]
711/// Registers an event handler method for an `EventHandler` controller.
712///
713/// The attribute takes the full event key as argument, which must match
714/// the `key` of the event being published via `EventPublisher`.
715///
716/// # Example
717///
718/// ```rust,ignore
719/// #[event(key = "mail.send.failed")]
720/// struct MailFailedEvent {
721/// to: String,
722/// error: String,
723/// }
724///
725/// #[controller(kind = Controller::EventHandler, source = EventSource::Memory)]
726/// struct MailHandler {
727/// mailer: Arc<Mailer>,
728/// }
729///
730/// impl MailHandler {
731/// #[handle("mail.send.failed")]
732/// async fn on_failed(&self, event: MailFailedEvent) -> Result<()> {
733/// self.mailer.resend(&event.to).await?;
734/// Ok(())
735/// }
736/// }
737/// ```
738#[proc_macro_attribute]
739pub fn handle(attr: TokenStream, item: TokenStream) -> TokenStream {
740 controllers::event_handler::expand_handle(attr, item)
741 .unwrap_or_else(|err| err.to_compile_error().into())
742}
743
744#[cfg(feature = "socketio-controllers")]
745/// Unified handler attribute for Socket.IO events.
746///
747/// ### Event Types
748/// - `#[on("connection")]` - Called when a client connects
749/// - `#[on("disconnection")]` - Called when a client disconnects
750/// - `#[on("fallback")]` - Called for unhandled events
751/// - `#[on("custom_event")]` - Called for custom event names
752///
753/// ### Parameters
754/// All handlers receive `&self` and `ctx: SocketContext` which provides access to:
755/// - Socket operations via `ctx`
756/// - Message data via `ctx.try_data::<T>()`
757/// - Event name via `ctx.event()`
758/// - Acknowledgments via `ctx.ack()`
759///
760/// ### Usage
761/// ```rust,ignore
762/// #[controller(kind = Controller::SocketIo, namespace = "/chat")]
763/// pub struct ChatController { ... }
764///
765/// impl ChatController {
766/// #[on("connection")]
767/// async fn on_connect(&self, ctx: SocketContext) {
768/// println!("Client connected: {}", ctx.id());
769/// }
770///
771/// #[on("message")]
772/// async fn handle_message(&self, ctx: SocketContext) {
773/// let msg: String = ctx.try_data().unwrap();
774/// println!("Received: {}", msg);
775/// }
776/// }
777/// ```
778#[proc_macro_attribute]
779pub fn on(attr: TokenStream, item: TokenStream) -> TokenStream {
780 controllers::expand_on_handler(attr, item).unwrap_or_else(|err| err.to_compile_error().into())
781}