Skip to main content

summer_macros/
lib.rs

1//! [![summer-rs](https://img.shields.io/github/stars/summer-rs/summer-rs)](https://summer-rs.github.io)
2#![doc(html_favicon_url = "https://summer-rs.github.io/favicon.ico")]
3#![doc(html_logo_url = "https://summer-rs.github.io/logo.svg")]
4
5mod auto;
6mod cache;
7mod component;
8mod config;
9mod problem_details;
10mod inject;
11mod job;
12mod middlewares;
13mod nest;
14mod route;
15#[cfg(feature = "socket_io")]
16mod socketioxide;
17mod stream;
18mod utils;
19
20use proc_macro::TokenStream;
21use syn::DeriveInput;
22
23/// Creates resource handler.
24///
25/// # Syntax
26/// ```plain
27/// #[route("path", method="HTTP_METHOD"[, attributes])]
28/// ```
29///
30/// # Attributes
31/// - `"path"`: Raw literal string with path for which to register handler.
32/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
33///   "GET", "POST" for example.
34///
35/// # Examples
36/// ```
37/// # use summer_web::axum::response::IntoResponse;
38/// # use summer_macros::route;
39/// #[route("/test", method = "GET", method = "HEAD")]
40/// async fn example() -> impl IntoResponse {
41///     "hello world"
42/// }
43/// ```
44#[proc_macro_attribute]
45pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
46    route::with_method(None, args, input, false)
47}
48
49/// Creates openapi resource handler.
50///
51/// # Syntax
52/// ```plain
53/// #[api_route("path", method="HTTP_METHOD"[, attributes])]
54/// ```
55///
56/// # Attributes
57/// - `"path"`: Raw literal string with path for which to register handler.
58/// - `method = "HTTP_METHOD"`: Registers HTTP method. Upper-case string,
59///   "GET", "POST" for example.
60///
61/// # Examples
62/// ```
63/// # use summer_web::axum::response::IntoResponse;
64/// # use summer_macros::api_route;
65/// #[api_route("/test", method = "GET", method = "HEAD")]
66/// async fn example() -> impl IntoResponse {
67///     "hello world"
68/// }
69/// ```
70#[proc_macro_attribute]
71pub fn api_route(args: TokenStream, input: TokenStream) -> TokenStream {
72    route::with_method(None, args, input, true)
73}
74
75/// Creates resource handler.
76///
77/// # Syntax
78/// ```plain
79/// #[routes]
80/// #[<method>("path", ...)]
81/// #[<method>("path", ...)]
82/// ...
83/// ```
84///
85/// # Attributes
86/// The `routes` macro itself has no parameters, but allows specifying the attribute macros for
87/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post).
88///
89/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler).
90///
91/// # Examples
92/// ```
93/// # use summer_web::axum::response::IntoResponse;
94/// # use summer_macros::routes;
95/// #[routes]
96/// #[get("/test")]
97/// #[get("/test2")]
98/// #[delete("/test")]
99/// async fn example() -> impl IntoResponse {
100///     "hello world"
101/// }
102/// ```
103#[proc_macro_attribute]
104pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream {
105    route::with_methods(input, false)
106}
107
108/// Creates openapi resource handler.
109///
110/// # Syntax
111/// ```plain
112/// #[api_routes]
113/// #[<method>("path", ...)]
114/// #[<method>("path", ...)]
115/// ...
116/// ```
117///
118/// # Attributes
119/// The `api_routes` macro itself has no parameters, but allows specifying the attribute macros for
120/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post).
121///
122/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler).
123///
124/// # Examples
125/// ```
126/// # use summer_web::axum::response::IntoResponse;
127/// # use summer_macros::api_routes;
128/// #[api_routes]
129/// #[get("/test")]
130/// #[get("/test2")]
131/// #[delete("/test")]
132/// async fn example() -> impl IntoResponse {
133///     "hello world"
134/// }
135/// ```
136#[proc_macro_attribute]
137pub fn api_routes(_: TokenStream, input: TokenStream) -> TokenStream {
138    route::with_methods(input, true)
139}
140
141macro_rules! method_macro {
142    ($variant:ident, $method:ident, $openapi:expr) => {
143        ///
144        /// # Syntax
145        /// ```plain
146        #[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
147        /// ```
148        ///
149        /// # Attributes
150        /// - `"path"`: Raw literal string with path for which to register handler.
151        ///
152        /// # Examples
153        /// ```
154        /// # use summer_web::axum::response::IntoResponse;
155        #[doc = concat!("# use summer_macros::", stringify!($method), ";")]
156        #[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
157        /// async fn example() -> impl IntoResponse {
158        ///     "hello world"
159        /// }
160        /// ```
161        #[proc_macro_attribute]
162        pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
163            route::with_method(Some(route::Method::$variant), args, input, $openapi)
164        }
165    };
166}
167
168method_macro!(Get, get, false);
169method_macro!(Post, post, false);
170method_macro!(Put, put, false);
171method_macro!(Delete, delete, false);
172method_macro!(Head, head, false);
173method_macro!(Options, options, false);
174method_macro!(Trace, trace, false);
175method_macro!(Patch, patch, false);
176
177method_macro!(Get, get_api, true);
178method_macro!(Post, post_api, true);
179method_macro!(Put, put_api, true);
180method_macro!(Delete, delete_api, true);
181method_macro!(Head, head_api, true);
182method_macro!(Options, options_api, true);
183method_macro!(Trace, trace_api, true);
184method_macro!(Patch, patch_api, true);
185
186/// Prepends a path prefix to all handlers using routing macros inside the attached module.
187///
188/// # Syntax
189///
190/// ```
191/// # use summer_macros::nest;
192/// #[nest("/prefix")]
193/// mod api {
194///     // ...
195/// }
196/// ```
197///
198/// # Arguments
199///
200/// - `"/prefix"` - Raw literal string to be prefixed onto contained handlers' paths.
201///
202/// # Example
203///
204/// ```
205/// # use summer_macros::{nest, get};
206/// # use summer_web::axum::response::IntoResponse;
207/// #[nest("/api")]
208/// mod api {
209///     # use super::*;
210///     #[get("/hello")]
211///     pub async fn hello() -> impl IntoResponse {
212///         // this has path /api/hello
213///         "Hello, world!"
214///     }
215/// }
216/// # fn main() {}
217/// ```
218#[proc_macro_attribute]
219pub fn nest(args: TokenStream, input: TokenStream) -> TokenStream {
220    nest::with_nest(args, input)
221}
222
223/// Applies middleware layers to all route handlers within a module.
224///
225/// # Syntax
226/// ```plain
227/// #[middlewares(middleware1, middleware2, ...)]
228/// mod module_name {
229///     // route handlers
230/// }
231/// ```
232///
233/// # Arguments
234/// - `middleware1`, `middleware2`, etc. - Middleware expressions that will be applied to all routes in the module
235///
236/// This macro generates a router function that applies the specified middleware
237/// to all route handlers defined within the module.
238#[proc_macro_attribute]
239pub fn middlewares(args: TokenStream, input: TokenStream) -> TokenStream {
240    middlewares::middlewares(args, input)
241}
242
243fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
244    let compile_err = TokenStream::from(err.to_compile_error());
245    item.extend(compile_err);
246    item
247}
248
249/// Job
250///
251macro_rules! job_macro {
252    ($variant:ident, $job_type:ident, $example:literal) => {
253        ///
254        /// # Syntax
255        /// ```plain
256        #[doc = concat!("#[", stringify!($job_type), "(", $example, ")]")]
257        /// ```
258        ///
259        /// # Attributes
260        /// - `"path"`: Raw literal string with path for which to register handler.
261        ///
262        /// # Examples
263        /// ```
264        /// # use summer_web::axum::response::IntoResponse;
265        #[doc = concat!("# use summer_macros::", stringify!($job_type), ";")]
266        #[doc = concat!("#[", stringify!($job_type), "(", stringify!($example), ")]")]
267        /// async fn example() {
268        ///     println!("hello world");
269        /// }
270        /// ```
271        #[proc_macro_attribute]
272        pub fn $job_type(args: TokenStream, input: TokenStream) -> TokenStream {
273            job::with_job(job::JobType::$variant, args, input)
274        }
275    };
276}
277
278job_macro!(OneShot, one_shot, 60);
279job_macro!(FixDelay, fix_delay, 60);
280job_macro!(FixRate, fix_rate, 60);
281job_macro!(Cron, cron, "1/10 * * * * *");
282
283/// Auto config
284/// ```diff
285///  use summer_macros::auto_config;
286///  use summer_web::{WebPlugin, WebConfigurator};
287///  use summer_job::{JobPlugin, JobConfigurator};
288///  use summer_pubsub::{pubsub_listener, PubSubPlugin, PubSubConfigurator};
289///  use summer_boot::app::App;
290/// +#[auto_config(WebConfigurator, JobConfigurator, PubSubConfigurator)]
291///  #[tokio::main]
292///  async fn main() {
293///      App::new()
294///         .add_plugin(WebPlugin)
295///         .add_plugin(JobPlugin)
296/// -       .add_router(router())
297/// -       .add_jobs(jobs())
298///         .run()
299///         .await
300///  }
301/// ```
302///
303#[proc_macro_attribute]
304pub fn auto_config(args: TokenStream, input: TokenStream) -> TokenStream {
305    auto::config(args, input)
306}
307
308/// stream macro
309#[proc_macro_attribute]
310pub fn stream_listener(args: TokenStream, input: TokenStream) -> TokenStream {
311    stream::listener(args, input)
312}
313
314/// Configurable
315#[proc_macro_derive(Configurable, attributes(config_prefix))]
316pub fn derive_config(input: TokenStream) -> TokenStream {
317    let input = syn::parse_macro_input!(input as DeriveInput);
318
319    config::expand_derive(input)
320        .unwrap_or_else(syn::Error::into_compile_error)
321        .into()
322}
323
324/// Injectable Servcie
325#[proc_macro_derive(Service, attributes(service, inject))]
326pub fn derive_service(input: TokenStream) -> TokenStream {
327    let input = syn::parse_macro_input!(input as DeriveInput);
328
329    inject::expand_derive(input)
330        .unwrap_or_else(syn::Error::into_compile_error)
331        .into()
332}
333
334/// Component macro for declarative component registration
335///
336/// This macro allows you to register components to the summer-rs application
337/// container in a declarative way, without manually implementing the Plugin trait.
338///
339/// # Syntax
340/// ```plain
341/// #[component]
342/// fn create_component(
343///     Config(config): Config<MyConfig>,
344///     Component(dep): Component<Dependency>,
345/// ) -> MyComponent {
346///     MyComponent::new(config, dep)
347/// }
348/// Declarative component registration macro for summer-rs applications.
349///
350/// The `#[component]` macro automatically generates a Plugin implementation that registers
351/// a component in the application. It handles dependency injection, configuration loading,
352/// and proper initialization order.
353///
354/// # How It Works
355///
356/// The macro transforms a component creation function into a Plugin that:
357/// 1. Extracts dependencies from function parameters
358/// 2. Generates a unique Plugin name based on the return type
359/// 3. Declares dependencies to ensure correct initialization order
360/// 4. Registers the component in the application registry
361/// 5. Automatically registers the Plugin via the `inventory` crate
362///
363/// # Syntax
364/// ```plain
365/// #[component(name = "CustomName")]  // Optional custom name
366/// fn create_component(
367///     Config(config): Config<ConfigType>,      // Configuration injection
368///     Component(dep): Component<DependencyType>, // Component injection
369/// ) -> ComponentType {
370///     // Component creation logic
371/// }
372/// ```
373///
374/// # Attributes
375/// - `name = "PluginName"` - **Optional**: Custom Plugin name. If not specified, the name
376///   is automatically generated as `__Create{TypeName}Plugin`.
377///
378/// # Parameters
379/// - `Config<T>` - Inject configuration of type `T` (must implement `Configurable`)
380/// - `Component<T>` - Inject another component of type `T`
381///   - Can use `#[inject("PluginName")]` attribute to specify explicit dependency
382///   - Without `#[inject]`, dependency is inferred as `__Create{T}Plugin`
383///
384/// # Return Type
385/// - Must implement `Clone + Send + Sync + 'static`
386/// - Can return `Result<T, E>` for fallible initialization (will panic on error)
387/// - Each component type can only be registered once
388///
389/// # Dependency Resolution
390///
391/// The macro automatically analyzes dependencies and generates a `dependencies()` method
392/// that returns the list of required Plugin names. The application will initialize plugins
393/// in the correct order based on these dependencies.
394///
395/// **Circular dependencies are not allowed** and will cause a panic at runtime.
396///
397/// # Examples
398///
399/// ## Basic Usage
400/// ```rust,ignore
401/// use summer::config::Configurable;
402/// use summer::extractor::Config;
403/// use summer_macros::component;
404/// use serde::Deserialize;
405///
406/// #[derive(Clone, Configurable, Deserialize)]
407/// #[config_prefix = "database"]
408/// struct DbConfig {
409///     host: String,
410///     port: u16,
411/// }
412///
413/// #[derive(Clone)]
414/// struct DbConnection {
415///     url: String,
416/// }
417///
418/// #[component]
419/// fn create_db_connection(
420///     Config(config): Config<DbConfig>,
421/// ) -> DbConnection {
422///     DbConnection {
423///         url: format!("{}:{}", config.host, config.port),
424///     }
425/// }
426/// ```
427///
428/// ## With Dependencies
429/// ```rust,ignore
430/// #[derive(Clone)]
431/// struct UserRepository {
432///     db: DbConnection,
433/// }
434///
435/// #[component]
436/// fn create_user_repository(
437///     Component(db): Component<DbConnection>,
438/// ) -> UserRepository {
439///     UserRepository { db }
440/// }
441/// ```
442///
443/// ## Multi-Level Dependencies
444/// ```rust,ignore
445/// #[derive(Clone)]
446/// struct UserService {
447///     repo: UserRepository,
448/// }
449///
450/// #[component]
451/// fn create_user_service(
452///     Component(repo): Component<UserRepository>,
453/// ) -> UserService {
454///     UserService { repo }
455/// }
456/// ```
457///
458/// ## Async Initialization
459/// ```rust,ignore
460/// #[component]
461/// async fn create_db_connection(
462///     Config(config): Config<DbConfig>,
463/// ) -> Result<DbConnection, anyhow::Error> {
464///     let pool = sqlx::PgPool::connect(&config.url).await?;
465///     Ok(DbConnection { pool })
466/// }
467/// ```
468///
469/// ## Custom Plugin Name
470///
471/// Use custom names when you need multiple components of the same type (NewType pattern):
472/// ```rust,ignore
473/// #[derive(Clone)]
474/// struct PrimaryDb(DbConnection);
475///
476/// #[derive(Clone)]
477/// struct SecondaryDb(DbConnection);
478///
479/// #[component(name = "PrimaryDatabase")]
480/// fn create_primary_db(
481///     Config(config): Config<PrimaryDbConfig>,
482/// ) -> PrimaryDb {
483///     PrimaryDb(DbConnection::new(&config))
484/// }
485///
486/// #[component(name = "SecondaryDatabase")]
487/// fn create_secondary_db(
488///     Config(config): Config<SecondaryDbConfig>,
489/// ) -> SecondaryDb {
490///     SecondaryDb(DbConnection::new(&config))
491/// }
492/// ```
493///
494/// ## Explicit Dependency
495///
496/// Use `#[inject("PluginName")]` when the dependency name cannot be inferred:
497/// ```rust,ignore
498/// #[component]
499/// fn create_repository(
500///     #[inject("PrimaryDatabase")] Component(db): Component<PrimaryDb>,
501/// ) -> UserRepository {
502///     UserRepository::new(db.0)
503/// }
504/// ```
505///
506/// # Usage in Application
507///
508/// Components defined with `#[component]` are automatically registered when the app is built:
509///
510/// ```rust,ignore
511/// use summer::App;
512///
513/// #[tokio::main]
514/// async fn main() {
515///     let app = App::new()
516///         .build()  // Auto plugins are registered automatically
517///         .await
518///         .expect("Failed to build app");
519///     
520///     // Use components
521///     let db = app.get_component::<DbConnection>().unwrap();
522/// }
523/// ```
524///
525/// # Best Practices
526///
527/// 1. **Keep component functions simple** - They should only create and configure the component
528/// 2. **Use NewType pattern for multiple instances** - Wrap the same type in different structs
529/// 3. **Prefer configuration over hardcoding** - Use `Config<T>` for all configurable values
530/// 4. **Use `Arc<T>` for large components** - Reduces clone overhead
531/// 5. **Avoid circular dependencies** - Refactor your design if you encounter them
532/// 6. **Use explicit names for clarity** - When the auto-generated name is not clear enough
533///
534/// # Limitations
535///
536/// - Each component type can only be registered once (use NewType pattern for multiple instances)
537/// - Circular dependencies are not supported
538/// - Component types must implement `Clone + Send + Sync + 'static`
539/// - Configuration types must implement `Configurable + Deserialize`
540///
541/// # See Also
542///
543/// - [`Config`](summer::extractor::Config) - Configuration injection wrapper
544/// - [`Component`](summer::extractor::Component) - Component injection wrapper
545/// - [`Configurable`](summer::config::Configurable) - Trait for configuration types
546#[proc_macro_attribute]
547pub fn component(attr: TokenStream, input: TokenStream) -> TokenStream {
548    component::component_macro(attr, input)
549}
550
551/// ProblemDetails derive macro
552///
553/// Derives the `From<T> for ProblemDetails` trait for error enums.
554/// This macro automatically generates implementations for converting error variants
555/// to RFC 7807 Problem Details responses.
556///
557/// Each variant must have a `#[status_code(code)]` attribute.
558/// 
559/// ## Supported Attributes
560/// 
561/// - `#[status_code(code)]` - **Required**: HTTP status code (e.g., 400, 404, 500)
562/// - `#[problem_type("uri")]` - **Optional**: Custom problem type URI
563/// - `#[title("title")]` - **Optional**: Custom problem title
564/// - `#[detail("detail")]` - **Optional**: Custom problem detail message
565/// - `#[instance("uri")]` - **Optional**: Problem instance URI
566///
567/// ## Title Compatibility
568/// 
569/// The `title` field can be automatically derived from the `#[error("...")]` attribute
570/// if no explicit `#[title("...")]` is provided. This provides compatibility with
571/// `thiserror::Error` and reduces duplication.
572///
573/// ## Basic Example
574/// ```rust,ignore
575/// use summer_web::ProblemDetails;
576///
577/// #[derive(ProblemDetails)]
578/// pub enum ApiError {
579///     #[status_code(400)]
580///     ValidationError,
581///     #[status_code(404)]
582///     NotFound,
583///     #[status_code(500)]
584///     InternalError,
585/// }
586/// ```
587///
588/// ## Advanced Example with Custom Attributes
589/// ```rust,ignore
590/// #[derive(ProblemDetails)]
591/// pub enum ApiError {
592///     // Explicit title
593///     #[status_code(400)]
594///     #[title("Input Validation Failed")]
595///     #[detail("The provided input data is invalid")]
596///     #[error("Validation error")]
597///     ValidationError,
598///     
599///     // Title derived from error attribute
600///     #[status_code(422)]
601///     #[detail("Request data failed validation")]
602///     #[error("Validation Failed")]  // This becomes the title
603///     ValidationFailed,
604///     
605///     // Full customization
606///     #[status_code(404)]
607///     #[problem_type("https://api.example.com/problems/not-found")]
608///     #[title("Resource Not Found")]
609///     #[detail("The requested resource could not be found")]
610///     #[instance("/users/123")]
611///     #[error("Not found")]
612///     NotFound,
613/// }
614/// ```
615///
616/// This will automatically implement:
617/// - `From<T> for ProblemDetails` trait for converting to Problem Details responses
618/// - `IntoResponse` trait for direct use in Axum handlers
619/// - OpenAPI integration for documentation generation
620#[proc_macro_derive(ProblemDetails, attributes(status_code, problem_type, title, detail, instance))]
621pub fn derive_problem_details(input: TokenStream) -> TokenStream {
622    let input = syn::parse_macro_input!(input as DeriveInput);
623
624    problem_details::expand_derive(input)
625        .unwrap_or_else(syn::Error::into_compile_error)
626        .into()
627}
628
629/// `#[cache]` - Transparent Redis-based caching for async functions.
630///
631/// This macro wraps an async function to automatically cache its result
632/// in Redis. It checks for a cached value before executing the function.
633/// If a cached result is found, it is deserialized and returned directly.
634/// Otherwise, the function runs normally and its result is stored in Redis.
635///
636/// # Syntax
637/// ```plain
638/// #[cache("key_pattern", expire = <seconds>, condition = <bool_expr>, unless = <bool_expr>)]
639/// ```
640///
641/// # Attributes
642/// - `"key_pattern"` (**required**):
643///   A format string used to generate the cache key. Function arguments can be interpolated using standard `format!` syntax.
644/// - `expire = <integer>` (**optional**):
645///   The number of seconds before the cached value expires. If omitted, the key will be stored without expiration.
646/// - `condition = <expression>` (**optional**):
647///   A boolean expression evaluated **before** executing the function.
648///   If this evaluates to `false`, caching is completely bypassed — no lookup and no insertion.
649///   The expression can access function parameters directly.
650/// - `unless = <expression>` (**optional**):
651///   A boolean expression evaluated **after** executing the function.
652///   If this evaluates to `true`, the result will **not** be written to the cache.
653///   The expression can access both parameters and a `result` variable (the return value).
654///   NOTE: If your function returns Result<T, E>, the `result` variable in unless refers to the inner Ok value (T), not the entire Result.
655///   This allows you to write expressions like result.is_none() for Result<Option<_>, _> functions.
656///
657/// # Function Requirements
658/// - Must be an `async fn`
659/// - Can return either a `Result<T, E>` or a plain value `T`
660/// - The return type must implement `serde::Serialize` and `serde::Deserialize`
661/// - Generics, attributes, and visibility will be preserved
662///
663/// # Example
664/// ```rust
665/// use summer_macros::cache;
666///
667/// #[derive(serde::Serialize, serde::Deserialize)]
668/// struct User {
669///     id: u64,
670///     name: String,
671/// }
672///
673/// struct MyError;
674///
675/// #[cache("user:{user_id}", expire = 600, condition = user_id % 2 == 0, unless = result.is_none())]
676/// async fn get_user(user_id: u64) -> Result<Option<User>, MyError> {
677///     // Fetch user from database
678///     unimplemented!("do something")
679/// }
680/// ```
681#[proc_macro_attribute]
682pub fn cache(args: TokenStream, input: TokenStream) -> TokenStream {
683    cache::cache(args, input)
684}
685
686#[cfg(feature = "socket_io")]
687/// Marks a function as a SocketIO connection handler
688///
689/// # Examples
690/// ```
691/// # use summer_web::socketioxide::extract::{SocketRef, Data};
692/// # use summer_web::rmpv::Value;
693/// # use summer_macros::on_connection;
694/// #[on_connection]
695/// async fn on_connection(socket: SocketRef, Data(data): Data<Value>) {
696///     // Handle connection
697/// }
698/// ```
699#[proc_macro_attribute]
700pub fn on_connection(args: TokenStream, input: TokenStream) -> TokenStream {
701    socketioxide::on_connection(args, input)
702}
703
704#[cfg(feature = "socket_io")]
705/// Marks a function as a SocketIO disconnection handler
706///
707/// # Examples
708/// ```
709/// # use summer_web::socketioxide::extract::SocketRef;
710/// # use summer_macros::on_disconnect;
711/// #[on_disconnect]
712/// async fn on_disconnect(socket: SocketRef) {
713///     // Handle disconnection
714/// }
715/// ```
716#[proc_macro_attribute]
717pub fn on_disconnect(args: TokenStream, input: TokenStream) -> TokenStream {
718    socketioxide::on_disconnect(args, input)
719}
720
721#[cfg(feature = "socket_io")]
722/// Marks a function as a SocketIO message subscription handler
723///
724/// # Examples
725/// ```
726/// # use summer_web::socketioxide::extract::{SocketRef, Data};
727/// # use summer_macros::subscribe_message;
728/// # use summer_web::rmpv::Value;
729/// #[subscribe_message("message")]
730/// async fn message(socket: SocketRef, Data(data): Data<Value>) {
731///     // Handle message
732/// }
733/// ```
734#[proc_macro_attribute]
735pub fn subscribe_message(args: TokenStream, input: TokenStream) -> TokenStream {
736    socketioxide::subscribe_message(args, input)
737}
738
739#[cfg(feature = "socket_io")]
740/// Marks a function as a SocketIO fallback handler
741///
742/// # Examples
743/// ```
744/// # use summer_web::socketioxide::extract::{SocketRef, Data};
745/// # use summer_web::rmpv::Value;
746/// # use summer_macros::on_fallback;
747/// #[on_fallback]
748/// async fn on_fallback(socket: SocketRef, Data(data): Data<Value>) {
749///     // Handle fallback
750/// }
751/// ```
752#[proc_macro_attribute]
753pub fn on_fallback(args: TokenStream, input: TokenStream) -> TokenStream {
754    socketioxide::on_fallback(args, input)
755}
756