Skip to main content

caelix_macros/
lib.rs

1#![deny(missing_docs)]
2#![deny(rustdoc::broken_intra_doc_links)]
3
4//! Procedural macros for defining Caelix applications.
5
6#[cfg(feature = "config")]
7mod config;
8mod controller;
9mod gateway;
10mod injectable;
11mod microservice;
12
13#[cfg(all(feature = "actix", feature = "axum"))]
14compile_error!("caelix-macros backend features `actix` and `axum` are mutually exclusive");
15
16use proc_macro::TokenStream;
17use quote::quote;
18#[cfg(any(feature = "axum", feature = "tokio"))]
19use syn::{ItemFn, parse_macro_input, parse_quote};
20
21/// Implements typed environment loading and dependency injection.
22#[cfg(feature = "config")]
23#[proc_macro_derive(Config, attributes(config))]
24pub fn config(input: TokenStream) -> TokenStream {
25    config::expand(input)
26}
27
28/// Implements `caelix::Injectable` for a named or unit struct.
29///
30/// Every named field must be `Arc<T>` and is resolved from the module container;
31/// `Arc<Logger>` receives a logger scoped to the struct name. Tuple structs are
32/// rejected. The macro also records the resolved dependencies for visibility
33/// validation and preserves the optional lifecycle hooks on `Injectable`.
34#[proc_macro_attribute]
35pub fn injectable(args: TokenStream, input: TokenStream) -> TokenStream {
36    injectable::expand(args, input)
37}
38
39/// Generates dependency-injected microservice metadata for an inherent impl.
40#[proc_macro_attribute]
41pub fn microservice(args: TokenStream, input: TokenStream) -> TokenStream {
42    microservice::expand(args, input)
43}
44
45/// Marks a typed Core NATS request/reply handler inside a [`microservice`].
46#[proc_macro_attribute]
47pub fn message_pattern(_args: TokenStream, input: TokenStream) -> TokenStream {
48    input
49}
50
51/// Marks a typed JetStream event handler inside a [`microservice`].
52#[proc_macro_attribute]
53pub fn event_pattern(_args: TokenStream, input: TokenStream) -> TokenStream {
54    input
55}
56
57/// Marks the decoded message payload parameter inside a [`microservice`].
58#[proc_macro_attribute]
59pub fn payload(_args: TokenStream, input: TokenStream) -> TokenStream {
60    input
61}
62
63/// Marks the optional [`caelix::MessageContext`] handler parameter.
64#[proc_macro_attribute]
65pub fn context(_args: TokenStream, input: TokenStream) -> TokenStream {
66    input
67}
68
69/// Marks a dependency-injected struct as a route guard.
70///
71/// It accepts the same struct forms and field rules as [`injectable`], and
72/// generates `Injectable`; implement `caelix::Guard` separately to decide
73/// whether a request may continue.
74#[proc_macro_attribute]
75pub fn guard(args: TokenStream, input: TokenStream) -> TokenStream {
76    injectable::expand(args, input)
77}
78
79/// Generates controller metadata and backend routes for an `impl` block.
80///
81/// The required argument is a base path such as `#[controller("/users")]`.
82/// Methods may use `#[get]`, `#[post]`, `#[put]`, `#[patch]`, or `#[delete]`;
83/// extractor and guard/interceptor attributes are interpreted by the selected
84/// Actix or Axum backend. Generated code resolves controller dependencies from
85/// the `caelix` facade, so applications need no direct runtime dependency.
86#[proc_macro_attribute]
87pub fn controller(args: TokenStream, input: TokenStream) -> TokenStream {
88    controller::expand(args, input)
89}
90
91/// Applies a fixed-window policy to a controller or route.
92#[proc_macro_attribute]
93pub fn throttle(args: TokenStream, input: TokenStream) -> TokenStream {
94    controller::expand_controller_throttle_marker(args, input, false)
95}
96
97/// Disables inherited throttling for a controller or route.
98#[proc_macro_attribute]
99pub fn skip_throttle(args: TokenStream, input: TokenStream) -> TokenStream {
100    controller::expand_controller_throttle_marker(args, input, true)
101}
102
103/// Declares an OpenAPI response for a controller handler.
104///
105/// This marker is consumed by [`controller`] when the `openapi` feature is
106/// enabled and otherwise leaves the annotated item unchanged.
107#[proc_macro_attribute]
108pub fn response(_args: TokenStream, input: TokenStream) -> TokenStream {
109    input
110}
111
112/// Declares OpenAPI error responses for a controller handler.
113///
114/// Use exception marker types as arguments; the attribute is documentation
115/// metadata only and leaves the item unchanged outside controller expansion.
116#[proc_macro_attribute]
117pub fn errors(_args: TokenStream, input: TokenStream) -> TokenStream {
118    input
119}
120
121/// Declares a request-header parameter in generated OpenAPI documentation.
122///
123/// This is consumed by [`controller`] with the `openapi` feature and has no
124/// runtime extraction effect by itself.
125#[proc_macro_attribute]
126pub fn request_header(_args: TokenStream, input: TokenStream) -> TokenStream {
127    input
128}
129
130/// Declares OpenAPI security requirements for a controller or route handler.
131///
132/// Use `caelix::openapi::Security` values accepted by the controller macro;
133/// this marker affects generated documentation, not request authentication.
134#[proc_macro_attribute]
135pub fn security(_args: TokenStream, input: TokenStream) -> TokenStream {
136    input
137}
138
139/// Registers either an RFC 6455 `impl WebSocketGateway` or an Axum Socket.IO
140/// gateway implementation at the supplied path.
141///
142/// Apply it to an `impl` block with a string path argument. Regular WebSocket
143/// gateways implement `WebSocketGateway`; Socket.IO gateway expansion is only
144/// available with the Axum-selected `socketio` feature and works with
145/// [`on_message`] methods.
146#[proc_macro_attribute]
147pub fn gateway(args: TokenStream, input: TokenStream) -> TokenStream {
148    gateway::expand(args, input)
149}
150
151/// Marks an async method on a Socket.IO `#[gateway]` implementation as an
152/// event handler.
153///
154/// The event name is derived from the method name unless configured by the
155/// gateway macro. This attribute is meaningful only for Socket.IO gateways;
156/// it otherwise leaves the method unchanged until gateway expansion consumes it.
157#[proc_macro_attribute]
158pub fn on_message(_args: TokenStream, input: TokenStream) -> TokenStream {
159    input
160}
161
162/// Marks an async `main` that runs on the selected backend runtime.
163///
164/// Expands through `caelix::__actix_web` so consumers only need a `caelix` dependency
165/// (with the `actix` feature), not a direct `actix-web` dependency.
166#[proc_macro_attribute]
167pub fn main(_args: TokenStream, item: TokenStream) -> TokenStream {
168    #[cfg(any(feature = "axum", feature = "tokio"))]
169    {
170        expand_tokio_runtime(item, false)
171    }
172    #[cfg(not(any(feature = "axum", feature = "tokio")))]
173    {
174        let item = proc_macro2::TokenStream::from(item);
175        quote! { #[caelix::__actix_web::rt::main(system = "caelix::__actix_web::rt::System")] #item }.into()
176    }
177}
178
179/// Marks an async test that runs on the selected backend runtime.
180///
181/// Prefer this for integration tests that use `TestApplication`. Expands through
182/// `caelix::__actix_web` so consumers only need a `caelix` dependency.
183#[proc_macro_attribute]
184pub fn test(_args: TokenStream, item: TokenStream) -> TokenStream {
185    #[cfg(any(feature = "axum", feature = "tokio"))]
186    {
187        expand_tokio_runtime(item, true)
188    }
189    #[cfg(not(any(feature = "axum", feature = "tokio")))]
190    {
191        let item = proc_macro2::TokenStream::from(item);
192        quote! { #[caelix::__actix_web::rt::test(system = "caelix::__actix_web::rt::System")] #item }.into()
193    }
194}
195
196#[cfg(any(feature = "axum", feature = "tokio"))]
197fn expand_tokio_runtime(item: TokenStream, is_test: bool) -> TokenStream {
198    let mut function = parse_macro_input!(item as ItemFn);
199    let body = function.block;
200    function.sig.asyncness = None;
201    function.block = Box::new(parse_quote!({
202        caelix::__tokio::runtime::Builder::new_multi_thread()
203            .enable_all()
204            .build()
205            .expect("failed to build the Caelix runtime")
206            .block_on(async #body)
207    }));
208    if is_test {
209        function
210            .attrs
211            .push(parse_quote!(#[::core::prelude::v1::test]));
212    }
213    quote!(#function).into()
214}