pavex/response/
response_.rs

1use bytes::Bytes;
2use http::header::CONTENT_TYPE;
3use http_body_util::Empty;
4
5use crate::http::StatusCode;
6use crate::http::{HeaderMap, Version};
7
8use super::ResponseBody;
9use super::body::TypedBody;
10use super::body::raw::RawBody;
11
12/// Represents an HTTP response.
13///
14/// ```rust
15/// use pavex::Response;
16/// use pavex::http::{HeaderValue, header::SERVER};
17///
18/// // Create a new response with:
19/// // - status code `OK`
20/// // - HTTP version `HTTP/1.1`
21/// // - the `Server` header set to `Pavex`
22/// // - the `Content-Type` header set to `text/plain; charset=utf-8`
23/// // - the body set to `Hello, world!`
24/// let response = Response::ok()
25///     .insert_header(SERVER, HeaderValue::from_static("Pavex"))
26///     .set_typed_body("Hello, world!");
27/// ```
28///
29/// The response is composed of a head ([`ResponseHead`]) and an optional body.
30///
31/// Check out [`Response::new`] for details on how to build a new [`Response`].
32/// You might also want to check out the following methods to further customize
33/// your response:
34///
35/// - [`set_status`](Response::set_status) to change the status code.
36/// - [`set_version`](Response::set_version) to change the HTTP version.
37/// - [`append_header`](Response::append_header) to append a value to a header.
38/// - [`insert_header`](Response::insert_header) to upsert a header value.
39/// - [`set_typed_body`](Response::set_typed_body) to set the body and automatically set the `Content-Type` header.
40///
41/// There are other methods available on [`Response`] that you might find useful, but the
42/// ones listed above are the most commonly used and should be enough to get you started.
43#[derive(Debug)]
44pub struct Response {
45    inner: http::Response<ResponseBody>,
46}
47
48#[non_exhaustive]
49#[derive(Debug)]
50/// All the information that is transmitted as part of an HTTP [`Response`] ahead of the body.
51///
52/// It includes the status code, the HTTP version, and the headers.
53pub struct ResponseHead {
54    status: StatusCode,
55    version: Version,
56    headers: HeaderMap,
57}
58
59impl Response {
60    /// Build a new [`Response`] with the given status code.
61    /// The HTTP version is set to HTTP 1.1, there are no headers and
62    /// the body is empty.
63    ///
64    /// # Example
65    ///
66    /// ```rust
67    /// use pavex::http::StatusCode;
68    /// use pavex::Response;
69    ///
70    /// let response = Response::new(StatusCode::OK);
71    /// ```
72    ///
73    /// # Alternatives
74    ///
75    /// Pavex's provides a set of shorthands for building a new [`Response`] using
76    /// well-known status code. For example, the following code is equivalent to the
77    /// example above:
78    ///
79    /// ```rust
80    /// use pavex::Response;
81    ///
82    /// let response = Response::ok();
83    /// ```
84    ///
85    /// Check out [`Response`]'s API documentation for a complete list of all
86    /// the supported shorthands.
87    pub fn new(status_code: StatusCode) -> Self {
88        let inner = http::Response::new(ResponseBody::new(Empty::new()));
89        Self { inner }.set_status(status_code)
90    }
91}
92
93impl Response {
94    /// Change the status code of the [`Response`].
95    ///
96    /// # Example
97    ///
98    /// ```rust
99    /// use pavex::http::StatusCode;
100    /// use pavex::Response;
101    ///
102    /// let mut response = Response::ok();
103    /// assert_eq!(response.status(), StatusCode::OK);
104    ///
105    /// // Change the status code to `CREATED`.
106    /// response = response.set_status(StatusCode::CREATED);
107    /// assert_eq!(response.status(), StatusCode::CREATED);
108    /// ```
109    pub fn set_status(mut self, status: StatusCode) -> Self {
110        *self.inner.status_mut() = status;
111        self
112    }
113
114    /// Get a mutable reference to the [`Response`] status.
115    ///
116    /// # Example
117    ///
118    /// ```rust
119    /// use pavex::http::StatusCode;
120    /// use pavex::Response;
121    /// use pavex::http::header::CONTENT_TYPE;
122    ///
123    /// let mut response = Response::ok();
124    ///
125    /// assert_eq!(response.status(), StatusCode::OK);
126    ///
127    /// // Get a mutable reference to the status.
128    /// let status = response.status_mut();
129    ///
130    /// // Change the Status
131    /// *status = StatusCode::NOT_FOUND;
132    ///
133    /// assert_eq!(response.status(), StatusCode::NOT_FOUND);
134    /// ```
135    pub fn status_mut(&mut self) -> &mut StatusCode {
136        self.inner.status_mut()
137    }
138
139    /// Change the HTTP version of the [`Response`].
140    ///
141    /// # Example
142    ///
143    /// ```rust
144    /// use pavex::http::Version;
145    /// use pavex::Response;
146    ///
147    /// let mut response = Response::ok();
148    /// // By default, the HTTP version is HTTP/1.1.
149    /// assert_eq!(response.version(), Version::HTTP_11);
150    ///
151    /// // Change the HTTP version to HTTP/2.
152    /// response = response.set_version(Version::HTTP_2);
153    /// assert_eq!(response.version(), Version::HTTP_2);
154    /// ```
155    pub fn set_version(mut self, version: Version) -> Self {
156        *self.inner.version_mut() = version;
157        self
158    }
159
160    /// Append a value to a [`Response`] header.
161    ///
162    /// If the header is not present, it is added with the given value.
163    /// If the header is present, the value is appended to the end
164    /// of the comma-separated list of existing values for that header.
165    ///
166    /// # Example
167    ///
168    /// ```rust
169    /// use pavex::http::{header::HOST, HeaderValue};
170    /// use pavex::Response;
171    ///
172    /// let mut response = Response::ok();
173    /// assert!(response.headers().get("host").is_none());
174    ///
175    /// // Append a value to the `host` header.
176    /// let value = HeaderValue::from_static("world");
177    /// response = response.append_header(HOST, value);
178    ///
179    /// let headers: Vec<_> = response.headers().get_all("host").iter().collect();
180    /// assert_eq!(headers.len(), 1);
181    /// assert_eq!(headers[0], "world");
182    ///
183    /// // Append another value to the `host` header.
184    /// let value = HeaderValue::from_static("earth");
185    /// response = response.append_header(HOST, value);
186    ///
187    /// let headers: Vec<_> = response.headers().get_all("host").iter().collect();
188    /// assert_eq!(headers.len(), 2);
189    /// assert_eq!(headers[0], "world");
190    /// assert_eq!(headers[1], "earth");
191    /// ```
192    ///
193    /// # Alternatives
194    ///
195    /// If you want to replace the value of a header instead of appending to it,
196    /// use [`insert_header`](Response::insert_header) instead.
197    pub fn append_header(
198        mut self,
199        key: crate::http::HeaderName,
200        value: crate::http::HeaderValue,
201    ) -> Self {
202        self.inner.headers_mut().append(key, value);
203        self
204    }
205
206    /// Insert a header value into the [`Response`].
207    ///
208    /// If the header key is not present, it is added with the given value.
209    /// If the header key is present, its value is replaced with the given value.
210    ///
211    /// # Example
212    ///
213    /// ```rust
214    /// use pavex::http::{header::HOST, HeaderValue};
215    /// use pavex::Response;
216    ///
217    /// let mut response = Response::ok();
218    /// assert!(response.headers().get("host").is_none());
219    ///
220    /// // Insert a value into the `host` header.
221    /// let value = HeaderValue::from_static("world");
222    /// response = response.insert_header(HOST, value);
223    ///
224    /// let headers: Vec<_> = response.headers().get_all("host").iter().collect();
225    /// assert_eq!(headers.len(), 1);
226    /// assert_eq!(headers[0], "world");
227    ///
228    /// // Insert another value into the `host` header.
229    /// let value = HeaderValue::from_static("earth");
230    /// response = response.insert_header(HOST, value);
231    ///
232    /// let headers: Vec<_> = response.headers().get_all("host").iter().collect();
233    /// assert_eq!(headers.len(), 1);
234    /// assert_eq!(headers[0], "earth");
235    /// ```
236    ///
237    /// # Alternatives
238    ///
239    /// If you want to append to the current header value instead of replacing it,
240    /// use [`append_header`](Response::append_header) instead.
241    pub fn insert_header(
242        mut self,
243        key: crate::http::HeaderName,
244        value: crate::http::HeaderValue,
245    ) -> Self {
246        self.inner.headers_mut().insert(key, value);
247        self
248    }
249
250    /// Set the [`Response`] body.
251    ///
252    /// The provided body must implement the [`TypedBody`] trait.
253    /// The `Content-Type` header is automatically set to the value returned
254    /// by [`TypedBody::content_type`].
255    ///
256    /// If a body is already set, it is replaced.
257    ///
258    /// # Example
259    ///
260    /// ```rust
261    /// use pavex::{Response, response::body::Html};
262    /// use pavex::http::header::CONTENT_TYPE;
263    ///
264    /// let typed_body = "Hello, world!";
265    /// let response = Response::ok().set_typed_body(typed_body);
266    ///
267    /// // The `Content-Type` header is set automatically
268    /// // when using `set_typed_body`.
269    /// assert_eq!(response.headers()[CONTENT_TYPE], "text/plain; charset=utf-8");
270    /// ```
271    ///
272    /// # Built-in `TypedBody` implementations
273    ///
274    /// Pavex provides several implementations of [`TypedBody`] out of the box,
275    /// to cover the most common use cases:
276    ///
277    /// - [`String`], [`&'static str`](std::primitive::str)
278    ///   and [`Cow<'static, str>`](std::borrow::Cow) for `text/plain; charset=utf-8` responses.
279    /// - [`Vec<u8>`], [`&'static [u8]`](std::primitive::u8),
280    ///   [`Cow<'static, [u8]>`](std::borrow::Cow) and [`Bytes`] for `application/octet-stream` responses.
281    /// - [`Json`](crate::response::body::Json) for `application/json` responses.
282    /// - [`Html`](crate::response::body::Html) for `text/html; charset=utf-8` responses.
283    ///
284    /// Check out the [`body`](super::body) sub-module for an exhaustive list.
285    ///
286    /// # Raw body
287    ///
288    /// If you don't want Pavex to automatically set the `Content-Type` header,
289    /// you might want to use [`Response::set_raw_body`] instead.
290    pub fn set_typed_body<NewBody>(self, body: NewBody) -> Response
291    where
292        NewBody: TypedBody,
293        <<NewBody as TypedBody>::Body as RawBody>::Error:
294            Into<Box<dyn std::error::Error + Send + Sync>>,
295    {
296        let (mut head, _) = self.inner.into_parts();
297        head.headers.insert(CONTENT_TYPE, body.content_type());
298        http::Response::from_parts(head, ResponseBody::new(body.body())).into()
299    }
300
301    /// Set the body of the [`Response`] to the given value, without setting
302    /// the `Content-Type` header.
303    ///
304    /// This method should only be used if you need fine-grained control over
305    /// the `Content-Type` header or the body type. In all other circumstances, use
306    /// [`set_typed_body`](Response::set_typed_body).
307    ///
308    /// # Example
309    ///
310    /// ```rust
311    /// use pavex::Response;
312    /// use pavex::response::body::raw::{Bytes, Full};
313    /// use pavex::http::header::CONTENT_TYPE;
314    ///
315    /// let raw_body: Full<Bytes> = Full::new("Hello, world!".into());
316    /// let response = Response::ok().set_raw_body(raw_body);
317    ///
318    /// // The `Content-Type` header is not set automatically
319    /// // when using `set_raw_body`.
320    /// assert_eq!(response.headers().get(CONTENT_TYPE), None);
321    /// ```
322    pub fn set_raw_body<NewBody>(self, body: NewBody) -> Response
323    where
324        NewBody: RawBody<Data = Bytes> + Send + 'static,
325        <NewBody as RawBody>::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
326    {
327        let (head, _) = self.inner.into_parts();
328        http::Response::from_parts(head, ResponseBody::new(body)).into()
329    }
330
331    /// Get a mutable reference to the [`Response`] body.
332    pub fn body_mut(&mut self) -> &mut ResponseBody {
333        self.inner.body_mut()
334    }
335
336    /// Get a mutable reference to the [`Response`] headers.
337    ///
338    /// # Example
339    ///
340    /// ```rust
341    /// use pavex::Response;
342    /// use pavex::http::{header::CONTENT_TYPE, HeaderValue};
343    /// use mime::TEXT_PLAIN_UTF_8;
344    ///
345    /// let mut response = Response::ok();
346    ///
347    /// // Get a mutable reference to the headers.
348    /// let headers = response.headers_mut();
349    ///
350    /// // Insert a header.
351    /// let value = HeaderValue::from_static(TEXT_PLAIN_UTF_8.as_ref());
352    /// headers.insert(CONTENT_TYPE, value);
353    ///
354    /// assert_eq!(headers.len(), 1);
355    ///
356    /// // Remove a header.
357    /// headers.remove(CONTENT_TYPE);
358    ///
359    /// assert!(headers.is_empty());
360    /// ```
361    pub fn headers_mut(&mut self) -> &mut crate::http::HeaderMap {
362        self.inner.headers_mut()
363    }
364}
365
366impl Response {
367    /// Get a reference to the [`Response`] status code.
368    ///
369    /// # Example
370    ///
371    /// ```rust
372    /// use pavex::{http::StatusCode, Response};
373    ///
374    /// let response = Response::bad_request();
375    /// assert_eq!(response.status(), StatusCode::BAD_REQUEST);
376    /// ```
377    ///
378    /// # Mutation
379    ///
380    /// Check out [`Response::set_status`] if you need to modify the
381    /// status code of the [`Response`].
382    pub fn status(&self) -> StatusCode {
383        self.inner.status()
384    }
385
386    /// Get a reference to the version of the HTTP protocol used by the [`Response`].
387    ///
388    /// # Example
389    ///
390    /// ```rust
391    /// use pavex::http::Version;
392    /// use pavex::Response;
393    ///
394    /// let mut response = Response::ok();
395    /// // By default, the HTTP version is HTTP/1.1.
396    /// assert_eq!(response.version(), Version::HTTP_11);
397    /// ```
398    ///
399    /// # Mutation
400    ///
401    /// Check out [`Response::set_version`] if you need to modify the
402    /// HTTP protocol version used by the [`Response`].
403    pub fn version(&self) -> crate::http::Version {
404        self.inner.version()
405    }
406
407    /// Get a reference to the [`Response`] headers.
408    ///
409    /// # Example
410    ///
411    /// ```rust
412    /// use pavex::http::{header::{HOST, SERVER}, HeaderValue};
413    /// use pavex::Response;
414    ///
415    /// let response = Response::ok()
416    ///     .append_header(HOST, HeaderValue::from_static("world"))
417    ///     .append_header(HOST, HeaderValue::from_static("earth"))
418    ///     .insert_header(SERVER, HeaderValue::from_static("Pavex"));
419    ///
420    /// let headers = response.headers();
421    /// assert_eq!(headers.len(), 3);
422    ///
423    /// let host_values: Vec<_> = response.headers().get_all("host").iter().collect();
424    /// assert_eq!(host_values.len(), 2);
425    /// assert_eq!(host_values[0], "world");
426    /// assert_eq!(host_values[1], "earth");
427    ///
428    /// assert_eq!(headers[SERVER], "Pavex");
429    /// ```
430    ///
431    /// # Mutation
432    ///
433    /// If you need to modify the [`Response`] headers, check out:
434    ///
435    /// - [`Response::append_header`]
436    /// - [`Response::insert_header`]
437    /// - [`Response::headers_mut`]
438    pub fn headers(&self) -> &crate::http::HeaderMap {
439        self.inner.headers()
440    }
441
442    /// Get a reference to the [`Response`] body.
443    ///
444    /// # Mutation
445    ///
446    /// If you need to modify the [`Response`] body, check out:
447    ///
448    /// - [`Response::set_typed_body`]
449    /// - [`Response::set_raw_body`]
450    /// - [`Response::body_mut`]
451    pub fn body(&self) -> &ResponseBody {
452        self.inner.body()
453    }
454}
455
456impl Response {
457    /// Break down the [`Response`] into its two components: the [`ResponseHead`]
458    /// and the body.
459    ///
460    /// This method consumes the [`Response`].
461    ///
462    /// # Related
463    ///
464    /// You can use [`Response::from_parts`] to reconstruct a [`Response`] from
465    /// a [`ResponseHead`] and a body.
466    pub fn into_parts(self) -> (ResponseHead, ResponseBody) {
467        let (head, body) = self.inner.into_parts();
468        (head.into(), body)
469    }
470
471    /// Build a [`Response`] from its two components: the [`ResponseHead`]
472    /// and the body.
473    ///
474    /// # Related
475    ///
476    /// You can use [`Response::into_parts`] to decompose a [`Response`] from
477    /// a [`ResponseHead`] and a body.
478    pub fn from_parts(head: ResponseHead, body: ResponseBody) -> Self {
479        Self {
480            inner: http::Response::from_parts(head.into(), body),
481        }
482    }
483}
484
485impl<Body> From<http::Response<Body>> for Response
486where
487    Body: Send + RawBody<Data = Bytes> + 'static,
488    <Body as RawBody>::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
489{
490    fn from(inner: http::Response<Body>) -> Self {
491        let (head, body) = inner.into_parts();
492        let inner = http::Response::from_parts(head, ResponseBody::new(body));
493        Self { inner }
494    }
495}
496
497impl From<Response> for http::Response<ResponseBody> {
498    fn from(res: Response) -> Self {
499        res.inner
500    }
501}
502
503impl From<ResponseHead> for http::response::Parts {
504    fn from(head: ResponseHead) -> Self {
505        let ResponseHead {
506            status,
507            version,
508            headers,
509        } = head;
510        // Is there no better way to do create a new `Parts` instance?
511        let (mut parts, _) = http::response::Response::builder()
512            .body(Empty::<()>::new())
513            .unwrap()
514            .into_parts();
515        parts.status = status;
516        parts.version = version;
517        parts.headers = headers;
518        parts
519    }
520}
521
522impl From<http::response::Parts> for ResponseHead {
523    fn from(parts: http::response::Parts) -> Self {
524        let http::response::Parts {
525            status,
526            version,
527            headers,
528            ..
529        } = parts;
530        Self {
531            status,
532            version,
533            headers,
534        }
535    }
536}
537
538macro_rules! shorthand {
539    ($name:ident) => {
540        paste::paste! {
541            #[doc = "Start building a new [`Response`] with [`" $name "`](`StatusCode::" $name "`) as status code."]
542            pub fn [<$name:lower>]() -> Response {
543                Response::new(StatusCode::[<$name>])
544            }
545        }
546    };
547}
548
549/// Shorthand for building a new [`Response`] using a well-known status code.
550impl Response {
551    /// Start building a new [`Response`] with [`CONTINUE`](StatusCode::CONTINUE) as status code.
552    // This is special-cased because `continue` is a keyword in Rust.
553    pub fn continue_() -> Response {
554        Response::new(StatusCode::CONTINUE)
555    }
556
557    // 2xx
558    shorthand!(SWITCHING_PROTOCOLS);
559    shorthand!(PROCESSING);
560    shorthand!(OK);
561    shorthand!(CREATED);
562    shorthand!(ACCEPTED);
563    shorthand!(NON_AUTHORITATIVE_INFORMATION);
564
565    shorthand!(NO_CONTENT);
566    shorthand!(RESET_CONTENT);
567    shorthand!(PARTIAL_CONTENT);
568    shorthand!(MULTI_STATUS);
569    shorthand!(ALREADY_REPORTED);
570
571    // 3xx
572    shorthand!(MULTIPLE_CHOICES);
573    shorthand!(MOVED_PERMANENTLY);
574    shorthand!(FOUND);
575    shorthand!(SEE_OTHER);
576    shorthand!(NOT_MODIFIED);
577    shorthand!(USE_PROXY);
578    shorthand!(TEMPORARY_REDIRECT);
579    shorthand!(PERMANENT_REDIRECT);
580
581    // 4xx
582    shorthand!(BAD_REQUEST);
583    shorthand!(NOT_FOUND);
584    shorthand!(UNAUTHORIZED);
585    shorthand!(PAYMENT_REQUIRED);
586    shorthand!(FORBIDDEN);
587    shorthand!(METHOD_NOT_ALLOWED);
588    shorthand!(NOT_ACCEPTABLE);
589    shorthand!(PROXY_AUTHENTICATION_REQUIRED);
590    shorthand!(REQUEST_TIMEOUT);
591    shorthand!(CONFLICT);
592    shorthand!(GONE);
593    shorthand!(LENGTH_REQUIRED);
594    shorthand!(PRECONDITION_FAILED);
595    shorthand!(PRECONDITION_REQUIRED);
596    shorthand!(PAYLOAD_TOO_LARGE);
597    shorthand!(URI_TOO_LONG);
598    shorthand!(UNSUPPORTED_MEDIA_TYPE);
599    shorthand!(RANGE_NOT_SATISFIABLE);
600    shorthand!(EXPECTATION_FAILED);
601    shorthand!(UNPROCESSABLE_ENTITY);
602    shorthand!(TOO_MANY_REQUESTS);
603    shorthand!(REQUEST_HEADER_FIELDS_TOO_LARGE);
604    shorthand!(UNAVAILABLE_FOR_LEGAL_REASONS);
605
606    // 5xx
607    shorthand!(INTERNAL_SERVER_ERROR);
608    shorthand!(NOT_IMPLEMENTED);
609    shorthand!(BAD_GATEWAY);
610    shorthand!(SERVICE_UNAVAILABLE);
611    shorthand!(GATEWAY_TIMEOUT);
612    shorthand!(HTTP_VERSION_NOT_SUPPORTED);
613    shorthand!(VARIANT_ALSO_NEGOTIATES);
614    shorthand!(INSUFFICIENT_STORAGE);
615    shorthand!(LOOP_DETECTED);
616}