Skip to main content

oxidite_core/
response.rs

1use crate::types::OxiditeResponse;
2use crate::error::Error;
3use http_body_util::{Full, BodyExt};
4use bytes::Bytes;
5use hyper::Response;
6use hyper::header::{HeaderValue, CONTENT_TYPE, SERVER};
7use http::StatusCode;
8
9pub(crate) const SERVER_HEADER_VALUE: &str = concat!("Oxidite/", env!("CARGO_PKG_VERSION"));
10
11impl OxiditeResponse {
12    /// Create a JSON response
13    pub fn json<T: serde::Serialize>(data: T) -> Self {
14        match serde_json::to_vec(&data) {
15            Ok(json_bytes) => {
16                let res = Response::builder()
17                    .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
18                    .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
19                    .body(Full::new(Bytes::from(json_bytes)).map_err(|e| match e {}).boxed())
20                    .unwrap();
21                Self(res)
22            },
23            Err(_) => {
24                let res = Response::builder()
25                    .status(StatusCode::INTERNAL_SERVER_ERROR)
26                    .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
27                    .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
28                    .body(Full::new(Bytes::from("{\"error\":\"Internal Server Error\"}".as_bytes().to_vec())).map_err(|e| match e {}).boxed())
29                    .unwrap();
30                Self(res)
31            },
32        }
33    }
34
35    /// Create an HTML response
36    pub fn html(body: impl Into<String>) -> Self {
37        let res = Response::builder()
38            .header(CONTENT_TYPE, HeaderValue::from_static("text/html"))
39            .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
40            .body(Full::new(Bytes::from(body.into())).map_err(|e| match e {}).boxed())
41            .unwrap();
42        Self(res)
43    }
44
45    /// Create a plain text response
46    pub fn text(body: impl Into<String>) -> Self {
47        let res = Response::builder()
48            .header(CONTENT_TYPE, HeaderValue::from_static("text/plain"))
49            .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
50            .body(Full::new(Bytes::from(body.into())).map_err(|e| match e {}).boxed())
51            .unwrap();
52        Self(res)
53    }
54
55    /// Create an empty response with 200 OK status
56    pub fn ok() -> Self {
57        let res = Response::builder()
58            .status(StatusCode::OK)
59            .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
60            .body(Full::new(Bytes::new()).map_err(|e| match e {}).boxed())
61            .unwrap();
62        Self(res)
63    }
64
65    /// Create an empty response with 204 No Content status
66    pub fn no_content() -> Self {
67        let res = Response::builder()
68            .status(StatusCode::NO_CONTENT)
69            .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
70            .body(Full::new(Bytes::new()).map_err(|e| match e {}).boxed())
71            .unwrap();
72        Self(res)
73    }
74}
75
76impl From<Error> for OxiditeResponse {
77    fn from(error: Error) -> Self {
78        let status = error.status_code();
79        let body = serde_json::json!({
80            "error": error.to_string()
81        });
82
83        let fallback = format!(r#"{{"error":"{}"}}"#, status);
84        let json_bytes = serde_json::to_vec(&body).unwrap_or_else(|_| fallback.into_bytes());
85
86        let res = Response::builder()
87            .status(status)
88            .header(CONTENT_TYPE, HeaderValue::from_static("application/json"))
89            .header(SERVER, HeaderValue::from_static(SERVER_HEADER_VALUE))
90            .body(Full::new(Bytes::from(json_bytes)).map_err(|e| match e {}).boxed())
91            .unwrap();
92        Self(res)
93    }
94}
95
96/// Helper functions for creating responses.
97///
98/// Use these for cleaner response creation without needing to call
99/// `Response::json(serde_json::json!(...))` directly.
100///
101/// # Examples
102///
103/// ```rust,ignore
104/// use oxidite_core::response::helpers::{json, text, html};
105///
106/// // Create a JSON response with inline data
107/// async fn handler() -> Result<OxiditeResponse> {
108///     Ok(json!({
109///         "status": "ok",
110///         "count": 42
111///     }))
112/// }
113///
114/// // Create a text response
115/// async fn text_handler() -> Result<OxiditeResponse> {
116///     Ok(text("Hello, world!"))
117/// }
118///
119/// // Create an HTML response
120/// async fn html_handler() -> Result<OxiditeResponse> {
121///     Ok(html("<h1>Hello</h1>"))
122/// }
123/// ```
124pub mod helpers {
125    use crate::types::OxiditeResponse;
126
127    /// Create a JSON response from any serializable value.
128    ///
129    /// This is a convenience wrapper around `Response::json()` that also
130    /// accepts `serde_json::json!()` macro output for inline JSON.
131    ///
132    /// # Examples
133    ///
134    /// ```rust,ignore
135    /// // With inline JSON (most common)
136    /// use oxidite_core::response::helpers::json;
137    /// async fn handler() -> OxiditeResponse {
138    ///     json!({
139    ///         "message": "Hello",
140    ///         "count": 42
141    ///     })
142    /// }
143    ///
144    /// // With a struct
145    /// use oxidite_core::response::helpers::json;
146    /// #[derive(serde::Serialize)]
147    /// struct MyData { message: String }
148    /// async fn handler2() -> OxiditeResponse {
149    ///     json(MyData { message: "Hello".into() })
150    /// }
151    /// ```
152    #[inline]
153    pub fn json<T: serde::Serialize>(data: T) -> OxiditeResponse {
154        OxiditeResponse::json(data)
155    }
156
157    /// Create a plain text response.
158    ///
159    /// # Examples
160    ///
161    /// ```rust,ignore
162    /// use oxidite_core::response::helpers::text;
163    /// async fn handler() -> OxiditeResponse {
164    ///     text("Hello, world!")
165    /// }
166    /// ```
167    #[inline]
168    pub fn text(body: impl Into<String>) -> OxiditeResponse {
169        OxiditeResponse::text(body)
170    }
171
172    /// Create an HTML response.
173    ///
174    /// # Examples
175    ///
176    /// ```rust,ignore
177    /// use oxidite_core::response::helpers::html;
178    /// async fn handler() -> OxiditeResponse {
179    ///     html("<h1>Hello</h1>")
180    /// }
181    /// ```
182    #[inline]
183    pub fn html(body: impl Into<String>) -> OxiditeResponse {
184        OxiditeResponse::html(body)
185    }
186
187    /// Create an empty 200 OK response.
188    #[inline]
189    pub fn ok() -> OxiditeResponse {
190        OxiditeResponse::ok()
191    }
192
193    /// Create an empty 204 No Content response.
194    #[inline]
195    pub fn no_content() -> OxiditeResponse {
196        OxiditeResponse::no_content()
197    }
198}