summer_macros/lib.rs
1//! [](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