Skip to main content

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}