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.body(Full::new(Bytes::from(self.body))).unwrap()
67 }
68}
69
70impl Default for HttpResponse {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76pub trait ResponseExt {
78 fn status(self, code: u16) -> Self;
79 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self;
80}
81
82impl ResponseExt for Response {
83 fn status(self, code: u16) -> Self {
84 self.map(|r| r.status(code))
85 }
86
87 fn header(self, name: impl Into<String>, value: impl Into<String>) -> Self {
88 self.map(|r| r.header(name, value))
89 }
90}
91
92pub struct Redirect {
94 location: String,
95 query_params: Vec<(String, String)>,
96 status: u16,
97}
98
99impl Redirect {
100 pub fn to(path: impl Into<String>) -> Self {
102 Self {
103 location: path.into(),
104 query_params: Vec::new(),
105 status: 302,
106 }
107 }
108
109 pub fn route(name: &str) -> RedirectRouteBuilder {
111 RedirectRouteBuilder {
112 name: name.to_string(),
113 params: std::collections::HashMap::new(),
114 query_params: Vec::new(),
115 status: 302,
116 }
117 }
118
119 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
121 self.query_params.push((key.to_string(), value.into()));
122 self
123 }
124
125 pub fn permanent(mut self) -> Self {
127 self.status = 301;
128 self
129 }
130
131 fn build_url(&self) -> String {
132 if self.query_params.is_empty() {
133 self.location.clone()
134 } else {
135 let query = self
136 .query_params
137 .iter()
138 .map(|(k, v)| format!("{}={}", k, v))
139 .collect::<Vec<_>>()
140 .join("&");
141 format!("{}?{}", self.location, query)
142 }
143 }
144}
145
146impl From<Redirect> for Response {
148 fn from(redirect: Redirect) -> Response {
149 Ok(HttpResponse::new()
150 .status(redirect.status)
151 .header("Location", redirect.build_url()))
152 }
153}
154
155pub struct RedirectRouteBuilder {
157 name: String,
158 params: std::collections::HashMap<String, String>,
159 query_params: Vec<(String, String)>,
160 status: u16,
161}
162
163impl RedirectRouteBuilder {
164 pub fn with(mut self, key: &str, value: impl Into<String>) -> Self {
166 self.params.insert(key.to_string(), value.into());
167 self
168 }
169
170 pub fn query(mut self, key: &str, value: impl Into<String>) -> Self {
172 self.query_params.push((key.to_string(), value.into()));
173 self
174 }
175
176 pub fn permanent(mut self) -> Self {
178 self.status = 301;
179 self
180 }
181
182 fn build_url(&self) -> Option<String> {
183 use crate::routing::route_with_params;
184
185 let mut url = route_with_params(&self.name, &self.params)?;
186 if !self.query_params.is_empty() {
187 let query = self
188 .query_params
189 .iter()
190 .map(|(k, v)| format!("{}={}", k, v))
191 .collect::<Vec<_>>()
192 .join("&");
193 url = format!("{}?{}", url, query);
194 }
195 Some(url)
196 }
197}
198
199impl From<RedirectRouteBuilder> for Response {
201 fn from(redirect: RedirectRouteBuilder) -> Response {
202 let url = redirect.build_url().ok_or_else(|| {
203 HttpResponse::text(format!("Route '{}' not found", redirect.name)).status(500)
204 })?;
205 Ok(HttpResponse::new()
206 .status(redirect.status)
207 .header("Location", url))
208 }
209}
210
211impl From<crate::error::FrameworkError> for HttpResponse {
216 fn from(err: crate::error::FrameworkError) -> HttpResponse {
217 let status = err.status_code();
218 let body = match &err {
219 crate::error::FrameworkError::ParamError { param_name } => {
220 serde_json::json!({
221 "error": format!("Missing required parameter: {}", param_name)
222 })
223 }
224 crate::error::FrameworkError::ValidationError { field, message } => {
225 serde_json::json!({
226 "error": "Validation failed",
227 "field": field,
228 "message": message
229 })
230 }
231 crate::error::FrameworkError::Validation(errors) => {
232 errors.to_json()
234 }
235 crate::error::FrameworkError::Unauthorized => {
236 serde_json::json!({
237 "message": "This action is unauthorized."
238 })
239 }
240 _ => {
241 serde_json::json!({
242 "error": err.to_string()
243 })
244 }
245 };
246 HttpResponse::json(body).status(status)
247 }
248}
249
250impl From<crate::error::AppError> for HttpResponse {
254 fn from(err: crate::error::AppError) -> HttpResponse {
255 let framework_err: crate::error::FrameworkError = err.into();
257 framework_err.into()
258 }
259}