sword_macros/lib.rs
1mod adapters;
2mod core;
3mod interceptor_derive;
4mod shared;
5
6use proc_macro::TokenStream;
7use quote::quote;
8use syn::{DeriveInput, parse_macro_input};
9
10#[proc_macro_attribute]
11pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
12 adapters::http::attributes::attribute("GET", attr, item)
13}
14
15#[proc_macro_attribute]
16pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
17 adapters::http::attributes::attribute("POST", attr, item)
18}
19
20#[proc_macro_attribute]
21pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
22 adapters::http::attributes::attribute("PUT", attr, item)
23}
24
25#[proc_macro_attribute]
26pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
27 adapters::http::attributes::attribute("DELETE", attr, item)
28}
29
30#[proc_macro_attribute]
31pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
32 adapters::http::attributes::attribute("PATCH", attr, item)
33}
34
35/// This macro is an alias for defining HTTP controllers.
36/// Defines an HTTP controller with a base path, and should be used in combination
37/// with the `#[routes]` macro for route implementation.
38///
39/// ### Parameters
40/// - `base_path`: The base path for the controller, e.g., "/api
41///
42/// ### Usage
43/// ```rust,ignore
44/// #[controller("/base_path")]
45/// struct MyController {}
46///
47/// #[routes]
48/// impl MyController {
49/// #[get("/sub_path")]
50/// async fn my_handler(&self) -> HttpResult {
51/// Ok(JsonResponse::Ok().message("Hello from MyController"))
52/// }
53/// }
54/// ```
55#[proc_macro_attribute]
56pub fn controller(attr: TokenStream, item: TokenStream) -> TokenStream {
57 adapters::expand_controller(attr, item)
58 .unwrap_or_else(|err| err.to_compile_error().into())
59}
60
61/// Derive macro for creating interceptors.
62///
63/// Generates implementations for the `Interceptor` trait.
64///
65/// # Usage
66/// ```rust,ignore
67/// use sword::prelude::*;
68///
69/// #[derive(Interceptor)]
70/// struct MyInterceptor;
71///
72/// // then implement some Interceptor trait variants
73/// // depending on the adapter type (e. g. OnRequest, OnConnect.)
74#[proc_macro_derive(Interceptor)]
75pub fn derive_interceptor(input: TokenStream) -> TokenStream {
76 interceptor_derive::derive_interceptor(input)
77 .unwrap_or_else(|err| err.to_compile_error().into())
78}
79
80/// Applies the interceptor to the current scope.
81/// This macro can be used to apply an `Interceptor` to different `Adapter` types,
82/// such as REST controllers or Socket.IO adapters.
83#[proc_macro_attribute]
84pub fn interceptor(attr: TokenStream, item: TokenStream) -> TokenStream {
85 let _ = attr;
86 item
87}
88
89/// Defines a configuration struct for the application.
90/// This macro generates the necessary code to deserialize the struct from
91/// the configuration toml file.
92///
93/// The struct must derive `Deserialize` from `serde`.
94///
95/// ### Parameters
96/// - `key`: The key in the configuration file where the struct is located.
97///
98/// ### Usage
99///
100/// ```rust,ignore
101/// #[derive(Deserialize)]
102/// #[config(key = "my-section")]
103/// struct MyConfig {
104/// my_key: String,
105/// }
106/// ```
107#[proc_macro_attribute]
108pub fn config(attr: TokenStream, item: TokenStream) -> TokenStream {
109 core::config::expand_config_struct(attr, item)
110}
111
112/// Marks a struct as injectable.
113///
114/// This macro generates the necessary code to register the struct
115/// in the dependency injection container. It can be used with or without
116/// parameters.
117///
118/// ### Parameters
119///
120/// - `kind`: (Optional) Specifies the kind of injectable.
121/// It can be either `provider` or `component`.
122///
123/// `provider`: The struct that has to be instantiated manually and
124/// registered in the container. The struct will be treated as a singleton by default.
125///
126/// `component`: The struct will be instantiated automatically by the container
127/// based on its dependencies. It's also treated as a singleton by default.
128///
129/// By default, if no kind is provided, it will be treated as `component`.
130///
131/// - `no_derive_clone`: (Optional) If provided, the struct will not derive the `Clone` automatically.
132/// By default, the struct will derive `Clone` if all its fields implement `Clone`.
133///
134/// ### Usage of `#[injectable]` without parameters (same as #[injectable(component)])
135///
136/// ```rust,ignore
137/// #[injectable]
138/// pub struct TaskRepository {
139/// db: Database,
140/// }
141///
142/// impl TaskRepository {
143/// pub async fn create(&self, task: Value) {
144/// self.db.insert("tasks", task).await;
145/// }
146///
147/// pub async fn find_all(&self) -> Option<Vec<Value>> {
148/// self.db.get_all("tasks").await
149/// }
150/// }
151/// ```
152///
153/// ### Usage of `#[injectable(provider)]` with parameters
154///
155/// ```rust,ignore
156/// #[injectable(provider)]
157/// pub struct Database {
158/// db: Store,
159/// }
160///
161/// impl Database {
162/// pub async fn new(db_conf: DatabaseConfig) -> Self {
163/// let db = Arc::new(RwLock::new(HashMap::new()));
164///
165/// db.write().await.insert(db_conf.collection_name, Vec::new());
166///
167/// Self { db }
168/// }
169///
170/// pub async fn insert(&self, table: &'static str, record: Value) {
171/// let mut db = self.db.write().await;
172///
173/// if let Some(table_data) = db.get_mut(table) {
174/// table_data.push(record);
175/// }
176/// }
177///
178/// pub async fn get_all(&self, table: &'static str) -> Option<Vec<Value>> {
179/// let db = self.db.read().await;
180///
181/// db.get(table).cloned()
182/// }
183/// }
184/// ```
185#[proc_macro_attribute]
186pub fn injectable(attr: TokenStream, item: TokenStream) -> TokenStream {
187 core::injectable::expand_injectable(attr, item)
188 .unwrap_or_else(|err| err.to_compile_error().into())
189}
190
191/// Derive macro for HTTP error enums.
192///
193/// Generates implementations for:
194/// - `From<Self> for JsonResponse` - Converts error to JSON response
195/// - `IntoResponse` - Allows returning error directly from handlers
196///
197/// **Note**: Use with `thiserror::Error` for `Display`, `Error`, and `#[from]`.
198///
199/// # Attributes
200///
201/// Each variant must have `#[http(...)]` with one of:
202///
203/// **For direct responses:**
204/// - `code = <u16>`: HTTP status code (required)
205/// - `message = "<string>"`: Custom message (optional, defaults to canonical reason)
206/// - `error = <field>`: Single error field to include (optional, named fields only)
207/// - `errors = <field>`: Multiple errors field to include (optional, named fields only)
208///
209/// **For delegation:**
210/// - `transparent`: Delegate to inner type's `From<T> for Json` (for wrapping other `HttpError` types)
211///
212/// **Tracing:**
213/// - `#[tracing(level)]`: Adds structured logging when the error occurs (optional)
214/// - `level`: One of `trace`, `debug`, `info`, `warn`, `error`
215/// - Generates `tracing::*!(...)` calls with error details
216/// - Compatible with `RUST_LOG` for filtering
217/// - Not allowed with `transparent` variants
218///
219/// ### Tracing Output
220/// The generated logs include:
221/// - `error_type`: The variant name as string
222/// - `status_code`: The HTTP status code
223/// - For unnamed variants (single field): `error = ?field` (debug format)
224/// - For named variants: Each field as `field_name = ?field_value`
225/// - Unit variants: Only `error_type` and `status_code`
226///
227/// # Example
228///
229/// ```rust,ignore
230/// use sword::prelude::*;
231/// use thiserror::Error;
232///
233/// #[derive(Debug, Error, HttpError)]
234/// pub enum ApiError {
235/// #[error("Not found")]
236/// #[http(code = 404)]
237/// #[tracing(info)] // Log: error_type="NotFound", status_code=404
238/// NotFound,
239///
240/// #[error("Forbidden: requires {role}")]
241/// #[http(code = 403, error = role)]
242/// #[tracing(warn)] // Log: error_type="Forbidden", status_code=403, role=?role
243/// Forbidden { role: String },
244///
245/// #[error("IO Error: {0}")]
246/// #[http(code = 500)]
247/// #[tracing(error)] // Log: error_type="Io", status_code=500, error=?_inner
248/// Io(#[from] std::io::Error),
249///
250/// #[error("Auth Error: {0}")]
251/// #[http(transparent)] // Delegates to other "HttpError" derivation
252/// Auth(#[from] AuthError),
253/// }
254/// ```
255#[proc_macro_derive(HttpError, attributes(http, tracing))]
256pub fn derive_http_error(input: TokenStream) -> TokenStream {
257 let input = parse_macro_input!(input as DeriveInput);
258
259 match adapters::derive_http_error(input) {
260 Ok(tokens) => tokens.into(),
261 Err(err) => err.to_compile_error().into(),
262 }
263}
264
265/// ### This is just a re-export of `tokio::main` to simplify the initial setup of
266/// ### Sword, you can use your own version of tokio adding it to your
267/// ### `Cargo.toml`, we are providing this initial base by default
268///
269/// ---
270///
271/// Marks async function to be executed by the selected runtime. This macro
272/// helps set up a `Runtime` without requiring the user to use
273/// [Runtime](../tokio/runtime/struct.Runtime.html) or
274/// [Builder](../tokio/runtime/struct.Builder.html) directly.
275///
276/// Note: This macro is designed to be simplistic and targets applications that
277/// do not require a complex setup. If the provided functionality is not
278/// sufficient, you may be interested in using
279/// [Builder](../tokio/runtime/struct.Builder.html), which provides a more
280/// powerful interface.
281///
282/// Note: This macro can be used on any function and not just the `main`
283/// function. Using it on a non-main function makes the function behave as if it
284/// was synchronous by starting a new runtime each time it is called. If the
285/// function is called often, it is preferable to create the runtime using the
286/// runtime builder so the runtime can be reused across calls.
287///
288/// # Non-worker async function
289///
290/// Note that the async function marked with this macro does not run as a
291/// worker. The expectation is that other tasks are spawned by the function here.
292/// Awaiting on other futures from the function provided here will not
293/// perform as fast as those spawned as workers.
294///
295/// # Multi-threaded runtime
296///
297/// To use the multi-threaded runtime, the macro can be configured using
298///
299/// ```rust,ignore
300/// #[tokio::main(flavor = "multi_thread", worker_threads = 10)]
301/// # async fn main() {}
302/// ```
303///
304/// The `worker_threads` option configures the number of worker threads, and
305/// defaults to the number of cpus on the system. This is the default flavor.
306///
307/// Note: The multi-threaded runtime requires the `rt-multi-thread` feature
308/// flag.
309///
310/// # Current thread runtime
311///
312/// To use the single-threaded runtime known as the `current_thread` runtime,
313/// the macro can be configured using
314///
315/// ```rust,ignore
316/// #[tokio::main(flavor = "current_thread")]
317/// # async fn main() {}
318/// ```
319///
320/// ## Function arguments:
321///
322/// Arguments are allowed for any functions aside from `main` which is special
323///
324/// ## Usage
325///
326/// ### Using the multi-thread runtime
327///
328/// ```ignore
329/// #[tokio::main]
330/// async fn main() {
331/// println!("Hello world");
332/// }
333/// ```
334///
335/// Equivalent code not using `#[tokio::main]`
336///
337/// ```ignore
338/// fn main() {
339/// tokio::runtime::Builder::new_multi_thread()
340/// .enable_all()
341/// .build()
342/// .unwrap()
343/// .block_on(async {
344/// println!("Hello world");
345/// })
346/// }
347/// ```
348///
349/// ### Using current thread runtime
350///
351/// The basic scheduler is single-threaded.
352///
353/// ```ignore
354/// #[tokio::main(flavor = "current_thread")]
355/// async fn main() {
356/// println!("Hello world");
357/// }
358/// ```
359///
360/// Equivalent code not using `#[tokio::main]`
361///
362/// ```ignore
363/// fn main() {
364/// tokio::runtime::Builder::new_current_thread()
365/// .enable_all()
366/// .build()
367/// .unwrap()
368/// .block_on(async {
369/// println!("Hello world");
370/// })
371/// }
372/// ```
373///
374/// ### Set number of worker threads
375///
376/// ```ignore
377/// #[tokio::main(worker_threads = 2)]
378/// async fn main() {
379/// println!("Hello world");
380/// }
381/// ```
382///
383/// Equivalent code not using `#[tokio::main]`
384///
385/// ```ignore
386/// fn main() {
387/// tokio::runtime::Builder::new_multi_thread()
388/// .worker_threads(2)
389/// .enable_all()
390/// .build()
391/// .unwrap()
392/// .block_on(async {
393/// println!("Hello world");
394/// })
395/// }
396/// ```
397///
398/// ### Configure the runtime to start with time paused
399///
400/// ```ignore
401/// #[tokio::main(flavor = "current_thread", start_paused = true)]
402/// async fn main() {
403/// println!("Hello world");
404/// }
405/// ```
406///
407/// Equivalent code not using `#[tokio::main]`
408///
409/// ```ignore
410/// fn main() {
411/// tokio::runtime::Builder::new_current_thread()
412/// .enable_all()
413/// .start_paused(true)
414/// .build()
415/// .unwrap()
416/// .block_on(async {
417/// println!("Hello world");
418/// })
419/// }
420/// ```
421///
422/// Note that `start_paused` requires the `test-util` feature to be enabled.
423///
424/// ### Rename package
425///
426/// ```ignore
427/// use tokio as tokio1;
428///
429/// #[tokio1::main(crate = "tokio1")]
430/// async fn main() {
431/// println!("Hello world");
432/// }
433/// ```
434///
435/// Equivalent code not using `#[tokio::main]`
436///
437/// ```ignore
438/// use tokio as tokio1;
439///
440/// fn main() {
441/// tokio1::runtime::Builder::new_multi_thread()
442/// .enable_all()
443/// .build()
444/// .unwrap()
445/// .block_on(async {
446/// println!("Hello world");
447/// })
448/// }
449/// ```
450///
451/// ### Configure unhandled panic behavior
452///
453/// Available options are `shutdown_runtime` and `ignore`. For more details, see
454/// [`Builder::unhandled_panic`].
455///
456/// This option is only compatible with the `current_thread` runtime.
457///
458/// ```no_run, ignore
459/// # #![allow(unknown_lints, unexpected_cfgs)]
460/// #[cfg(tokio_unstable)]
461/// #[tokio::main(flavor = "current_thread", unhandled_panic = "shutdown_runtime")]
462/// async fn main() {
463/// let _ = tokio::spawn(async {
464/// panic!("This panic will shutdown the runtime.");
465/// }).await;
466/// }
467/// # #[cfg(not(tokio_unstable))]
468/// # fn main() { }
469/// ```
470///
471/// Equivalent code not using `#[tokio::main]`
472///
473/// ```no_run, ignore
474/// # #![allow(unknown_lints, unexpected_cfgs)]
475/// #[cfg(tokio_unstable)]
476/// fn main() {
477/// tokio::runtime::Builder::new_current_thread()
478/// .enable_all()
479/// .unhandled_panic(UnhandledPanic::ShutdownRuntime)
480/// .build()
481/// .unwrap()
482/// .block_on(async {
483/// let _ = tokio::spawn(async {
484/// panic!("This panic will shutdown the runtime.");
485/// }).await;
486/// })
487/// }
488/// # #[cfg(not(tokio_unstable))]
489/// # fn main() { }
490/// ```
491///
492/// **Note**: This option depends on Tokio's [unstable API][unstable]. See [the
493/// documentation on unstable features][unstable] for details on how to enable
494/// Tokio's unstable features.
495///
496/// [`Builder::unhandled_panic`]: ../tokio/runtime/struct.Builder.html#method.unhandled_panic
497/// [unstable]: ../tokio/index.html#unstable-features
498#[proc_macro_attribute]
499pub fn main(_args: TokenStream, item: TokenStream) -> TokenStream {
500 let input = parse_macro_input!(item as syn::ItemFn);
501
502 let fn_body = input.block.clone();
503 let fn_attrs = input.attrs.clone();
504 let fn_vis = input.vis.clone();
505 let _fn_sig = input.sig;
506
507 #[allow(unused)]
508 let mut output = quote! {};
509
510 if cfg!(feature = "hot-reload") {
511 output = quote! {
512
513 async fn __internal_main() {
514 #fn_body
515 }
516
517 #(#fn_attrs)*
518 #fn_vis fn main() {
519 ::sword::internal::tokio_runtime::Builder::new_multi_thread()
520 .enable_all()
521 .build()
522 .expect("Failed building the Runtime")
523 .block_on(::sword::internal::dioxus_devtools::serve_subsecond(__internal_main))
524 }
525 };
526 } else {
527 output = quote! {
528 #(#fn_attrs)*
529 #fn_vis fn main() {
530 ::sword::internal::tokio_runtime::Builder::new_multi_thread()
531 .enable_all()
532 .build()
533 .expect("Failed building the Runtime")
534 .block_on( async #fn_body )
535 }
536 };
537 }
538
539 output.into()
540}
541
542#[cfg(feature = "adapter-socketio")]
543/// Marks a struct as a Socket.IO adapter.
544/// This macro should be used in combination with the `#[on]`
545/// macro for handler implementation.
546///
547/// ### Usage
548/// ```rust,ignore
549/// #[socketio_adapter("/chat")]
550/// struct ChatSocket;
551///
552/// impl ChatSocket {
553/// #[on("connection")]
554/// async fn on_connect(&self, socket: SocketRef) {
555/// println!("Client connected");
556/// }
557/// }
558/// ```
559#[proc_macro_attribute]
560pub fn socketio_adapter(attr: TokenStream, item: TokenStream) -> TokenStream {
561 adapters::expand_socketio_adapter(attr, item)
562 .unwrap_or_else(|err| err.to_compile_error().into())
563}
564
565#[cfg(feature = "adapter-socketio")]
566/// Defines Socket.IO handlers for its associated adapter.
567/// This macro should be used inside an `impl` block of a struct annotated with the `#[socketio_adapter]` macro.
568///
569/// ### Parameters
570/// - `path`: The path for the Socket.IO endpoint, e.g., `"/socket"`
571///
572/// ### Usage
573/// ```rust,ignore
574/// #[socketio_adapter("/socket")]
575/// struct SocketController;
576///
577/// #[handlers]
578/// impl SocketController {
579/// #[on_connection]
580/// async fn on_connect(&self, socket: SocketRef) {
581/// println!("Client connected");
582/// }
583///
584/// #[on_message("message")]
585/// async fn on_message(&self, socket: SocketRef, Data(msg): Data<String>) {
586/// println!("Received: {}", msg);
587/// }
588///
589/// #[on_disconnect]
590/// async fn on_disconnect(&self, socket: SocketRef) {
591/// println!("Client disconnected");
592/// }
593/// }
594/// ```
595/// Unified handler attribute for Socket.IO events.
596///
597/// ### Event Types
598/// - `#[on("connection")]` - Called when a client connects
599/// - `#[on("disconnection")]` - Called when a client disconnects
600/// - `#[on("fallback")]` - Called for unhandled events
601/// - `#[on("custom_event")]` - Called for custom event names
602///
603/// ### Parameters
604/// All handlers receive `&self` and `ctx: SocketContext` which provides access to:
605/// - Socket operations via `ctx.socket`
606/// - Message data via `ctx.try_data::<T>()`
607/// - Event name via `ctx.event()`
608/// - Acknowledgments via `ctx.ack()`
609///
610/// ### Usage
611/// ```rust,ignore
612/// #[socketio_adapter("/chat")]
613/// pub struct ChatAdapter { ... }
614///
615/// impl ChatAdapter {
616/// #[on("connection")]
617/// async fn on_connect(&self, ctx: SocketContext) {
618/// println!("Client connected: {}", ctx.socket.id);
619/// }
620///
621/// #[on("message")]
622/// async fn handle_message(&self, ctx: SocketContext) {
623/// let msg: String = ctx.try_data().unwrap();
624/// println!("Received: {}", msg);
625/// }
626/// }
627/// ```
628#[proc_macro_attribute]
629pub fn on(attr: TokenStream, item: TokenStream) -> TokenStream {
630 adapters::expand_on_handler(attr, item)
631 .unwrap_or_else(|err| err.to_compile_error().into())
632}