kit_macros/
lib.rs

1//! Procedural macros for the Kit framework
2//!
3//! This crate provides compile-time validated macros for:
4//! - Inertia.js responses with component validation
5//! - Named route redirects with route validation
6//! - Service auto-registration
7//! - Handler attribute for controller methods
8//! - FormRequest for validated request data
9
10use proc_macro::TokenStream;
11
12mod domain_error;
13mod handler;
14mod inertia;
15mod injectable;
16mod redirect;
17mod request;
18mod service;
19mod utils;
20
21/// Derive macro for generating `Serialize` implementation for Inertia props
22///
23/// # Example
24///
25/// ```rust,ignore
26/// #[derive(InertiaProps)]
27/// struct HomeProps {
28///     title: String,
29///     user: User,
30/// }
31/// ```
32#[proc_macro_derive(InertiaProps)]
33pub fn derive_inertia_props(input: TokenStream) -> TokenStream {
34    inertia::derive_inertia_props_impl(input)
35}
36
37/// Create an Inertia response with compile-time component validation
38///
39/// # Examples
40///
41/// ## With typed struct (recommended for type safety):
42/// ```rust,ignore
43/// #[derive(InertiaProps)]
44/// struct HomeProps {
45///     title: String,
46///     user: User,
47/// }
48///
49/// inertia_response!("Home", HomeProps { title: "Welcome".into(), user })
50/// ```
51///
52/// ## With JSON-like syntax (for quick prototyping):
53/// ```rust,ignore
54/// inertia_response!("Dashboard", { "user": { "name": "John" } })
55/// ```
56///
57/// This macro validates that the component file exists at compile time.
58/// If `frontend/src/pages/Dashboard.tsx` doesn't exist, you'll get a compile error.
59#[proc_macro]
60pub fn inertia_response(input: TokenStream) -> TokenStream {
61    inertia::inertia_response_impl(input)
62}
63
64/// Create a redirect to a named route with compile-time validation
65///
66/// # Examples
67///
68/// ```rust,ignore
69/// // Simple redirect
70/// redirect!("users.index").into()
71///
72/// // Redirect with route parameters
73/// redirect!("users.show").with("id", "42").into()
74///
75/// // Redirect with query parameters
76/// redirect!("users.index").query("page", "1").into()
77/// ```
78///
79/// This macro validates that the route name exists at compile time.
80/// If the route doesn't exist, you'll get a compile error with suggestions.
81#[proc_macro]
82pub fn redirect(input: TokenStream) -> TokenStream {
83    redirect::redirect_impl(input)
84}
85
86/// Mark a trait as a service for the App container
87///
88/// This attribute macro automatically adds `Send + Sync + 'static` bounds
89/// to your trait, making it suitable for use with the dependency injection
90/// container.
91///
92/// # Example
93///
94/// ```rust,ignore
95/// use kit::service;
96///
97/// #[service]
98/// pub trait HttpClient {
99///     async fn get(&self, url: &str) -> Result<String, Error>;
100/// }
101///
102/// // This expands to:
103/// pub trait HttpClient: Send + Sync + 'static {
104///     async fn get(&self, url: &str) -> Result<String, Error>;
105/// }
106/// ```
107///
108/// Then you can use it with the App container:
109///
110/// ```rust,ignore
111/// // Register
112/// App::bind::<dyn HttpClient>(Arc::new(RealHttpClient::new()));
113///
114/// // Resolve
115/// let client: Arc<dyn HttpClient> = App::make::<dyn HttpClient>().unwrap();
116/// ```
117#[proc_macro_attribute]
118pub fn service(attr: TokenStream, input: TokenStream) -> TokenStream {
119    service::service_impl(attr, input)
120}
121
122/// Attribute macro to auto-register a concrete type as a singleton
123///
124/// This macro automatically:
125/// 1. Derives `Default` and `Clone` for the struct
126/// 2. Registers it as a singleton in the App container at startup
127///
128/// # Example
129///
130/// ```rust,ignore
131/// use kit::injectable;
132///
133/// #[injectable]
134/// pub struct AppState {
135///     pub counter: u32,
136/// }
137///
138/// // Automatically registered at startup
139/// // Resolve via:
140/// let state: AppState = App::get().unwrap();
141/// ```
142#[proc_macro_attribute]
143pub fn injectable(_attr: TokenStream, input: TokenStream) -> TokenStream {
144    injectable::injectable_impl(input)
145}
146
147/// Define a domain error with automatic HTTP response conversion
148///
149/// This macro automatically:
150/// 1. Derives `Debug` and `Clone` for the type
151/// 2. Implements `Display`, `Error`, and `HttpError` traits
152/// 3. Implements `From<T> for FrameworkError` for seamless `?` usage
153///
154/// # Attributes
155///
156/// - `status`: HTTP status code (default: 500)
157/// - `message`: Error message for Display (default: struct name converted to sentence)
158///
159/// # Example
160///
161/// ```rust,ignore
162/// use kit::domain_error;
163///
164/// #[domain_error(status = 404, message = "User not found")]
165/// pub struct UserNotFoundError {
166///     pub user_id: i32,
167/// }
168///
169/// // Usage in controller - just use ? operator
170/// pub async fn get_user(id: i32) -> Result<User, FrameworkError> {
171///     users.find(id).ok_or(UserNotFoundError { user_id: id })?
172/// }
173/// ```
174#[proc_macro_attribute]
175pub fn domain_error(attr: TokenStream, input: TokenStream) -> TokenStream {
176    domain_error::domain_error_impl(attr, input)
177}
178
179/// Attribute macro for controller handler methods
180///
181/// Transforms handler functions to automatically extract typed parameters
182/// from HTTP requests using the `FromRequest` trait.
183///
184/// # Examples
185///
186/// ## With Request parameter:
187/// ```rust,ignore
188/// use kit::{handler, Request, Response, json_response};
189///
190/// #[handler]
191/// pub async fn index(req: Request) -> Response {
192///     json_response!({ "message": "Hello" })
193/// }
194/// ```
195///
196/// ## With FormRequest parameter:
197/// ```rust,ignore
198/// use kit::{handler, Response, json_response, form_request};
199///
200/// #[form_request]
201/// pub struct CreateUserRequest {
202///     #[validate(email)]
203///     pub email: String,
204/// }
205///
206/// #[handler]
207/// pub async fn store(form: CreateUserRequest) -> Response {
208///     // `form` is already validated - returns 422 if invalid
209///     json_response!({ "email": form.email })
210/// }
211/// ```
212///
213/// ## Without parameters:
214/// ```rust,ignore
215/// #[handler]
216/// pub async fn health_check() -> Response {
217///     json_response!({ "status": "ok" })
218/// }
219/// ```
220#[proc_macro_attribute]
221pub fn handler(attr: TokenStream, input: TokenStream) -> TokenStream {
222    handler::handler_impl(attr, input)
223}
224
225/// Derive macro for FormRequest trait
226///
227/// Generates the `FormRequest` trait implementation for a struct.
228/// The struct must also derive `serde::Deserialize` and `validator::Validate`.
229///
230/// For the cleanest DX, use the `#[request]` attribute macro instead,
231/// which handles all derives automatically.
232///
233/// # Example
234///
235/// ```rust,ignore
236/// use kit::{FormRequest, Deserialize, Validate};
237///
238/// #[derive(Deserialize, Validate, FormRequest)]
239/// pub struct CreateUserRequest {
240///     #[validate(email)]
241///     pub email: String,
242///
243///     #[validate(length(min = 8))]
244///     pub password: String,
245/// }
246/// ```
247#[proc_macro_derive(FormRequest)]
248pub fn derive_form_request(input: TokenStream) -> TokenStream {
249    request::derive_request_impl(input)
250}
251
252/// Attribute macro for clean request data definition
253///
254/// This is the recommended way to define validated request types.
255/// It automatically adds the necessary derives and generates the trait impl.
256///
257/// Works with both:
258/// - `application/json` - JSON request bodies
259/// - `application/x-www-form-urlencoded` - HTML form submissions
260///
261/// # Example
262///
263/// ```rust,ignore
264/// use kit::request;
265///
266/// #[request]
267/// pub struct CreateUserRequest {
268///     #[validate(email)]
269///     pub email: String,
270///
271///     #[validate(length(min = 8))]
272///     pub password: String,
273/// }
274///
275/// // This can now be used directly in handlers:
276/// #[handler]
277/// pub async fn store(form: CreateUserRequest) -> Response {
278///     // Automatically validated - returns 422 with errors if invalid
279///     json_response!({ "email": form.email })
280/// }
281/// ```
282#[proc_macro_attribute]
283pub fn request(attr: TokenStream, input: TokenStream) -> TokenStream {
284    request::request_attr_impl(attr, input)
285}