elif_http/response/
helpers.rs

1//! Response helpers for maximum simplicity
2//!
3//! These functions provide one-liner response creation,
4//! making elif the easiest HTTP framework in Rust.
5//!
6//! # Examples
7//!
8//! ```rust,no_run
9//! use elif_http::response::{json, json_status, redirect, html};
10//! use elif_http::{HttpResult, ElifResponse, ElifStatusCode};
11//!
12//! // Simple one-liners
13//! async fn get_users() -> HttpResult<ElifResponse> {
14//!     let users = vec!["Alice", "Bob"];
15//!     Ok(json(&users))  // Simple JSON response
16//! }
17//!
18//! async fn create_user() -> HttpResult<ElifResponse> {
19//!     let user_data = serde_json::json!({"name": "Alice"});
20//!     Ok(json_status(&user_data, ElifStatusCode::CREATED))
21//! }
22//! ```
23
24use crate::response::{ElifResponse, ElifStatusCode};
25use serde::Serialize;
26use std::collections::HashMap;
27
28/// Create a JSON response with 200 OK status
29pub fn json<T: Serialize>(data: &T) -> ElifResponse {
30    ElifResponse::ok().json(data).unwrap_or_else(|err| {
31        tracing::error!("JSON serialization failed in response helper: {}", err);
32        ElifResponse::internal_server_error()
33    })
34}
35
36/// Create a JSON response with custom status code
37pub fn json_status<T: Serialize>(data: &T, status: ElifStatusCode) -> ElifResponse {
38    ElifResponse::with_status(status)
39        .json(data)
40        .unwrap_or_else(|err| {
41            tracing::error!("JSON serialization failed in json_status helper: {}", err);
42            ElifResponse::internal_server_error()
43        })
44}
45
46/// Create a JSON response with headers
47pub fn json_with_headers<T: Serialize>(data: &T, headers: &[(&str, &str)]) -> ElifResponse {
48    let mut response = ElifResponse::ok()
49        .json(data)
50        .unwrap_or_else(|_| ElifResponse::internal_server_error());
51
52    for (key, value) in headers {
53        response = response
54            .header(key, value)
55            .unwrap_or_else(|_| ElifResponse::internal_server_error());
56    }
57
58    response
59}
60
61/// Create a temporary redirect response (302)
62pub fn redirect(url: &str) -> ElifResponse {
63    ElifResponse::redirect_temporary(url).unwrap_or_else(|_| ElifResponse::internal_server_error())
64}
65
66/// Create a permanent redirect response (301)
67pub fn redirect_permanent(url: &str) -> ElifResponse {
68    ElifResponse::redirect_permanent(url).unwrap_or_else(|_| ElifResponse::internal_server_error())
69}
70
71/// Create a plain text response
72///
73/// Simple equivalent: `return response($text)`
74pub fn text<S: AsRef<str>>(content: S) -> ElifResponse {
75    ElifResponse::ok().text(content.as_ref())
76}
77
78/// Create an HTML response
79///
80/// Simple equivalent: `return response($html)->header('Content-Type', 'text/html')`
81pub fn html<S: AsRef<str>>(content: S) -> ElifResponse {
82    ElifResponse::ok()
83        .text(content.as_ref())
84        .with_header("content-type", "text/html; charset=utf-8")
85}
86
87/// Create a no content response (204)
88///
89/// Simple equivalent: `return response()->noContent()`
90pub fn no_content() -> ElifResponse {
91    ElifResponse::no_content()
92}
93
94/// Create a created response (201) with JSON data
95///
96/// Simple equivalent: `return response()->json($data, 201)`
97pub fn created<T: Serialize>(data: &T) -> ElifResponse {
98    json_status(data, ElifStatusCode::CREATED)
99}
100
101/// Create an accepted response (202) with optional data
102///
103/// Simple equivalent: `return response()->json($data, 202)`
104pub fn accepted<T: Serialize>(data: &T) -> ElifResponse {
105    json_status(data, ElifStatusCode::ACCEPTED)
106}
107
108/// Create a bad request response (400) with error message
109///
110/// Simple equivalent: `return response()->json(['error' => $message], 400)`
111pub fn bad_request<S: AsRef<str>>(message: S) -> ElifResponse {
112    let error = HashMap::from([("error", message.as_ref())]);
113    json_status(&error, ElifStatusCode::BAD_REQUEST)
114}
115
116/// Create an unauthorized response (401) with error message
117///
118/// Simple equivalent: `return response()->json(['error' => 'Unauthorized'], 401)`
119pub fn unauthorized<S: AsRef<str>>(message: S) -> ElifResponse {
120    let error = HashMap::from([("error", message.as_ref())]);
121    json_status(&error, ElifStatusCode::UNAUTHORIZED)
122}
123
124/// Create a forbidden response (403) with error message
125///
126/// Simple equivalent: `return response()->json(['error' => 'Forbidden'], 403)`
127pub fn forbidden<S: AsRef<str>>(message: S) -> ElifResponse {
128    let error = HashMap::from([("error", message.as_ref())]);
129    json_status(&error, ElifStatusCode::FORBIDDEN)
130}
131
132/// Create a not found response (404) with error message
133///
134/// Simple equivalent: `return response()->json(['error' => 'Not Found'], 404)`
135pub fn not_found<S: AsRef<str>>(message: S) -> ElifResponse {
136    let error = HashMap::from([("error", message.as_ref())]);
137    json_status(&error, ElifStatusCode::NOT_FOUND)
138}
139
140/// Create an internal server error response (500) with error message
141///
142/// Simple equivalent: `return response()->json(['error' => 'Internal Server Error'], 500)`
143pub fn server_error<S: AsRef<str>>(message: S) -> ElifResponse {
144    let error = HashMap::from([("error", message.as_ref())]);
145    json_status(&error, ElifStatusCode::INTERNAL_SERVER_ERROR)
146}
147
148/// Create a validation error response (422) with field errors
149///
150/// Simple equivalent: `return response()->json(['errors' => $errors], 422)`
151pub fn validation_error<T: Serialize>(errors: &T) -> ElifResponse {
152    let response_body = HashMap::from([("errors", errors)]);
153    json_status(&response_body, ElifStatusCode::UNPROCESSABLE_ENTITY)
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use serde_json::json;
160
161    #[test]
162    fn test_json_response() {
163        let data = vec!["Alice", "Bob"];
164        let response = json(&data);
165        assert_eq!(response.status_code(), ElifStatusCode::OK);
166    }
167
168    #[test]
169    fn test_json_status_response() {
170        let data = vec!["Alice"];
171        let response = json_status(&data, ElifStatusCode::CREATED);
172        assert_eq!(response.status_code(), ElifStatusCode::CREATED);
173    }
174
175    #[test]
176    fn test_redirect_response() {
177        let response = redirect("https://example.com");
178        assert_eq!(response.status_code(), ElifStatusCode::FOUND);
179    }
180
181    #[test]
182    fn test_text_response() {
183        let response = text("Hello, World!");
184        assert_eq!(response.status_code(), ElifStatusCode::OK);
185    }
186
187    #[test]
188    fn test_html_response() {
189        let response = html("<h1>Hello, World!</h1>");
190        assert_eq!(response.status_code(), ElifStatusCode::OK);
191    }
192
193    #[test]
194    fn test_no_content_response() {
195        let response = no_content();
196        assert_eq!(response.status_code(), ElifStatusCode::NO_CONTENT);
197    }
198
199    #[test]
200    fn test_created_response() {
201        let data = vec!["Alice"];
202        let response = created(&data);
203        assert_eq!(response.status_code(), ElifStatusCode::CREATED);
204    }
205
206    #[test]
207    fn test_error_responses() {
208        let bad_req = bad_request("Invalid input");
209        assert_eq!(bad_req.status_code(), ElifStatusCode::BAD_REQUEST);
210
211        let unauthorized_resp = unauthorized("Please login");
212        assert_eq!(
213            unauthorized_resp.status_code(),
214            ElifStatusCode::UNAUTHORIZED
215        );
216
217        let forbidden_resp = forbidden("Access denied");
218        assert_eq!(forbidden_resp.status_code(), ElifStatusCode::FORBIDDEN);
219
220        let not_found_resp = not_found("User not found");
221        assert_eq!(not_found_resp.status_code(), ElifStatusCode::NOT_FOUND);
222
223        let server_err = server_error("Database connection failed");
224        assert_eq!(
225            server_err.status_code(),
226            ElifStatusCode::INTERNAL_SERVER_ERROR
227        );
228    }
229
230    #[test]
231    fn test_validation_error() {
232        let errors = json!({
233            "name": ["The name field is required"],
234            "email": ["The email must be a valid email address"]
235        });
236
237        let response = validation_error(&errors);
238        assert_eq!(response.status_code(), ElifStatusCode::UNPROCESSABLE_ENTITY);
239    }
240
241    #[test]
242    fn test_json_with_headers() {
243        let data = vec!["Alice", "Bob"];
244        let headers = [("X-Total-Count", "2"), ("X-Custom", "value")];
245        let response = json_with_headers(&data, &headers);
246        assert_eq!(response.status_code(), ElifStatusCode::OK);
247    }
248}