spring_macros/lib.rs
1//! [](https://spring-rs.github.io)
2#![doc(html_favicon_url = "https://spring-rs.github.io/favicon.ico")]
3#![doc(html_logo_url = "https://spring-rs.github.io/logo.svg")]
4
5mod auto;
6mod cache;
7mod config;
8mod inject;
9mod job;
10mod middlewares;
11mod nest;
12mod route;
13#[cfg(feature = "socket_io")]
14mod socketioxide;
15mod stream;
16mod utils;
17
18use proc_macro::TokenStream;
19use syn::DeriveInput;
20
21/// Creates resource handler.
22///
23/// # Syntax
24/// ```plain
25/// #[route("path", method="HTTP_METHOD"[, attributes])]
26/// ```
27///
28/// # Attributes
29/// - `"path"`: Raw literal string with path for which to register handler.
30/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
31/// "GET", "POST" for example.
32///
33/// # Examples
34/// ```
35/// # use spring_web::axum::response::IntoResponse;
36/// # use spring_macros::route;
37/// #[route("/test", method = "GET", method = "HEAD")]
38/// async fn example() -> impl IntoResponse {
39/// "hello world"
40/// }
41/// ```
42#[proc_macro_attribute]
43pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
44 route::with_method(None, args, input, false)
45}
46
47/// Creates openapi resource handler.
48///
49/// # Syntax
50/// ```plain
51/// #[api_route("path", method="HTTP_METHOD"[, attributes])]
52/// ```
53///
54/// # Attributes
55/// - `"path"`: Raw literal string with path for which to register handler.
56/// - `method = "HTTP_METHOD"`: Registers HTTP method. Upper-case string,
57/// "GET", "POST" for example.
58///
59/// # Examples
60/// ```
61/// # use spring_web::axum::response::IntoResponse;
62/// # use spring_macros::api_route;
63/// #[api_route("/test", method = "GET", method = "HEAD")]
64/// async fn example() -> impl IntoResponse {
65/// "hello world"
66/// }
67/// ```
68#[proc_macro_attribute]
69pub fn api_route(args: TokenStream, input: TokenStream) -> TokenStream {
70 route::with_method(None, args, input, true)
71}
72
73/// Creates resource handler.
74///
75/// # Syntax
76/// ```plain
77/// #[routes]
78/// #[<method>("path", ...)]
79/// #[<method>("path", ...)]
80/// ...
81/// ```
82///
83/// # Attributes
84/// The `routes` macro itself has no parameters, but allows specifying the attribute macros for
85/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post).
86///
87/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler).
88///
89/// # Examples
90/// ```
91/// # use spring_web::axum::response::IntoResponse;
92/// # use spring_macros::routes;
93/// #[routes]
94/// #[get("/test")]
95/// #[get("/test2")]
96/// #[delete("/test")]
97/// async fn example() -> impl IntoResponse {
98/// "hello world"
99/// }
100/// ```
101#[proc_macro_attribute]
102pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream {
103 route::with_methods(input, false)
104}
105
106/// Creates openapi resource handler.
107///
108/// # Syntax
109/// ```plain
110/// #[api_routes]
111/// #[<method>("path", ...)]
112/// #[<method>("path", ...)]
113/// ...
114/// ```
115///
116/// # Attributes
117/// The `api_routes` macro itself has no parameters, but allows specifying the attribute macros for
118/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post).
119///
120/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler).
121///
122/// # Examples
123/// ```
124/// # use spring_web::axum::response::IntoResponse;
125/// # use spring_macros::api_routes;
126/// #[api_routes]
127/// #[get("/test")]
128/// #[get("/test2")]
129/// #[delete("/test")]
130/// async fn example() -> impl IntoResponse {
131/// "hello world"
132/// }
133/// ```
134#[proc_macro_attribute]
135pub fn api_routes(_: TokenStream, input: TokenStream) -> TokenStream {
136 route::with_methods(input, true)
137}
138
139macro_rules! method_macro {
140 ($variant:ident, $method:ident, $openapi:expr) => {
141 ///
142 /// # Syntax
143 /// ```plain
144 #[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
145 /// ```
146 ///
147 /// # Attributes
148 /// - `"path"`: Raw literal string with path for which to register handler.
149 ///
150 /// # Examples
151 /// ```
152 /// # use spring_web::axum::response::IntoResponse;
153 #[doc = concat!("# use spring_macros::", stringify!($method), ";")]
154 #[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
155 /// async fn example() -> impl IntoResponse {
156 /// "hello world"
157 /// }
158 /// ```
159 #[proc_macro_attribute]
160 pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
161 route::with_method(Some(route::Method::$variant), args, input, $openapi)
162 }
163 };
164}
165
166method_macro!(Get, get, false);
167method_macro!(Post, post, false);
168method_macro!(Put, put, false);
169method_macro!(Delete, delete, false);
170method_macro!(Head, head, false);
171method_macro!(Options, options, false);
172method_macro!(Trace, trace, false);
173method_macro!(Patch, patch, false);
174
175method_macro!(Get, get_api, true);
176method_macro!(Post, post_api, true);
177method_macro!(Put, put_api, true);
178method_macro!(Delete, delete_api, true);
179method_macro!(Head, head_api, true);
180method_macro!(Options, options_api, true);
181method_macro!(Trace, trace_api, true);
182method_macro!(Patch, patch_api, true);
183
184/// Prepends a path prefix to all handlers using routing macros inside the attached module.
185///
186/// # Syntax
187///
188/// ```
189/// # use spring_macros::nest;
190/// #[nest("/prefix")]
191/// mod api {
192/// // ...
193/// }
194/// ```
195///
196/// # Arguments
197///
198/// - `"/prefix"` - Raw literal string to be prefixed onto contained handlers' paths.
199///
200/// # Example
201///
202/// ```
203/// # use spring_macros::{nest, get};
204/// # use spring_web::axum::response::IntoResponse;
205/// #[nest("/api")]
206/// mod api {
207/// # use super::*;
208/// #[get("/hello")]
209/// pub async fn hello() -> impl IntoResponse {
210/// // this has path /api/hello
211/// "Hello, world!"
212/// }
213/// }
214/// # fn main() {}
215/// ```
216#[proc_macro_attribute]
217pub fn nest(args: TokenStream, input: TokenStream) -> TokenStream {
218 nest::with_nest(args, input)
219}
220
221/// Applies middleware layers to all route handlers within a module.
222///
223/// # Syntax
224/// ```plain
225/// #[middlewares(middleware1, middleware2, ...)]
226/// mod module_name {
227/// // route handlers
228/// }
229/// ```
230///
231/// # Arguments
232/// - `middleware1`, `middleware2`, etc. - Middleware expressions that will be applied to all routes in the module
233///
234/// This macro generates a router function that applies the specified middleware
235/// to all route handlers defined within the module.
236#[proc_macro_attribute]
237pub fn middlewares(args: TokenStream, input: TokenStream) -> TokenStream {
238 middlewares::middlewares(args, input)
239}
240
241fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
242 let compile_err = TokenStream::from(err.to_compile_error());
243 item.extend(compile_err);
244 item
245}
246
247/// Job
248///
249macro_rules! job_macro {
250 ($variant:ident, $job_type:ident, $example:literal) => {
251 ///
252 /// # Syntax
253 /// ```plain
254 #[doc = concat!("#[", stringify!($job_type), "(", $example, ")]")]
255 /// ```
256 ///
257 /// # Attributes
258 /// - `"path"`: Raw literal string with path for which to register handler.
259 ///
260 /// # Examples
261 /// ```
262 /// # use spring_web::axum::response::IntoResponse;
263 #[doc = concat!("# use spring_macros::", stringify!($job_type), ";")]
264 #[doc = concat!("#[", stringify!($job_type), "(", stringify!($example), ")]")]
265 /// async fn example() {
266 /// println!("hello world");
267 /// }
268 /// ```
269 #[proc_macro_attribute]
270 pub fn $job_type(args: TokenStream, input: TokenStream) -> TokenStream {
271 job::with_job(job::JobType::$variant, args, input)
272 }
273 };
274}
275
276job_macro!(OneShot, one_shot, 60);
277job_macro!(FixDelay, fix_delay, 60);
278job_macro!(FixRate, fix_rate, 60);
279job_macro!(Cron, cron, "1/10 * * * * *");
280
281/// Auto config
282/// ```diff
283/// use spring_macros::auto_config;
284/// use spring_web::{WebPlugin, WebConfigurator};
285/// use spring_job::{JobPlugin, JobConfigurator};
286/// use spring_boot::app::App;
287/// +#[auto_config(WebConfigurator, JobConfigurator)]
288/// #[tokio::main]
289/// async fn main() {
290/// App::new()
291/// .add_plugin(WebPlugin)
292/// .add_plugin(JobPlugin)
293/// - .add_router(router())
294/// - .add_jobs(jobs())
295/// .run()
296/// .await
297/// }
298/// ```
299///
300#[proc_macro_attribute]
301pub fn auto_config(args: TokenStream, input: TokenStream) -> TokenStream {
302 auto::config(args, input)
303}
304
305/// stream macro
306#[proc_macro_attribute]
307pub fn stream_listener(args: TokenStream, input: TokenStream) -> TokenStream {
308 stream::listener(args, input)
309}
310
311/// Configurable
312#[proc_macro_derive(Configurable, attributes(config_prefix))]
313pub fn derive_config(input: TokenStream) -> TokenStream {
314 let input = syn::parse_macro_input!(input as DeriveInput);
315
316 config::expand_derive(input)
317 .unwrap_or_else(syn::Error::into_compile_error)
318 .into()
319}
320
321/// Injectable Servcie
322#[proc_macro_derive(Service, attributes(service, inject))]
323pub fn derive_service(input: TokenStream) -> TokenStream {
324 let input = syn::parse_macro_input!(input as DeriveInput);
325
326 inject::expand_derive(input)
327 .unwrap_or_else(syn::Error::into_compile_error)
328 .into()
329}
330
331/// `#[cache]` - Transparent Redis-based caching for async functions.
332///
333/// This macro wraps an async function to automatically cache its result
334/// in Redis. It checks for a cached value before executing the function.
335/// If a cached result is found, it is deserialized and returned directly.
336/// Otherwise, the function runs normally and its result is stored in Redis.
337///
338/// # Syntax
339/// ```plain
340/// #[cache("key_pattern", expire = <seconds>, condition = <bool_expr>, unless = <bool_expr>)]
341/// ```
342///
343/// # Attributes
344/// - `"key_pattern"` (**required**):
345/// A format string used to generate the cache key. Function arguments can be interpolated using standard `format!` syntax.
346/// - `expire = <integer>` (**optional**):
347/// The number of seconds before the cached value expires. If omitted, the key will be stored without expiration.
348/// - `condition = <expression>` (**optional**):
349/// A boolean expression evaluated **before** executing the function.
350/// If this evaluates to `false`, caching is completely bypassed — no lookup and no insertion.
351/// The expression can access function parameters directly.
352/// - `unless = <expression>` (**optional**):
353/// A boolean expression evaluated **after** executing the function.
354/// If this evaluates to `true`, the result will **not** be written to the cache.
355/// The expression can access both parameters and a `result` variable (the return value).
356/// NOTE: If your function returns Result<T, E>, the `result` variable in unless refers to the inner Ok value (T), not the entire Result.
357/// This allows you to write expressions like result.is_none() for Result<Option<_>, _> functions.
358///
359/// # Function Requirements
360/// - Must be an `async fn`
361/// - Can return either a `Result<T, E>` or a plain value `T`
362/// - The return type must implement `serde::Serialize` and `serde::Deserialize`
363/// - Generics, attributes, and visibility will be preserved
364///
365/// # Example
366/// ```rust
367/// use spring_macros::cache;
368///
369/// #[derive(serde::Serialize, serde::Deserialize)]
370/// struct User {
371/// id: u64,
372/// name: String,
373/// }
374///
375/// struct MyError;
376///
377/// #[cache("user:{user_id}", expire = 600, condition = user_id % 2 == 0, unless = result.is_none())]
378/// async fn get_user(user_id: u64) -> Result<Option<User>, MyError> {
379/// // Fetch user from database
380/// unimplemented!("do something")
381/// }
382/// ```
383#[proc_macro_attribute]
384pub fn cache(args: TokenStream, input: TokenStream) -> TokenStream {
385 cache::cache(args, input)
386}
387
388#[cfg(feature = "socket_io")]
389/// Marks a function as a SocketIO connection handler
390///
391/// # Examples
392/// ```
393/// # use spring_web::socketioxide::extract::{SocketRef, Data};
394/// # use spring_web::rmpv::Value;
395/// # use spring_macros::on_connection;
396/// #[on_connection]
397/// async fn on_connection(socket: SocketRef, Data(data): Data<Value>) {
398/// // Handle connection
399/// }
400/// ```
401#[proc_macro_attribute]
402pub fn on_connection(args: TokenStream, input: TokenStream) -> TokenStream {
403 socketioxide::on_connection(args, input)
404}
405
406#[cfg(feature = "socket_io")]
407/// Marks a function as a SocketIO disconnection handler
408///
409/// # Examples
410/// ```
411/// # use spring_web::socketioxide::extract::SocketRef;
412/// # use spring_macros::on_disconnect;
413/// #[on_disconnect]
414/// async fn on_disconnect(socket: SocketRef) {
415/// // Handle disconnection
416/// }
417/// ```
418#[proc_macro_attribute]
419pub fn on_disconnect(args: TokenStream, input: TokenStream) -> TokenStream {
420 socketioxide::on_disconnect(args, input)
421}
422
423#[cfg(feature = "socket_io")]
424/// Marks a function as a SocketIO message subscription handler
425///
426/// # Examples
427/// ```
428/// # use spring_web::socketioxide::extract::{SocketRef, Data};
429/// # use spring_macros::subscribe_message;
430/// # use spring_web::rmpv::Value;
431/// #[subscribe_message("message")]
432/// async fn message(socket: SocketRef, Data(data): Data<Value>) {
433/// // Handle message
434/// }
435/// ```
436#[proc_macro_attribute]
437pub fn subscribe_message(args: TokenStream, input: TokenStream) -> TokenStream {
438 socketioxide::subscribe_message(args, input)
439}
440
441#[cfg(feature = "socket_io")]
442/// Marks a function as a SocketIO fallback handler
443///
444/// # Examples
445/// ```
446/// # use spring_web::socketioxide::extract::{SocketRef, Data};
447/// # use spring_web::rmpv::Value;
448/// # use spring_macros::on_fallback;
449/// #[on_fallback]
450/// async fn on_fallback(socket: SocketRef, Data(data): Data<Value>) {
451/// // Handle fallback
452/// }
453/// ```
454#[proc_macro_attribute]
455pub fn on_fallback(args: TokenStream, input: TokenStream) -> TokenStream {
456 socketioxide::on_fallback(args, input)
457}