warp/
reply.rs

1//! Reply to requests.
2//!
3//! A [`Reply`](./trait.Reply.html) is a type that can be converted into an HTTP
4//! response to be sent to the client. These are typically the successful
5//! counterpart to a [rejection](../reject).
6//!
7//! The functions in this module are helpers for quickly creating a reply.
8//! Besides them, you can return a type that implements [`Reply`](./trait.Reply.html). This
9//! could be any of the following:
10//!
11//! - [`http::Response<impl Into<hyper::Body>>`](https://docs.rs/http)
12//! - `String`
13//! - `&'static str`
14//! - `http::StatusCode`
15//!
16//! # Example
17//!
18//! ```
19//! use warp::{Filter, http::Response};
20//!
21//! // Returns an empty `200 OK` response.
22//! let empty_200 = warp::any().map(warp::reply);
23//!
24//! // Returns a `200 OK` response with custom header and body.
25//! let custom = warp::any().map(|| {
26//!     Response::builder()
27//!         .header("my-custom-header", "some-value")
28//!         .body("and a custom body")
29//! });
30//!
31//! // GET requests return the empty 200, POST return the custom.
32//! let routes = warp::get().and(empty_200)
33//!     .or(warp::post().and(custom));
34//! ```
35
36use std::borrow::Cow;
37use std::convert::TryFrom;
38
39use crate::generic::{Either, One};
40use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
41use http::StatusCode;
42use hyper::Body;
43use serde::Serialize;
44
45// This re-export just looks weird in docs...
46pub(crate) use self::sealed::Reply_;
47use self::sealed::{BoxedReply, Internal};
48#[doc(hidden)]
49pub use crate::filters::reply as with;
50
51/// Response type into which types implementing the `Reply` trait are convertable.
52pub type Response = ::http::Response<Body>;
53
54/// Returns an empty `Reply` with status code `200 OK`.
55///
56/// # Example
57///
58/// ```
59/// use warp::Filter;
60///
61/// // GET /just-ok returns an empty `200 OK`.
62/// let route = warp::path("just-ok")
63///     .map(|| {
64///         println!("got a /just-ok request!");
65///         warp::reply()
66///     });
67/// ```
68#[inline]
69pub fn reply() -> impl Reply {
70    StatusCode::OK
71}
72
73/// Convert the value into a `Reply` with the value encoded as JSON.
74///
75/// The passed value must implement [`Serialize`][ser]. Many
76/// collections do, and custom domain types can have `Serialize` derived.
77///
78/// [ser]: https://serde.rs
79///
80/// # Example
81///
82/// ```
83/// use warp::Filter;
84///
85/// // GET /ids returns a `200 OK` with a JSON array of ids:
86/// // `[1, 3, 7, 13]`
87/// let route = warp::path("ids")
88///     .map(|| {
89///         let our_ids = vec![1, 3, 7, 13];
90///         warp::reply::json(&our_ids)
91///     });
92/// ```
93///
94/// # Note
95///
96/// If a type fails to be serialized into JSON, the error is logged at the
97/// `error` level, and the returned `impl Reply` will be an empty
98/// `500 Internal Server Error` response.
99pub fn json<T>(val: &T) -> Json
100where
101    T: Serialize,
102{
103    Json {
104        inner: serde_json::to_vec(val).map_err(|err| {
105            tracing::error!("reply::json error: {}", err);
106        }),
107    }
108}
109
110/// A JSON formatted reply.
111#[allow(missing_debug_implementations)]
112pub struct Json {
113    inner: Result<Vec<u8>, ()>,
114}
115
116impl Reply for Json {
117    #[inline]
118    fn into_response(self) -> Response {
119        match self.inner {
120            Ok(body) => {
121                let mut res = Response::new(body.into());
122                res.headers_mut()
123                    .insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
124                res
125            }
126            Err(()) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
127        }
128    }
129}
130
131/// Reply with a body and `content-type` set to `text/html; charset=utf-8`.
132///
133/// # Example
134///
135/// ```
136/// use warp::Filter;
137///
138/// let body = r#"
139/// <html>
140///     <head>
141///         <title>HTML with warp!</title>
142///     </head>
143///     <body>
144///         <h1>warp + HTML = &hearts;</h1>
145///     </body>
146/// </html>
147/// "#;
148///
149/// let route = warp::any()
150///     .map(move || {
151///         warp::reply::html(body)
152///     });
153/// ```
154pub fn html<T>(body: T) -> Html<T>
155where
156    Body: From<T>,
157    T: Send,
158{
159    Html { body }
160}
161
162/// An HTML reply.
163#[allow(missing_debug_implementations)]
164pub struct Html<T> {
165    body: T,
166}
167
168impl<T> Reply for Html<T>
169where
170    Body: From<T>,
171    T: Send,
172{
173    #[inline]
174    fn into_response(self) -> Response {
175        let mut res = Response::new(Body::from(self.body));
176        res.headers_mut().insert(
177            CONTENT_TYPE,
178            HeaderValue::from_static("text/html; charset=utf-8"),
179        );
180        res
181    }
182}
183
184/// Types that can be converted into a `Response`.
185///
186/// This trait is implemented for the following:
187///
188/// - `http::StatusCode`
189/// - `http::Response<impl Into<hyper::Body>>`
190/// - `String`
191/// - `&'static str`
192///
193/// # Example
194///
195/// ```rust
196/// use warp::{Filter, http::Response};
197///
198/// struct Message {
199///     msg: String
200/// }
201///
202/// impl warp::Reply for Message {
203///     fn into_response(self) -> warp::reply::Response {
204///         Response::new(format!("message: {}", self.msg).into())
205///     }
206/// }
207///
208/// fn handler() -> Message {
209///     Message { msg: "Hello".to_string() }
210/// }
211///
212/// let route = warp::any().map(handler);
213/// ```
214pub trait Reply: BoxedReply + Send {
215    /// Converts the given value into a [`Response`].
216    ///
217    /// [`Response`]: type.Response.html
218    fn into_response(self) -> Response;
219
220    /*
221    TODO: Currently unsure about having trait methods here, as it
222    requires returning an exact type, which I'd rather not commit to.
223    Additionally, it doesn't work great with `Box<Reply>`.
224
225    A possible alternative is to have wrappers, like
226
227    - `WithStatus<R: Reply>(StatusCode, R)`
228
229
230    /// Change the status code of this `Reply`.
231    fn with_status(self, status: StatusCode) -> Reply_
232    where
233        Self: Sized,
234    {
235        let mut res = self.into_response();
236        *res.status_mut() = status;
237        Reply_(res)
238    }
239
240    /// Add a header to this `Reply`.
241    ///
242    /// # Example
243    ///
244    /// ```rust
245    /// use warp::Reply;
246    ///
247    /// let reply = warp::reply()
248    ///     .with_header("x-foo", "bar");
249    /// ```
250    fn with_header<K, V>(self, name: K, value: V) -> Reply_
251    where
252        Self: Sized,
253        HeaderName: TryFrom<K>,
254        HeaderValue: TryFrom<V>,
255    {
256        match <HeaderName as TryFrom<K>>::try_from(name) {
257            Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
258                Ok(value) => {
259                    let mut res = self.into_response();
260                    res.headers_mut().append(name, value);
261                    Reply_(res)
262                },
263                Err(err) => {
264                    tracing::error!("with_header value error: {}", err.into());
265                    Reply_(::reject::server_error()
266                        .into_response())
267                }
268            },
269            Err(err) => {
270                tracing::error!("with_header name error: {}", err.into());
271                Reply_(::reject::server_error()
272                    .into_response())
273            }
274        }
275    }
276    */
277}
278
279impl<T: Reply + ?Sized> Reply for Box<T> {
280    fn into_response(self) -> Response {
281        self.boxed_into_response(Internal)
282    }
283}
284
285fn _assert_object_safe() {
286    fn _assert(_: &dyn Reply) {}
287}
288
289/// Wrap an `impl Reply` to change its `StatusCode`.
290///
291/// # Example
292///
293/// ```
294/// use warp::Filter;
295///
296/// let route = warp::any()
297///     .map(warp::reply)
298///     .map(|reply| {
299///         warp::reply::with_status(reply, warp::http::StatusCode::CREATED)
300///     });
301/// ```
302pub fn with_status<T: Reply>(reply: T, status: StatusCode) -> WithStatus<T> {
303    WithStatus { reply, status }
304}
305
306/// Wrap an `impl Reply` to change its `StatusCode`.
307///
308/// Returned by `warp::reply::with_status`.
309#[derive(Debug)]
310pub struct WithStatus<T> {
311    reply: T,
312    status: StatusCode,
313}
314
315impl<T: Reply> Reply for WithStatus<T> {
316    fn into_response(self) -> Response {
317        let mut res = self.reply.into_response();
318        *res.status_mut() = self.status;
319        res
320    }
321}
322
323/// Wrap an `impl Reply` to add a header when rendering.
324///
325/// # Example
326///
327/// ```
328/// use warp::Filter;
329///
330/// let route = warp::any()
331///     .map(warp::reply)
332///     .map(|reply| {
333///         warp::reply::with_header(reply, "server", "warp")
334///     });
335/// ```
336pub fn with_header<T: Reply, K, V>(reply: T, name: K, value: V) -> WithHeader<T>
337where
338    HeaderName: TryFrom<K>,
339    <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
340    HeaderValue: TryFrom<V>,
341    <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
342{
343    let header = match <HeaderName as TryFrom<K>>::try_from(name) {
344        Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
345            Ok(value) => Some((name, value)),
346            Err(err) => {
347                let err = err.into();
348                tracing::error!("with_header value error: {}", err);
349                None
350            }
351        },
352        Err(err) => {
353            let err = err.into();
354            tracing::error!("with_header name error: {}", err);
355            None
356        }
357    };
358
359    WithHeader { header, reply }
360}
361
362/// Wraps an `impl Reply` and adds a header when rendering.
363///
364/// Returned by `warp::reply::with_header`.
365#[derive(Debug)]
366pub struct WithHeader<T> {
367    header: Option<(HeaderName, HeaderValue)>,
368    reply: T,
369}
370
371impl<T: Reply> Reply for WithHeader<T> {
372    fn into_response(self) -> Response {
373        let mut res = self.reply.into_response();
374        if let Some((name, value)) = self.header {
375            res.headers_mut().insert(name, value);
376        }
377        res
378    }
379}
380
381impl<T: Send> Reply for ::http::Response<T>
382where
383    Body: From<T>,
384{
385    #[inline]
386    fn into_response(self) -> Response {
387        self.map(Body::from)
388    }
389}
390
391impl Reply for ::http::StatusCode {
392    #[inline]
393    fn into_response(self) -> Response {
394        let mut res = Response::default();
395        *res.status_mut() = self;
396        res
397    }
398}
399
400impl Reply for ::http::Error {
401    #[inline]
402    fn into_response(self) -> Response {
403        tracing::error!("reply error: {:?}", self);
404        StatusCode::INTERNAL_SERVER_ERROR.into_response()
405    }
406}
407
408impl<T, E> Reply for Result<T, E>
409where
410    T: Reply,
411    E: Reply,
412{
413    #[inline]
414    fn into_response(self) -> Response {
415        match self {
416            Ok(t) => t.into_response(),
417            Err(e) => e.into_response(),
418        }
419    }
420}
421
422fn text_plain<T: Into<Body>>(body: T) -> Response {
423    let mut response = ::http::Response::new(body.into());
424    response.headers_mut().insert(
425        CONTENT_TYPE,
426        HeaderValue::from_static("text/plain; charset=utf-8"),
427    );
428    response
429}
430
431impl Reply for String {
432    #[inline]
433    fn into_response(self) -> Response {
434        text_plain(self)
435    }
436}
437
438impl Reply for Vec<u8> {
439    #[inline]
440    fn into_response(self) -> Response {
441        ::http::Response::builder()
442            .header(
443                CONTENT_TYPE,
444                HeaderValue::from_static("application/octet-stream"),
445            )
446            .body(Body::from(self))
447            .unwrap()
448    }
449}
450
451impl Reply for &'static str {
452    #[inline]
453    fn into_response(self) -> Response {
454        text_plain(self)
455    }
456}
457
458impl Reply for Cow<'static, str> {
459    #[inline]
460    fn into_response(self) -> Response {
461        match self {
462            Cow::Borrowed(s) => s.into_response(),
463            Cow::Owned(s) => s.into_response(),
464        }
465    }
466}
467
468impl Reply for &'static [u8] {
469    #[inline]
470    fn into_response(self) -> Response {
471        ::http::Response::builder()
472            .header(
473                CONTENT_TYPE,
474                HeaderValue::from_static("application/octet-stream"),
475            )
476            .body(Body::from(self))
477            .unwrap()
478    }
479}
480
481impl<T, U> Reply for Either<T, U>
482where
483    T: Reply,
484    U: Reply,
485{
486    #[inline]
487    fn into_response(self) -> Response {
488        match self {
489            Either::A(a) => a.into_response(),
490            Either::B(b) => b.into_response(),
491        }
492    }
493}
494
495impl<T> Reply for One<T>
496where
497    T: Reply,
498{
499    #[inline]
500    fn into_response(self) -> Response {
501        self.0.into_response()
502    }
503}
504
505impl Reply for std::convert::Infallible {
506    #[inline(always)]
507    fn into_response(self) -> Response {
508        match self {}
509    }
510}
511
512mod sealed {
513    use super::{Reply, Response};
514
515    // An opaque type to return `impl Reply` from trait methods.
516    #[allow(missing_debug_implementations)]
517    pub struct Reply_(pub(crate) Response);
518
519    impl Reply for Reply_ {
520        #[inline]
521        fn into_response(self) -> Response {
522            self.0
523        }
524    }
525
526    #[allow(missing_debug_implementations)]
527    pub struct Internal;
528
529    // Implemented for all types that implement `Reply`.
530    //
531    // A user doesn't need to worry about this, it's just trait
532    // hackery to get `Box<dyn Reply>` working.
533    pub trait BoxedReply {
534        fn boxed_into_response(self: Box<Self>, internal: Internal) -> Response;
535    }
536
537    impl<T: Reply> BoxedReply for T {
538        fn boxed_into_response(self: Box<Self>, _: Internal) -> Response {
539            (*self).into_response()
540        }
541    }
542}
543
544#[cfg(test)]
545mod tests {
546    use std::collections::HashMap;
547
548    use super::*;
549
550    #[test]
551    fn json_serde_error() {
552        // a HashMap<Vec, _> cannot be serialized to JSON
553        let mut map = HashMap::new();
554        map.insert(vec![1, 2], 45);
555
556        let res = json(&map).into_response();
557        assert_eq!(res.status(), 500);
558    }
559
560    #[test]
561    fn response_builder_error() {
562        let res = ::http::Response::builder()
563            .status(1337)
564            .body("woops")
565            .into_response();
566
567        assert_eq!(res.status(), 500);
568    }
569
570    #[test]
571    fn boxed_reply() {
572        let r: Box<dyn Reply> = Box::new(reply());
573        let resp = r.into_response();
574        assert_eq!(resp.status(), 200);
575    }
576}