armature_macros/
lib.rs

1//! Declarative macros for the Armature framework
2//!
3//! This crate provides convenient declarative macros (macro_rules!) for
4//! common patterns in Armature applications.
5//!
6//! # Response Macros
7//!
8//! Quick HTTP responses:
9//! ```ignore
10//! json_response!({ "message": "Success" })
11//! ok_json!({ "id": 123 })
12//! error_json!("User not found")
13//! ```
14//!
15//! # Route Grouping
16//!
17//! ```ignore
18//! routes! {
19//!     GET "/users" => list_users,
20//!     POST "/users" => create_user,
21//!     GET "/users/:id" => get_user,
22//! }
23//! ```
24
25/// Create a JSON HTTP response with status code
26///
27/// # Examples
28///
29/// ```ignore
30/// // With explicit status
31/// json_response!(200, { "message": "Success", "id": 123 })
32///
33/// // With status constant
34/// json_response!(StatusCode::OK, { "data": data })
35/// ```
36#[macro_export]
37macro_rules! json_response {
38    ($status:expr, $value:expr) => {{
39        use armature_core::HttpResponse;
40        HttpResponse::new($status).with_json(&$value)
41    }};
42}
43
44/// Create a 200 OK JSON response
45///
46/// # Examples
47///
48/// ```ignore
49/// ok_json!({ "message": "Success" })
50/// ok_json!({ "users": users_vec })
51/// ```
52#[macro_export]
53macro_rules! ok_json {
54    ($value:expr) => {{
55        use armature_core::HttpResponse;
56        HttpResponse::ok().with_json(&$value)
57    }};
58}
59
60/// Create a 201 Created JSON response
61///
62/// # Examples
63///
64/// ```ignore
65/// created_json!({ "id": new_id, "message": "User created" })
66/// ```
67#[macro_export]
68macro_rules! created_json {
69    ($value:expr) => {{
70        use armature_core::HttpResponse;
71        HttpResponse::created().with_json(&$value)
72    }};
73}
74
75/// Create a 400 Bad Request JSON error response
76///
77/// # Examples
78///
79/// ```ignore
80/// bad_request!("Invalid input")
81/// bad_request!("Field '{}' is required", field_name)
82/// ```
83#[macro_export]
84macro_rules! bad_request {
85    ($msg:expr) => {
86        {
87            use armature_core::HttpResponse;
88            HttpResponse::bad_request().with_json(&serde_json::json!({
89                "error": $msg,
90                "status": 400
91            }))
92        }
93    };
94    ($fmt:expr, $($arg:tt)*) => {
95        bad_request!(format!($fmt, $($arg)*))
96    };
97}
98
99/// Create a 404 Not Found JSON error response
100///
101/// # Examples
102///
103/// ```ignore
104/// not_found!("User not found")
105/// not_found!("Resource '{}' not found", resource_id)
106/// ```
107#[macro_export]
108macro_rules! not_found {
109    ($msg:expr) => {
110        {
111            use armature_core::HttpResponse;
112            HttpResponse::not_found().with_json(&serde_json::json!({
113                "error": $msg,
114                "status": 404
115            }))
116        }
117    };
118    ($fmt:expr, $($arg:tt)*) => {
119        not_found!(format!($fmt, $($arg)*))
120    };
121}
122
123/// Create a 500 Internal Server Error JSON response
124///
125/// # Examples
126///
127/// ```ignore
128/// internal_error!("Database connection failed")
129/// ```
130#[macro_export]
131macro_rules! internal_error {
132    ($msg:expr) => {
133        {
134            use armature_core::HttpResponse;
135            HttpResponse::internal_server_error().with_json(&serde_json::json!({
136                "error": $msg,
137                "status": 500
138            }))
139        }
140    };
141}
142
143/// Extract and validate path parameters
144///
145/// # Examples
146///
147/// ```ignore
148/// let id: i64 = path_param!(request, "id")?;
149/// let slug: String = path_param!(request, "slug")?;
150/// ```
151#[macro_export]
152macro_rules! path_param {
153    ($req:expr, $name:expr) => {{
154        $req.path_params
155            .get($name)
156            .ok_or_else(|| {
157                armature_core::Error::BadRequest(format!("Missing path parameter: {}", $name))
158            })?
159            .parse()
160            .map_err(|e| {
161                armature_core::Error::BadRequest(format!(
162                    "Invalid path parameter '{}': {}",
163                    $name, e
164                ))
165            })
166    }};
167}
168
169/// Extract and validate query parameters
170///
171/// # Examples
172///
173/// ```ignore
174/// let page: u32 = query_param!(request, "page").unwrap_or(1);
175/// let limit: u32 = query_param!(request, "limit").unwrap_or(20);
176/// ```
177#[macro_export]
178macro_rules! query_param {
179    ($req:expr, $name:expr) => {{ $req.query_params.get($name).and_then(|v| v.parse().ok()) }};
180}
181
182/// Extract header value
183///
184/// # Examples
185///
186/// ```ignore
187/// let auth = header!(request, "Authorization")?;
188/// let content_type = header!(request, "Content-Type").unwrap_or("text/plain");
189/// ```
190#[macro_export]
191macro_rules! header {
192    ($req:expr, $name:expr) => {{
193        $req.headers
194            .get($name)
195            .ok_or_else(|| armature_core::Error::BadRequest(format!("Missing header: {}", $name)))
196    }};
197}
198
199/// Create a validation error
200///
201/// # Examples
202///
203/// ```ignore
204/// return validation_error!("Email is required");
205/// return validation_error!("Age must be at least {}", min_age);
206/// ```
207#[macro_export]
208macro_rules! validation_error {
209    ($msg:expr) => {
210        Err(armature_core::Error::Validation($msg.to_string()))
211    };
212    ($fmt:expr, $($arg:tt)*) => {
213        validation_error!(format!($fmt, $($arg)*))
214    };
215}
216
217/// Guard a condition, returning an error if false
218///
219/// # Examples
220///
221/// ```ignore
222/// guard!(user.is_admin(), "Admin access required");
223/// guard!(age >= 18, "Must be 18 or older");
224/// ```
225#[macro_export]
226macro_rules! guard {
227    ($cond:expr, $msg:expr) => {
228        if !($cond) {
229            return Err(armature_core::Error::Forbidden($msg.to_string()));
230        }
231    };
232}
233
234/// Define multiple routes concisely
235///
236/// # Examples
237///
238/// ```ignore
239/// routes! {
240///     GET "/users" => list_users,
241///     POST "/users" => create_user,
242///     GET "/users/:id" => get_user,
243///     PUT "/users/:id" => update_user,
244///     DELETE "/users/:id" => delete_user,
245/// }
246/// ```
247#[macro_export]
248macro_rules! routes {
249    ($($method:ident $path:literal => $handler:expr),* $(,)?) => {
250        vec![
251            $(
252                armature_core::Route {
253                    method: armature_core::HttpMethod::from_str(stringify!($method)).unwrap(),
254                    path: $path.to_string(),
255                    handler: std::sync::Arc::new($handler),
256                    constraints: None,
257                },
258            )*
259        ]
260    };
261}
262
263/// Build JSON objects with type safety
264///
265/// # Examples
266///
267/// ```ignore
268/// let response = json_object! {
269///     "id" => user.id,
270///     "name" => user.name,
271///     "email" => user.email,
272/// };
273/// ```
274#[macro_export]
275macro_rules! json_object {
276    ($($key:literal => $value:expr),* $(,)?) => {
277        {
278            let mut map = serde_json::Map::new();
279            $(
280                map.insert($key.to_string(), serde_json::json!($value));
281            )*
282            serde_json::Value::Object(map)
283        }
284    };
285}
286
287/// Extract multiple path params at once
288///
289/// # Examples
290///
291/// ```ignore
292/// let (user_id, post_id) = path_params!(request, "user_id": i64, "post_id": i64)?;
293/// ```
294#[macro_export]
295macro_rules! path_params {
296    ($req:expr, $($name:literal : $type:ty),+ $(,)?) => {
297        {
298            (
299                $(
300                    {
301                        $req.path_params
302                            .get($name)
303                            .ok_or_else(|| armature_core::Error::BadRequest(
304                                format!("Missing path parameter: {}", $name)
305                            ))?
306                            .parse::<$type>()
307                            .map_err(|e| armature_core::Error::BadRequest(
308                                format!("Invalid path parameter '{}': {}", $name, e)
309                            ))?
310                    }
311                ),+
312            )
313        }
314    };
315}
316
317/// Log and return an error
318///
319/// # Examples
320///
321/// ```ignore
322/// log_error!("Failed to connect to database: {}", err);
323/// ```
324#[macro_export]
325macro_rules! log_error {
326    ($($arg:tt)*) => {
327        {
328            tracing::error!($($arg)*);
329            Err(armature_core::Error::InternalServerError(
330                format!($($arg)*)
331            ))
332        }
333    };
334}
335
336/// Create a paginated response
337///
338/// # Examples
339///
340/// ```ignore
341/// paginated_response!(users, page, total_count)
342/// ```
343#[macro_export]
344macro_rules! paginated_response {
345    ($data:expr, $page:expr, $total:expr) => {
346        {
347            use armature_core::HttpResponse;
348            HttpResponse::ok().with_json(&serde_json::json!({
349                "data": $data,
350                "pagination": {
351                    "page": $page,
352                    "total": $total,
353                    "per_page": $data.len(),
354                }
355            }))
356        }
357    };
358}
359
360pub mod prelude;
361
362#[cfg(test)]
363mod tests {
364    #[test]
365    fn test_macros_compile() {
366        // Ensure macros are exported
367    }
368}