gearbox_rs_macros/lib.rs
1mod app;
2mod cog;
3mod config;
4mod crud;
5mod paths;
6mod pg_entity;
7mod pg_queries;
8mod route;
9mod utils;
10
11use proc_macro::TokenStream;
12
13/// Derive macro for creating a Cog with automatic dependency injection.
14///
15/// Generates `impl Cog`, a `CogFactory`, and inventory registration.
16///
17/// # Field Attributes
18///
19/// - `#[inject]` - Dependency injection. Field type must be `Arc<T>` where `T: Cog`.
20/// - `#[config]` - Load from configuration. Type must implement `CogConfig + Default`.
21/// - `#[default(fn)]` - Initialize via sync function: `fn() -> T`.
22/// - `#[default_async(fn)]` - Initialize via async function: `async fn(&Arc<Hub>) -> Result<T, Error>`.
23/// - No attribute - Uses `Default::default()`.
24///
25/// # Example
26///
27/// ```ignore
28/// #[cog]
29/// struct UserService {
30/// #[inject]
31/// db: Arc<Database>,
32/// #[config]
33/// settings: UserServiceConfig,
34/// request_count: u64,
35/// }
36/// ```
37#[proc_macro_attribute]
38pub fn cog(_attr: TokenStream, item: TokenStream) -> TokenStream {
39 cog::generate_cog(item)
40}
41
42/// Defines a GET route handler with automatic dependency injection.
43///
44/// # Parameter Injection
45///
46/// Parameters typed as `Arc<T>` where `T: Cog` are automatically rewritten to use
47/// `Inject<T>`, which extracts the service from the Hub at request time. Other
48/// axum extractors (`Json`, `Path`, `Query`, etc.) pass through unchanged.
49///
50/// For example, `repo: Arc<UserRepo>` expands to `Inject(repo): Inject<UserRepo>`.
51///
52/// # Example
53///
54/// ```ignore
55/// #[get("/users")]
56/// async fn list_users(repo: Arc<UserRepo>) -> impl IntoResponse {
57/// Json(repo.find_all().await)
58/// }
59/// ```
60#[proc_macro_attribute]
61pub fn get(attr: TokenStream, item: TokenStream) -> TokenStream {
62 route::generate_route("GET", attr, item)
63}
64
65/// Defines a POST route handler with automatic dependency injection.
66///
67/// `Arc<T>` parameters are rewritten to `Inject<T>` (see [`get`] for details).
68///
69/// # Example
70///
71/// ```ignore
72/// #[post("/users")]
73/// async fn create_user(
74/// repo: Arc<UserRepo>,
75/// body: Json<CreateUser>,
76/// ) -> impl IntoResponse {
77/// let user = repo.create(body.0).await;
78/// (StatusCode::CREATED, Json(user))
79/// }
80/// ```
81#[proc_macro_attribute]
82pub fn post(attr: TokenStream, item: TokenStream) -> TokenStream {
83 route::generate_route("POST", attr, item)
84}
85
86/// Defines a PUT route handler with automatic dependency injection.
87///
88/// `Arc<T>` parameters are rewritten to `Inject<T>` (see [`get`] for details).
89///
90/// # Example
91///
92/// ```ignore
93/// #[put("/users/{id}")]
94/// async fn update_user(
95/// path: Path<String>,
96/// repo: Arc<UserRepo>,
97/// body: Json<UpdateUser>,
98/// ) -> impl IntoResponse {
99/// Json(repo.update(&path, body.0).await)
100/// }
101/// ```
102#[proc_macro_attribute]
103pub fn put(attr: TokenStream, item: TokenStream) -> TokenStream {
104 route::generate_route("PUT", attr, item)
105}
106
107/// Defines a DELETE route handler with automatic dependency injection.
108///
109/// `Arc<T>` parameters are rewritten to `Inject<T>` (see [`get`] for details).
110///
111/// # Example
112///
113/// ```ignore
114/// #[delete("/users/{id}")]
115/// async fn delete_user(
116/// path: Path<String>,
117/// repo: Arc<UserRepo>,
118/// ) -> impl IntoResponse {
119/// repo.delete(&path).await;
120/// StatusCode::NO_CONTENT
121/// }
122/// ```
123#[proc_macro_attribute]
124pub fn delete(attr: TokenStream, item: TokenStream) -> TokenStream {
125 route::generate_route("DELETE", attr, item)
126}
127
128/// Defines a PATCH route handler with automatic dependency injection.
129///
130/// `Arc<T>` parameters are rewritten to `Inject<T>` (see [`get`] for details).
131///
132/// # Example
133///
134/// ```ignore
135/// #[patch("/users/{id}")]
136/// async fn patch_user(
137/// path: Path<String>,
138/// repo: Arc<UserRepo>,
139/// body: Json<PatchUser>,
140/// ) -> impl IntoResponse {
141/// Json(repo.patch(&path, body.0).await)
142/// }
143/// ```
144#[proc_macro_attribute]
145pub fn patch(attr: TokenStream, item: TokenStream) -> TokenStream {
146 route::generate_route("PATCH", attr, item)
147}
148
149/// Implements `CogConfig` trait for a struct with the given config key.
150///
151/// The struct must also derive `Default` and `serde::Deserialize`.
152///
153/// # Example
154///
155/// ```ignore
156/// #[cog_config("database")]
157/// #[derive(Default, Deserialize)]
158/// pub struct DbConfig {
159/// url: String,
160/// max_connections: u32,
161/// }
162/// ```
163#[proc_macro_attribute]
164pub fn cog_config(attr: TokenStream, item: TokenStream) -> TokenStream {
165 config::generate_cog_config(attr, item)
166}
167
168/// Derive macro for implementing `PgEntity` and `PgRepository` traits.
169///
170/// Generates CRUD repository operations (create, update, find, delete) on `PgClient`.
171///
172/// # Struct Attributes
173///
174/// - `#[table("name")]` - Required. Database table name.
175///
176/// # Field Attributes
177///
178/// - `#[primary_key]` - Primary key field(s). Multiple fields create composite keys.
179/// - `#[skip]` - Exclude from all DB operations. Field must implement `Default`.
180/// - `#[skip_upsert]` - Exclude from UPDATE portion of upsert operations.
181/// - `#[pg_type(Type)]` - Cast field to a different type when binding.
182///
183/// # Example
184///
185/// ```ignore
186/// #[derive(PgEntity)]
187/// #[table("users")]
188/// pub struct User {
189/// #[primary_key]
190/// pub id: String,
191/// pub name: String,
192/// #[skip]
193/// pub computed_field: String,
194/// }
195/// ```
196#[proc_macro_derive(PgEntity, attributes(table, schema, primary_key, skip, skip_upsert, pg_type))]
197pub fn pg_entity(input: TokenStream) -> TokenStream {
198 pg_entity::generate_pg_entity(input)
199}
200
201/// Derive macro for generating REST CRUD endpoints with pagination.
202///
203/// Use alongside `PgEntity` to generate DTOs and route handlers
204/// (GET, POST, PUT, PATCH, DELETE) registered via inventory.
205///
206/// # Struct Attributes
207///
208/// - `#[table("name")]` - Required. Database table name (from PgEntity).
209/// - `#[crud(path = "/users")]` - REST path (default: pluralized snake_case).
210/// - `#[crud(read_only)]` - Only generate read endpoints.
211/// - `#[crud(skip_create)]` / `#[crud(skip_delete)]` - Skip specific endpoints.
212///
213/// # Field Attributes
214///
215/// - `#[primary_key]` - Primary key (from PgEntity).
216/// - `#[auto_generated]` - DB-generated field (excluded from create/update DTOs).
217/// - `#[readonly]` - Response-only field (e.g., `created_at`).
218/// - `#[writeonly]` - Input-only field (e.g., `password_hash`).
219///
220/// # Example
221///
222/// ```ignore
223/// #[derive(PgEntity, Crud)]
224/// #[table("users")]
225/// #[crud(path = "/users")]
226/// pub struct User {
227/// #[primary_key]
228/// #[auto_generated]
229/// pub id: Uuid,
230/// pub name: String,
231/// #[readonly]
232/// pub created_at: DateTime<Utc>,
233/// }
234/// ```
235#[proc_macro_derive(
236 Crud,
237 attributes(
238 table,
239 schema,
240 crud,
241 primary_key,
242 auto_generated,
243 readonly,
244 writeonly,
245 skip,
246 pg_type
247 )
248)]
249pub fn crud(input: TokenStream) -> TokenStream {
250 crud::generate_crud(input)
251}
252
253/// Generates a Gearbox application entry point.
254///
255/// Replaces `main` with the Gearbox startup sequence (tokio runtime + framework init).
256///
257/// # Example
258///
259/// ```ignore
260/// #[gearbox_app]
261/// fn main() {}
262/// ```
263#[proc_macro_attribute]
264pub fn gearbox_app(attr: TokenStream, item: TokenStream) -> TokenStream {
265 app::generate_gearbox_app(attr, item)
266}
267
268/// Generate custom query methods on `PgClient`.
269///
270/// Define SQL queries with type-safe parameters and return types.
271/// Placeholder count is validated at compile time.
272///
273/// # Return Types
274///
275/// - `Option<T>` — `fetch_optional`
276/// - `Vec<T>` — `fetch_all`
277/// - `T` (struct) — `fetch_one`
278/// - Scalars (`i64`, `String`, etc.) — `query_scalar`
279/// - `bool` — `rows_affected > 0`
280/// - `u64` — `rows_affected`
281/// - (none) — `execute`
282///
283/// # Example
284///
285/// ```ignore
286/// pg_queries! {
287/// fn find_by_email(email: &str) -> Option<User> {
288/// "SELECT * FROM users WHERE email = $1"
289/// }
290///
291/// fn count_active() -> i64 {
292/// "SELECT COUNT(*) FROM users WHERE active = true"
293/// }
294/// }
295/// ```
296#[proc_macro]
297pub fn pg_queries(input: TokenStream) -> TokenStream {
298 pg_queries::pg_queries(input)
299}