1use bytes::Bytes;
2use http_body_util::Full;
3
4pub struct HttpResponse {
6 status: u16,
7 body: String,
8 headers: Vec<(String, String)>,
9}
10
11pub type Response = Result<HttpResponse, HttpResponse>;
13
14impl HttpResponse {
15 pub fn new() -> Self {
16 Self {
17 status: 200,
18 body: String::new(),
19 headers: Vec::new(),
20 }
21 }
22
23 pub fn text(body: impl Into<String>) -> Self {
25 Self {
26 status: 200,
27 body: body.into(),
28 headers: vec![("Content-Type".to_string(), "text/plain".to_string())],
29 }
30 }
31
32 pub fn json(body: serde_json::Value) -> Self {
34 Self {
35 status: 200,
36 body: body.to_string(),
37 headers: vec![("Content-Type".to_string(), "application/json".to_string())],
38 }
39 }
40
41 pub fn status(mut self, status: u16) -> Self {
43 self.status = status;
44 self
45 }
46
47 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
49 self.headers.push((name.into(), value.into()));
50 self
51 }
52
53 pub fn ok(self) -> Response {
55 Ok(self)
56 }
57
58 pub fn into_hyper(self) -> hyper::Response<Full<Bytes>> {
60 let mut builder = hyper::Response::builder().status(self.status);
61
62 for (name, value) in self.headers {
63 builder = builder.header(name, value);
64 }
65
66 builder
67 .body(Full::new(Bytes::from(self.body)))
68 .unwrap()
69 }
70}
71
72impl Default for HttpResponse {
73 fn default() -> Self {
74 Self::new()
75 }
76}
77
78pub trait ResponseExt {
80 fn status(self, code: u16) -> Self;
81 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self;
82}
83
84impl ResponseExt for Response {
85 fn status(self, code: u16) -> Self {
86 self.map(|r| r.status(code))
87 }
88
89 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self {
90 self.map(|r| r.header(name, value))
91 }
92}
93
94pub struct Redirect {
96 location: String,
97 query_params: Vec<(String, String)>,
98 status: u16,
99}
100
101impl Redirect {
102 pub fn to(path: impl Into<String>) -> Self {
104 Self {
105 location: path.into(),
106 query_params: Vec::new(),
107 status: 302,
108 }
109 }
110
111 pub fn route(name: &str) -> RedirectRouteBuilder {
113 RedirectRouteBuilder {
114 name: name.to_string(),
115 params: std::collections::HashMap::new(),
116 query_params: Vec::new(),
117 status: 302,
118 }
119 }
120
121 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
123 self.query_params.push((key.to_string(), value.into()));
124 self
125 }
126
127 pub fn permanent(mut self) -> Self {
129 self.status = 301;
130 self
131 }
132
133 fn build_url(&self) -> String {
134 if self.query_params.is_empty() {
135 self.location.clone()
136 } else {
137 let query = self
138 .query_params
139 .iter()
140 .map(|(k, v)| format!("{}={}", k, v))
141 .collect::<Vec<_>>()
142 .join("&");
143 format!("{}?{}", self.location, query)
144 }
145 }
146}
147
148impl From<Redirect> for Response {
150 fn from(redirect: Redirect) -> Response {
151 Ok(HttpResponse::new()
152 .status(redirect.status)
153 .header("Location", redirect.build_url()))
154 }
155}
156
157pub struct RedirectRouteBuilder {
159 name: String,
160 params: std::collections::HashMap<String, String>,
161 query_params: Vec<(String, String)>,
162 status: u16,
163}
164
165impl RedirectRouteBuilder {
166 pub fn with(mut self, key: &str, value: impl Into<String>) -> Self {
168 self.params.insert(key.to_string(), value.into());
169 self
170 }
171
172 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
174 self.query_params.push((key.to_string(), value.into()));
175 self
176 }
177
178 pub fn permanent(mut self) -> Self {
180 self.status = 301;
181 self
182 }
183
184 fn build_url(&self) -> Option<String> {
185 use crate::routing::route_with_params;
186
187 let mut url = route_with_params(&self.name, &self.params)?;
188 if !self.query_params.is_empty() {
189 let query = self
190 .query_params
191 .iter()
192 .map(|(k, v)| format!("{}={}", k, v))
193 .collect::<Vec<_>>()
194 .join("&");
195 url = format!("{}?{}", url, query);
196 }
197 Some(url)
198 }
199}
200
201impl From<RedirectRouteBuilder> for Response {
203 fn from(redirect: RedirectRouteBuilder) -> Response {
204 let url = redirect.build_url().ok_or_else(|| {
205 HttpResponse::text(format!("Route '{}' not found", redirect.name)).status(500)
206 })?;
207 Ok(HttpResponse::new()
208 .status(redirect.status)
209 .header("Location", url))
210 }
211}
212
213impl From<crate::error::FrameworkError> for HttpResponse {
218 fn from(err: crate::error::FrameworkError) -> HttpResponse {
219 let status = err.status_code();
220 let body = match &err {
221 crate::error::FrameworkError::ParamError { param_name } => {
222 serde_json::json!({
223 "error": format!("Missing required parameter: {}", param_name)
224 })
225 }
226 crate::error::FrameworkError::ValidationError { field, message } => {
227 serde_json::json!({
228 "error": "Validation failed",
229 "field": field,
230 "message": message
231 })
232 }
233 _ => {
234 serde_json::json!({
235 "error": err.to_string()
236 })
237 }
238 };
239 HttpResponse::json(body).status(status)
240 }
241}