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