1use super::cookie::Cookie;
2use bytes::Bytes;
3use http_body_util::Full;
4
5pub struct HttpResponse {
7 status: u16,
8 body: String,
9 headers: Vec<(String, String)>,
10}
11
12pub type Response = Result<HttpResponse, HttpResponse>;
14
15impl HttpResponse {
16 pub fn new() -> Self {
17 Self {
18 status: 200,
19 body: String::new(),
20 headers: Vec::new(),
21 }
22 }
23
24 pub fn text(body: impl Into<String>) -> Self {
26 Self {
27 status: 200,
28 body: body.into(),
29 headers: vec![("Content-Type".to_string(), "text/plain".to_string())],
30 }
31 }
32
33 pub fn json(body: serde_json::Value) -> Self {
35 Self {
36 status: 200,
37 body: body.to_string(),
38 headers: vec![("Content-Type".to_string(), "application/json".to_string())],
39 }
40 }
41
42 pub fn status(mut self, status: u16) -> Self {
44 self.status = status;
45 self
46 }
47
48 pub fn header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
50 self.headers.push((name.into(), value.into()));
51 self
52 }
53
54 pub fn cookie(self, cookie: Cookie) -> Self {
66 self.header("Set-Cookie", cookie.to_header_value())
67 }
68
69 pub fn ok(self) -> Response {
71 Ok(self)
72 }
73
74 pub fn into_hyper(self) -> hyper::Response<Full<Bytes>> {
76 let mut builder = hyper::Response::builder().status(self.status);
77
78 for (name, value) in self.headers {
79 builder = builder.header(name, value);
80 }
81
82 builder.body(Full::new(Bytes::from(self.body))).unwrap()
83 }
84}
85
86impl Default for HttpResponse {
87 fn default() -> Self {
88 Self::new()
89 }
90}
91
92pub trait ResponseExt {
94 fn status(self, code: u16) -> Self;
95 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self;
96}
97
98impl ResponseExt for Response {
99 fn status(self, code: u16) -> Self {
100 self.map(|r| r.status(code))
101 }
102
103 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self {
104 self.map(|r| r.header(name, value))
105 }
106}
107
108pub struct Redirect {
110 location: String,
111 query_params: Vec<(String, String)>,
112 status: u16,
113}
114
115impl Redirect {
116 pub fn to(path: impl Into<String>) -> Self {
118 Self {
119 location: path.into(),
120 query_params: Vec::new(),
121 status: 302,
122 }
123 }
124
125 pub fn route(name: &str) -> RedirectRouteBuilder {
127 RedirectRouteBuilder {
128 name: name.to_string(),
129 params: std::collections::HashMap::new(),
130 query_params: Vec::new(),
131 status: 302,
132 }
133 }
134
135 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
137 self.query_params.push((key.to_string(), value.into()));
138 self
139 }
140
141 pub fn permanent(mut self) -> Self {
143 self.status = 301;
144 self
145 }
146
147 fn build_url(&self) -> String {
148 if self.query_params.is_empty() {
149 self.location.clone()
150 } else {
151 let query = self
152 .query_params
153 .iter()
154 .map(|(k, v)| format!("{}={}", k, v))
155 .collect::<Vec<_>>()
156 .join("&");
157 format!("{}?{}", self.location, query)
158 }
159 }
160}
161
162impl From<Redirect> for Response {
164 fn from(redirect: Redirect) -> Response {
165 Ok(HttpResponse::new()
166 .status(redirect.status)
167 .header("Location", redirect.build_url()))
168 }
169}
170
171pub struct RedirectRouteBuilder {
173 name: String,
174 params: std::collections::HashMap<String, String>,
175 query_params: Vec<(String, String)>,
176 status: u16,
177}
178
179impl RedirectRouteBuilder {
180 pub fn with(mut self, key: &str, value: impl Into<String>) -> Self {
182 self.params.insert(key.to_string(), value.into());
183 self
184 }
185
186 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
188 self.query_params.push((key.to_string(), value.into()));
189 self
190 }
191
192 pub fn permanent(mut self) -> Self {
194 self.status = 301;
195 self
196 }
197
198 fn build_url(&self) -> Option<String> {
199 use crate::routing::route_with_params;
200
201 let mut url = route_with_params(&self.name, &self.params)?;
202 if !self.query_params.is_empty() {
203 let query = self
204 .query_params
205 .iter()
206 .map(|(k, v)| format!("{}={}", k, v))
207 .collect::<Vec<_>>()
208 .join("&");
209 url = format!("{}?{}", url, query);
210 }
211 Some(url)
212 }
213}
214
215impl From<RedirectRouteBuilder> for Response {
217 fn from(redirect: RedirectRouteBuilder) -> Response {
218 let url = redirect.build_url().ok_or_else(|| {
219 HttpResponse::text(format!("Route '{}' not found", redirect.name)).status(500)
220 })?;
221 Ok(HttpResponse::new()
222 .status(redirect.status)
223 .header("Location", url))
224 }
225}
226
227impl From<crate::error::FrameworkError> for HttpResponse {
232 fn from(err: crate::error::FrameworkError) -> HttpResponse {
233 let status = err.status_code();
234 let body = match &err {
235 crate::error::FrameworkError::ParamError { param_name } => {
236 serde_json::json!({
237 "error": format!("Missing required parameter: {}", param_name)
238 })
239 }
240 crate::error::FrameworkError::ValidationError { field, message } => {
241 serde_json::json!({
242 "error": "Validation failed",
243 "field": field,
244 "message": message
245 })
246 }
247 crate::error::FrameworkError::Validation(errors) => {
248 errors.to_json()
250 }
251 crate::error::FrameworkError::Unauthorized => {
252 serde_json::json!({
253 "message": "This action is unauthorized."
254 })
255 }
256 _ => {
257 serde_json::json!({
258 "error": err.to_string()
259 })
260 }
261 };
262 HttpResponse::json(body).status(status)
263 }
264}
265
266impl From<crate::error::AppError> for HttpResponse {
270 fn from(err: crate::error::AppError) -> HttpResponse {
271 let framework_err: crate::error::FrameworkError = err.into();
273 framework_err.into()
274 }
275}