1use bytes::Bytes;
10use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
11use rainier_support::{Error, Extensions};
12use serde::Serialize;
13
14use crate::body::Body;
15use crate::cookie::Cookie;
16
17#[derive(Debug)]
19pub struct Response {
20 status: StatusCode,
21 headers: HeaderMap,
22 body: Body,
23 extensions: Extensions,
24}
25
26impl Response {
27 pub fn new(status: StatusCode) -> Self {
29 Self { status, headers: HeaderMap::new(), body: Body::Empty, extensions: Extensions::new() }
30 }
31
32 pub fn ok(body: impl Into<Body>) -> Self {
34 Self::new(StatusCode::OK).with_body(body)
35 }
36
37 pub fn no_content() -> Self {
39 Self::new(StatusCode::NO_CONTENT)
40 }
41
42 pub fn text(body: impl Into<String>) -> Self {
44 Self::ok(body.into()).with_content_type("text/plain; charset=utf-8")
45 }
46
47 pub fn html(body: impl Into<String>) -> Self {
49 Self::ok(body.into()).with_content_type("text/html; charset=utf-8")
50 }
51
52 pub fn json(value: &impl Serialize) -> Self {
58 match serde_json::to_vec(value) {
59 Ok(encoded) => Self::ok(encoded).with_content_type("application/json; charset=utf-8"),
60 Err(e) => Error::internal(format!("could not serialise the response: {e}")).into(),
61 }
62 }
63
64 pub fn created(location: impl AsRef<str>) -> Self {
66 Self::new(StatusCode::CREATED).with_header("location", location.as_ref())
67 }
68
69 pub fn download(bytes: impl Into<Bytes>, file_name: &str) -> Self {
71 let safe: String =
74 file_name.chars().filter(|c| *c != '"' && *c != '\r' && *c != '\n').collect();
75 Self::ok(bytes.into())
76 .with_content_type("application/octet-stream")
77 .with_header("content-disposition", &format!("attachment; filename=\"{safe}\""))
78 }
79
80 pub fn stream<S>(stream: S) -> Self
82 where
83 S: futures_core::Stream<Item = Result<Bytes, Error>> + Send + 'static,
84 {
85 Self::new(StatusCode::OK).with_body(Body::from_stream(stream))
86 }
87
88 pub fn status(&self) -> StatusCode {
92 self.status
93 }
94
95 pub fn headers(&self) -> &HeaderMap {
97 &self.headers
98 }
99
100 pub fn headers_mut(&mut self) -> &mut HeaderMap {
102 &mut self.headers
103 }
104
105 pub fn header(&self, name: &str) -> Option<&str> {
107 self.headers.get(name)?.to_str().ok()
108 }
109
110 pub fn body(&self) -> &Body {
112 &self.body
113 }
114
115 pub fn take_body(&mut self) -> Body {
117 std::mem::take(&mut self.body)
118 }
119
120 pub fn extensions(&self) -> &Extensions {
122 &self.extensions
123 }
124
125 pub fn extensions_mut(&mut self) -> &mut Extensions {
127 &mut self.extensions
128 }
129
130 pub fn is_successful(&self) -> bool {
132 self.status.is_success()
133 }
134
135 pub fn with_status(mut self, status: StatusCode) -> Self {
139 self.status = status;
140 self
141 }
142
143 pub fn with_body(mut self, body: impl Into<Body>) -> Self {
145 self.body = body.into();
146 self
147 }
148
149 pub fn with_header(mut self, name: &str, value: &str) -> Self {
155 if let (Ok(name), Ok(value)) = (HeaderName::try_from(name), HeaderValue::from_str(value)) {
156 self.headers.insert(name, value);
157 }
158 self
159 }
160
161 pub fn with_added_header(mut self, name: &str, value: &str) -> Self {
163 if let (Ok(name), Ok(value)) = (HeaderName::try_from(name), HeaderValue::from_str(value)) {
164 self.headers.append(name, value);
165 }
166 self
167 }
168
169 pub fn with_content_type(self, content_type: &str) -> Self {
171 self.with_header("content-type", content_type)
172 }
173
174 pub fn with_cookie(self, cookie: &Cookie) -> Self {
176 self.with_added_header("set-cookie", &cookie.to_set_cookie())
177 }
178
179 pub fn without_cookie(self, name: &str) -> Self {
181 self.with_cookie(&Cookie::removal(name))
182 }
183
184 pub async fn into_bytes(self) -> Result<Bytes, Error> {
189 self.body.collect().await
190 }
191
192 pub async fn into_string(self) -> Result<String, Error> {
201 Ok(String::from_utf8_lossy(&self.into_bytes().await?).into_owned())
202 }
203
204 pub async fn into_json<T: serde::de::DeserializeOwned>(self) -> Result<T, Error> {
206 let bytes = self.into_bytes().await?;
207
208 serde_json::from_slice(&bytes).map_err(|e| {
209 let body = String::from_utf8_lossy(&bytes);
213 Error::internal(format!(
214 "the response body is not the JSON that was expected: {e} — body was: {}",
215 body.chars().take(400).collect::<String>()
216 ))
217 })
218 }
219
220 pub fn into_http(self) -> http::Response<Body> {
222 let mut builder = http::Response::builder().status(self.status);
223 if let Some(headers) = builder.headers_mut() {
224 *headers = self.headers;
225 }
226 builder.body(self.body).unwrap_or_else(|_| {
227 http::Response::new(Body::Empty)
230 })
231 }
232}
233
234impl Default for Response {
235 fn default() -> Self {
236 Self::new(StatusCode::OK)
237 }
238}
239
240#[derive(Debug, Clone)]
242pub struct Redirect {
243 status: StatusCode,
244 location: String,
245}
246
247impl Redirect {
248 pub fn to(location: impl Into<String>) -> Self {
250 Self { status: StatusCode::FOUND, location: location.into() }
251 }
252
253 pub fn permanent(location: impl Into<String>) -> Self {
255 Self { status: StatusCode::MOVED_PERMANENTLY, location: location.into() }
256 }
257
258 pub fn see_other(location: impl Into<String>) -> Self {
261 Self { status: StatusCode::SEE_OTHER, location: location.into() }
262 }
263
264 pub fn temporary(location: impl Into<String>) -> Self {
266 Self { status: StatusCode::TEMPORARY_REDIRECT, location: location.into() }
267 }
268}
269
270#[derive(Debug, Clone)]
272pub struct Html<T>(pub T);
273
274#[derive(Debug, Clone)]
276pub struct Json<T>(pub T);
277
278pub trait IntoResponse {
280 fn into_response(self) -> Response;
282}
283
284impl IntoResponse for Response {
285 fn into_response(self) -> Response {
286 self
287 }
288}
289
290impl IntoResponse for () {
291 fn into_response(self) -> Response {
292 Response::no_content()
293 }
294}
295
296impl IntoResponse for StatusCode {
297 fn into_response(self) -> Response {
298 Response::new(self)
299 }
300}
301
302impl IntoResponse for String {
303 fn into_response(self) -> Response {
304 Response::text(self)
305 }
306}
307
308impl IntoResponse for &'static str {
309 fn into_response(self) -> Response {
310 Response::text(self)
311 }
312}
313
314impl IntoResponse for serde_json::Value {
315 fn into_response(self) -> Response {
316 Response::json(&self)
317 }
318}
319
320impl<T: Serialize> IntoResponse for Json<T> {
321 fn into_response(self) -> Response {
322 Response::json(&self.0)
323 }
324}
325
326impl<T: Into<String>> IntoResponse for Html<T> {
327 fn into_response(self) -> Response {
328 Response::html(self.0)
329 }
330}
331
332impl IntoResponse for Redirect {
333 fn into_response(self) -> Response {
334 Response::new(self.status).with_header("location", &self.location)
335 }
336}
337
338impl IntoResponse for Body {
339 fn into_response(self) -> Response {
340 Response::ok(self)
341 }
342}
343
344impl<T: IntoResponse> IntoResponse for (StatusCode, T) {
345 fn into_response(self) -> Response {
346 let (status, inner) = self;
347 inner.into_response().with_status(status)
348 }
349}
350
351impl<T: IntoResponse> IntoResponse for Option<T> {
352 fn into_response(self) -> Response {
355 match self {
356 Some(inner) => inner.into_response(),
357 None => Error::not_found("Not Found").into_response(),
358 }
359 }
360}
361
362impl<T, E> IntoResponse for Result<T, E>
363where
364 T: IntoResponse,
365 E: IntoResponse,
366{
367 fn into_response(self) -> Response {
368 match self {
369 Ok(inner) => inner.into_response(),
370 Err(e) => e.into_response(),
371 }
372 }
373}
374
375#[derive(Debug, Clone)]
383pub struct RenderedError {
384 pub status: u16,
386 pub message: String,
388 pub details: Option<serde_json::Value>,
390 pub disclosable: bool,
396}
397
398impl IntoResponse for Error {
404 fn into_response(self) -> Response {
405 let status =
406 StatusCode::from_u16(self.status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
407 let disclosable = !self.kind().is_server_error();
408
409 let mut payload = serde_json::Map::new();
410 payload.insert("message".into(), serde_json::Value::String(self.message().to_string()));
411 if let Some(details) = self.details() {
412 payload.insert("errors".into(), details.clone());
413 }
414
415 let mut response = Response::json(&serde_json::Value::Object(payload)).with_status(status);
416
417 response.extensions_mut().insert(RenderedError {
418 status: self.status(),
419 message: self.message().to_string(),
420 details: self.details().cloned(),
421 disclosable,
422 });
423 response
424 }
425}
426
427impl From<Error> for Response {
428 fn from(error: Error) -> Self {
429 error.into_response()
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use serde_json::json;
437
438 async fn body_of(response: Response) -> String {
439 String::from_utf8(response.body.collect().await.unwrap().to_vec()).unwrap()
440 }
441
442 #[tokio::test]
443 async fn builds_the_common_shapes() {
444 let text = Response::text("hi");
445 assert_eq!(text.status(), StatusCode::OK);
446 assert_eq!(text.header("content-type"), Some("text/plain; charset=utf-8"));
447 assert_eq!(body_of(text).await, "hi");
448
449 let html = Response::html("<p>hi</p>");
450 assert_eq!(html.header("content-type"), Some("text/html; charset=utf-8"));
451
452 let json = Response::json(&json!({ "a": 1 }));
453 assert_eq!(json.header("content-type"), Some("application/json; charset=utf-8"));
454 assert_eq!(body_of(json).await, r#"{"a":1}"#);
455
456 assert_eq!(Response::no_content().status(), StatusCode::NO_CONTENT);
457 assert_eq!(Response::created("/posts/1").header("location"), Some("/posts/1"));
458 }
459
460 #[test]
461 fn headers_replace_or_append() {
462 let response = Response::ok(()).with_header("x-a", "1").with_header("x-a", "2");
463 assert_eq!(response.headers().get_all("x-a").iter().count(), 1);
464 assert_eq!(response.header("x-a"), Some("2"));
465
466 let response = Response::ok(()).with_added_header("x-b", "1").with_added_header("x-b", "2");
467 assert_eq!(response.headers().get_all("x-b").iter().count(), 2);
468 }
469
470 #[test]
471 fn an_invalid_header_is_dropped_rather_than_panicking() {
472 let response = Response::ok(()).with_header("bad header name", "v");
473 assert!(response.headers().is_empty());
474
475 let response = Response::ok(()).with_header("x-ok", "line\nbreak");
476 assert!(response.headers().is_empty());
477 }
478
479 #[test]
480 fn cookies_stack_up_as_separate_headers() {
481 let response = Response::ok(())
482 .with_cookie(&Cookie::new("a", "1"))
483 .with_cookie(&Cookie::new("b", "2"))
484 .without_cookie("c");
485
486 assert_eq!(response.headers().get_all("set-cookie").iter().count(), 3);
487 }
488
489 #[test]
490 fn downloads_escape_the_file_name() {
491 let response = Response::download("data", "re\"port\r\n.csv");
492 assert_eq!(
493 response.header("content-disposition"),
494 Some("attachment; filename=\"report.csv\"")
495 );
496 }
497
498 #[tokio::test]
499 async fn into_response_covers_the_handler_return_types() {
500 assert_eq!(().into_response().status(), StatusCode::NO_CONTENT);
501 assert_eq!("hi".into_response().status(), StatusCode::OK);
502 assert_eq!(String::from("hi").into_response().status(), StatusCode::OK);
503 assert_eq!(StatusCode::IM_A_TEAPOT.into_response().status(), StatusCode::IM_A_TEAPOT);
504
505 let tuple = (StatusCode::ACCEPTED, "queued").into_response();
506 assert_eq!(tuple.status(), StatusCode::ACCEPTED);
507 assert_eq!(body_of(tuple).await, "queued");
508
509 let json = Json(json!({ "ok": true })).into_response();
510 assert_eq!(body_of(json).await, r#"{"ok":true}"#);
511
512 let html = Html("<b>x</b>").into_response();
513 assert_eq!(html.header("content-type"), Some("text/html; charset=utf-8"));
514 }
515
516 #[test]
517 fn options_and_results_carry_their_failure() {
518 let none: Option<&'static str> = None;
519 assert_eq!(none.into_response().status(), StatusCode::NOT_FOUND);
520 assert_eq!(Some("x").into_response().status(), StatusCode::OK);
521
522 let ok: Result<&'static str, Error> = Ok("x");
523 assert_eq!(ok.into_response().status(), StatusCode::OK);
524
525 let err: Result<&'static str, Error> = Err(Error::not_found("no post"));
526 assert_eq!(err.into_response().status(), StatusCode::NOT_FOUND);
527 }
528
529 #[tokio::test]
530 async fn errors_render_as_json_with_their_details() {
531 let error = Error::validation(json!({ "email": ["is required"] }));
532 let response = error.into_response();
533
534 assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY);
535 let body: serde_json::Value = serde_json::from_str(&body_of(response).await).unwrap();
536 assert_eq!(body["message"], "The given data was invalid.");
537 assert_eq!(body["errors"]["email"][0], "is required");
538 }
539
540 #[test]
541 fn redirects_set_the_location_and_status() {
542 assert_eq!(Redirect::to("/a").into_response().status(), StatusCode::FOUND);
543 assert_eq!(
544 Redirect::permanent("/a").into_response().status(),
545 StatusCode::MOVED_PERMANENTLY
546 );
547 assert_eq!(Redirect::see_other("/a").into_response().status(), StatusCode::SEE_OTHER);
548 assert_eq!(
549 Redirect::temporary("/a").into_response().status(),
550 StatusCode::TEMPORARY_REDIRECT
551 );
552 assert_eq!(Redirect::to("/dash").into_response().header("location"), Some("/dash"));
553 }
554
555 #[tokio::test]
556 async fn converts_into_an_http_response() {
557 let response = Response::text("body").with_header("x-a", "1").into_http();
558 assert_eq!(response.status(), StatusCode::OK);
559 assert_eq!(response.headers().get("x-a").unwrap(), "1");
560 }
561}