Skip to main content

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