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/// Reply with a body and `content-type` set to `text/html; charset=utf-8`.
131///
132/// # Example
133///
134/// ```
135/// use warp::Filter;
136///
137/// let body = r#"
138/// <html>
139///     <head>
140///         <title>HTML with warp!</title>
141///     </head>
142///     <body>
143///         <h1>warp + HTML = &hearts;</h1>
144///     </body>
145/// </html>
146/// "#;
147///
148/// let route = warp::any()
149///     .map(move || {
150///         warp::reply::html(body)
151///     });
152/// ```
153pub fn html<T>(body: T) -> Html<T>
154where
155    crate::bodyt::Body: From<T>,
156    T: Send,
157{
158    Html { body }
159}
160
161/// An HTML reply.
162#[allow(missing_debug_implementations)]
163pub struct Html<T> {
164    body: T,
165}
166
167impl<T> Reply for Html<T>
168where
169    crate::bodyt::Body: From<T>,
170    T: Send,
171{
172    #[inline]
173    fn into_response(self) -> Response {
174        let mut res = Response::new(Body::from(self.body));
175        res.headers_mut().insert(
176            CONTENT_TYPE,
177            HeaderValue::from_static("text/html; charset=utf-8"),
178        );
179        res
180    }
181}
182
183/// Types that can be converted into a `Response`.
184///
185/// This trait is implemented for the following:
186///
187/// - `http::StatusCode`
188/// - `http::Response<impl Into<hyper::Body>>`
189/// - `String`
190/// - `&'static str`
191///
192/// # Example
193///
194/// ```rust
195/// use warp::{Filter, http::Response};
196///
197/// struct Message {
198///     msg: String
199/// }
200///
201/// impl warp::Reply for Message {
202///     fn into_response(self) -> warp::reply::Response {
203///         Response::new(format!("message: {}", self.msg).into())
204///     }
205/// }
206///
207/// fn handler() -> Message {
208///     Message { msg: "Hello".to_string() }
209/// }
210///
211/// let route = warp::any().map(handler);
212/// ```
213pub trait Reply: BoxedReply + Send {
214    /// Converts the given value into a [`Response`].
215    ///
216    /// [`Response`]: type.Response.html
217    fn into_response(self) -> Response;
218
219    /*
220    TODO: Currently unsure about having trait methods here, as it
221    requires returning an exact type, which I'd rather not commit to.
222    Additionally, it doesn't work great with `Box<Reply>`.
223
224    A possible alternative is to have wrappers, like
225
226    - `WithStatus<R: Reply>(StatusCode, R)`
227
228
229    /// Change the status code of this `Reply`.
230    fn with_status(self, status: StatusCode) -> Reply_
231    where
232        Self: Sized,
233    {
234        let mut res = self.into_response();
235        *res.status_mut() = status;
236        Reply_(res)
237    }
238
239    /// Add a header to this `Reply`.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// use warp::Reply;
245    ///
246    /// let reply = warp::reply()
247    ///     .with_header("x-foo", "bar");
248    /// ```
249    fn with_header<K, V>(self, name: K, value: V) -> Reply_
250    where
251        Self: Sized,
252        HeaderName: TryFrom<K>,
253        HeaderValue: TryFrom<V>,
254    {
255        match <HeaderName as TryFrom<K>>::try_from(name) {
256            Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
257                Ok(value) => {
258                    let mut res = self.into_response();
259                    res.headers_mut().append(name, value);
260                    Reply_(res)
261                },
262                Err(err) => {
263                    tracing::error!("with_header value error: {}", err.into());
264                    Reply_(::reject::server_error()
265                        .into_response())
266                }
267            },
268            Err(err) => {
269                tracing::error!("with_header name error: {}", err.into());
270                Reply_(::reject::server_error()
271                    .into_response())
272            }
273        }
274    }
275    */
276}
277
278impl<T: Reply + ?Sized> Reply for Box<T> {
279    fn into_response(self) -> Response {
280        self.boxed_into_response(Internal)
281    }
282}
283
284fn _assert_object_safe() {
285    fn _assert(_: &dyn Reply) {}
286}
287
288/// Wrap an `impl Reply` to change its `StatusCode`.
289///
290/// # Example
291///
292/// ```
293/// use warp::Filter;
294///
295/// let route = warp::any()
296///     .map(warp::reply)
297///     .map(|reply| {
298///         warp::reply::with_status(reply, warp::http::StatusCode::CREATED)
299///     });
300/// ```
301pub fn with_status<T: Reply>(reply: T, status: StatusCode) -> WithStatus<T> {
302    WithStatus { reply, status }
303}
304
305/// Wrap an `impl Reply` to change its `StatusCode`.
306///
307/// Returned by `warp::reply::with_status`.
308#[derive(Debug)]
309pub struct WithStatus<T> {
310    reply: T,
311    status: StatusCode,
312}
313
314impl<T: Reply> Reply for WithStatus<T> {
315    fn into_response(self) -> Response {
316        let mut res = self.reply.into_response();
317        *res.status_mut() = self.status;
318        res
319    }
320}
321
322/// Wrap an `impl Reply` to add a header when rendering.
323///
324/// # Example
325///
326/// ```
327/// use warp::Filter;
328///
329/// let route = warp::any()
330///     .map(warp::reply)
331///     .map(|reply| {
332///         warp::reply::with_header(reply, "server", "warp")
333///     });
334/// ```
335pub fn with_header<T: Reply, K, V>(reply: T, name: K, value: V) -> WithHeader<T>
336where
337    HeaderName: TryFrom<K>,
338    <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
339    HeaderValue: TryFrom<V>,
340    <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
341{
342    let header = match <HeaderName as TryFrom<K>>::try_from(name) {
343        Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
344            Ok(value) => Some((name, value)),
345            Err(err) => {
346                let err = err.into();
347                tracing::error!("with_header value error: {}", err);
348                None
349            }
350        },
351        Err(err) => {
352            let err = err.into();
353            tracing::error!("with_header name error: {}", err);
354            None
355        }
356    };
357
358    WithHeader { header, reply }
359}
360
361/// Wraps an `impl Reply` and adds a header when rendering.
362///
363/// Returned by `warp::reply::with_header`.
364#[derive(Debug)]
365pub struct WithHeader<T> {
366    header: Option<(HeaderName, HeaderValue)>,
367    reply: T,
368}
369
370impl<T: Reply> Reply for WithHeader<T> {
371    fn into_response(self) -> Response {
372        let mut res = self.reply.into_response();
373        if let Some((name, value)) = self.header {
374            res.headers_mut().insert(name, value);
375        }
376        res
377    }
378}
379
380impl<T: Send> Reply for ::http::Response<T>
381where
382    crate::bodyt::Body: From<T>,
383{
384    #[inline]
385    fn into_response(self) -> Response {
386        self.map(Body::from)
387    }
388}
389
390impl Reply for ::http::StatusCode {
391    #[inline]
392    fn into_response(self) -> Response {
393        let mut res = Response::default();
394        *res.status_mut() = self;
395        res
396    }
397}
398
399impl Reply for ::http::Error {
400    #[inline]
401    fn into_response(self) -> Response {
402        tracing::error!("reply error: {:?}", self);
403        StatusCode::INTERNAL_SERVER_ERROR.into_response()
404    }
405}
406
407impl<T, E> Reply for Result<T, E>
408where
409    T: Reply,
410    E: Reply,
411{
412    #[inline]
413    fn into_response(self) -> Response {
414        match self {
415            Ok(t) => t.into_response(),
416            Err(e) => e.into_response(),
417        }
418    }
419}
420
421fn text_plain<T: Into<Body>>(body: T) -> Response {
422    let mut response = ::http::Response::new(body.into());
423    response.headers_mut().insert(
424        CONTENT_TYPE,
425        HeaderValue::from_static("text/plain; charset=utf-8"),
426    );
427    response
428}
429
430impl Reply for String {
431    #[inline]
432    fn into_response(self) -> Response {
433        text_plain(self)
434    }
435}
436
437impl Reply for Vec<u8> {
438    #[inline]
439    fn into_response(self) -> Response {
440        ::http::Response::builder()
441            .header(
442                CONTENT_TYPE,
443                HeaderValue::from_static("application/octet-stream"),
444            )
445            .body(Body::from(self))
446            .unwrap()
447    }
448}
449
450impl Reply for &'static str {
451    #[inline]
452    fn into_response(self) -> Response {
453        text_plain(self)
454    }
455}
456
457impl Reply for Cow<'static, str> {
458    #[inline]
459    fn into_response(self) -> Response {
460        match self {
461            Cow::Borrowed(s) => s.into_response(),
462            Cow::Owned(s) => s.into_response(),
463        }
464    }
465}
466
467impl Reply for &'static [u8] {
468    #[inline]
469    fn into_response(self) -> Response {
470        ::http::Response::builder()
471            .header(
472                CONTENT_TYPE,
473                HeaderValue::from_static("application/octet-stream"),
474            )
475            .body(Body::from(bytes::Bytes::from_static(self)))
476            .unwrap()
477    }
478}
479
480impl<T, U> Reply for Either<T, U>
481where
482    T: Reply,
483    U: Reply,
484{
485    #[inline]
486    fn into_response(self) -> Response {
487        match self {
488            Either::A(a) => a.into_response(),
489            Either::B(b) => b.into_response(),
490        }
491    }
492}
493
494impl<T> Reply for One<T>
495where
496    T: Reply,
497{
498    #[inline]
499    fn into_response(self) -> Response {
500        self.0.into_response()
501    }
502}
503
504impl Reply for std::convert::Infallible {
505    #[inline(always)]
506    fn into_response(self) -> Response {
507        match self {}
508    }
509}
510
511mod sealed {
512    use super::{Reply, Response};
513
514    // An opaque type to return `impl Reply` from trait methods.
515    #[allow(missing_debug_implementations)]
516    pub struct Reply_(pub(crate) Response);
517
518    impl Reply for Reply_ {
519        #[inline]
520        fn into_response(self) -> Response {
521            self.0
522        }
523    }
524
525    #[allow(missing_debug_implementations)]
526    pub struct Internal;
527
528    // Implemented for all types that implement `Reply`.
529    //
530    // A user doesn't need to worry about this, it's just trait
531    // hackery to get `Box<dyn Reply>` working.
532    pub trait BoxedReply {
533        fn boxed_into_response(self: Box<Self>, internal: Internal) -> Response;
534    }
535
536    impl<T: Reply> BoxedReply for T {
537        fn boxed_into_response(self: Box<Self>, _: Internal) -> Response {
538            (*self).into_response()
539        }
540    }
541}
542
543#[cfg(test)]
544mod tests {
545    use std::collections::HashMap;
546
547    use super::*;
548
549    #[test]
550    fn json_serde_error() {
551        // a HashMap<Vec, _> cannot be serialized to JSON
552        let mut map = HashMap::new();
553        map.insert(vec![1, 2], 45);
554
555        let res = json(&map).into_response();
556        assert_eq!(res.status(), 500);
557    }
558
559    #[test]
560    fn response_builder_error() {
561        let res = ::http::Response::builder()
562            .status(1337)
563            .body("woops")
564            .into_response();
565
566        assert_eq!(res.status(), 500);
567    }
568
569    #[test]
570    fn boxed_reply() {
571        let r: Box<dyn Reply> = Box::new(reply());
572        let resp = r.into_response();
573        assert_eq!(resp.status(), 200);
574    }
575}