Skip to main content

ullun/
response.rs

1//! Response types and builders
2
3use crate::cookies::Cookie;
4use crate::error::Error;
5use bytes::Bytes;
6use http::StatusCode;
7use serde::Serialize;
8
9/// HTTP Response wrapper
10pub struct Response {
11    pub status: StatusCode,
12    pub headers: http::HeaderMap,
13    pub body: Bytes,
14    cookies: Vec<Cookie>,
15}
16
17impl Response {
18    pub fn new(status: StatusCode) -> Self {
19        Self {
20            status,
21            headers: http::HeaderMap::new(),
22            body: Bytes::new(),
23            cookies: Vec::new(),
24        }
25    }
26
27    /// Set a cookie
28    pub fn cookie(mut self, cookie: Cookie) -> Self {
29        self.cookies.push(cookie);
30        self
31    }
32
33    pub fn ok() -> Self {
34        Self::new(StatusCode::OK)
35    }
36
37    pub fn with_status(mut self, status: StatusCode) -> Self {
38        self.status = status;
39        self
40    }
41
42    pub fn with_body(mut self, body: impl Into<Bytes>) -> Self {
43        self.body = body.into();
44        self
45    }
46
47    pub fn with_header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
48        let key = key.into();
49        let value = value.into();
50
51        // Validate and insert header, skip invalid ones
52        if let (Ok(header_name), Ok(header_value)) = (
53            http::HeaderName::from_bytes(key.as_bytes()),
54            http::HeaderValue::from_str(&value),
55        ) {
56            self.headers.insert(header_name, header_value);
57        } else {
58            eprintln!("Warning: Invalid header {}={}", key, value);
59        }
60        self
61    }
62
63    pub fn json<T: Serialize>(data: T) -> Self {
64        match serde_json::to_vec(&data) {
65            Ok(body) => Self::ok()
66                .with_header("content-type", "application/json")
67                .with_body(body),
68            Err(e) => {
69                eprintln!("JSON serialization error: {}", e);
70                Self::new(StatusCode::INTERNAL_SERVER_ERROR)
71                    .with_header("content-type", "application/json")
72                    .with_body("{\"error\":\"Internal serialization error\"}")
73            }
74        }
75    }
76
77    pub fn text(text: impl Into<String>) -> Self {
78        Self::ok()
79            .with_header("content-type", "text/plain")
80            .with_body(text.into())
81    }
82
83    /// `200 OK` with an HTML body
84    pub fn html(html: impl Into<String>) -> Self {
85        Self::ok()
86            .with_header("content-type", "text/html; charset=utf-8")
87            .with_body(html.into())
88    }
89
90    /// `303 See Other`, the right answer after a form POST (Post/Redirect/Get)
91    pub fn redirect(location: impl Into<String>) -> Self {
92        Self::new(StatusCode::SEE_OTHER).with_header("location", location)
93    }
94
95    /// `308 Permanent Redirect`, keeps method and body
96    pub fn redirect_permanent(location: impl Into<String>) -> Self {
97        Self::new(StatusCode::PERMANENT_REDIRECT).with_header("location", location)
98    }
99
100    pub fn from_error(error: Error) -> Self {
101        let status = error.status();
102        let mut body = serde_json::json!({
103            "error": error.message(),
104            "status": error.status_code(),
105        });
106        if let Error::Validation(fields) = &error {
107            // field -> list of messages, in the order they were found
108            let mut by_field = serde_json::Map::new();
109            for f in fields {
110                if let Some(list) = by_field
111                    .entry(f.field.clone())
112                    .or_insert_with(|| serde_json::Value::Array(Vec::new()))
113                    .as_array_mut()
114                {
115                    list.push(serde_json::Value::String(f.message.clone()));
116                }
117            }
118            body["fields"] = serde_json::Value::Object(by_field);
119        }
120        let body_bytes =
121            serde_json::to_vec(&body).unwrap_or_else(|_| b"{\"error\":\"Unknown error\"}".to_vec());
122        Self::new(status)
123            .with_header("content-type", "application/json")
124            .with_body(body_bytes)
125    }
126
127    pub(crate) fn into_hyper(self) -> hyper::Response<http_body_util::Full<Bytes>> {
128        let mut builder = hyper::Response::builder().status(self.status);
129
130        for (key, value) in self.headers.iter() {
131            builder = builder.header(key, value);
132        }
133
134        // Add Set-Cookie headers
135        for cookie in self.cookies {
136            builder = builder.header("set-cookie", cookie.to_header_value());
137        }
138
139        builder
140            .body(http_body_util::Full::new(self.body))
141            .unwrap_or_else(|e| {
142                eprintln!("Failed to build response: {}", e);
143                hyper::Response::builder()
144                    .status(StatusCode::INTERNAL_SERVER_ERROR)
145                    .body(http_body_util::Full::new(Bytes::from(
146                        "Internal Server Error",
147                    )))
148                    .unwrap()
149            })
150    }
151}
152
153/// JSON response helper
154pub struct Json<T>(pub T);
155
156impl<T: Serialize> From<Json<T>> for Response {
157    fn from(json: Json<T>) -> Self {
158        Response::json(json.0)
159    }
160}