//! Reply to requests. //! //! A [`Reply`](./trait.Reply.html) is a type that can be converted into an HTTP //! response to be sent to the client. These are typically the successful //! counterpart to a [rejection](../reject). //! //! The functions in this module are helpers for quickly creating a reply. //! Besides them, you can return a type that implements [`Reply`](./trait.Reply.html). This //! could be any of the following: //! //! - [`http::Response>`](https://docs.rs/http) //! - `String` //! - `&'static str` //! - `http::StatusCode` //! //! # Example //! //! ``` //! use warp::{Filter, http::Response}; //! //! // Returns an empty `200 OK` response. //! let empty_200 = warp::any().map(warp::reply); //! //! // Returns a `200 OK` response with custom header and body. //! let custom = warp::any().map(|| { //! Response::builder() //! .header("my-custom-header", "some-value") //! .body("and a custom body") //! }); //! //! // GET requests return the empty 200, POST return the custom. //! let routes = warp::get().and(empty_200) //! .or(warp::post().and(custom)); //! ``` use std::borrow::Cow; use std::convert::TryFrom; use std::error::Error as StdError; use std::fmt; use crate::generic::{Either, One}; use http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use http::StatusCode; use hyper::Body; use serde::Serialize; use serde_json; // This re-export just looks weird in docs... pub(crate) use self::sealed::Reply_; use self::sealed::{BoxedReply, Internal}; #[doc(hidden)] pub use crate::filters::reply as with; /// Response type into which types implementing the `Reply` trait are convertable. pub type Response = ::http::Response; /// Returns an empty `Reply` with status code `200 OK`. /// /// # Example /// /// ``` /// use warp::Filter; /// /// // GET /just-ok returns an empty `200 OK`. /// let route = warp::path("just-ok") /// .map(|| { /// println!("got a /just-ok request!"); /// warp::reply() /// }); /// ``` #[inline] pub fn reply() -> impl Reply { StatusCode::OK } /// Convert the value into a `Reply` with the value encoded as JSON. /// /// The passed value must implement [`Serialize`][ser]. Many /// collections do, and custom domain types can have `Serialize` derived. /// /// [ser]: https://serde.rs /// /// # Example /// /// ``` /// use warp::Filter; /// /// // GET /ids returns a `200 OK` with a JSON array of ids: /// // `[1, 3, 7, 13]` /// let route = warp::path("ids") /// .map(|| { /// let our_ids = vec![1, 3, 7, 13]; /// warp::reply::json(&our_ids) /// }); /// ``` /// /// # Note /// /// If a type fails to be serialized into JSON, the error is logged at the /// `error` level, and the returned `impl Reply` will be an empty /// `500 Internal Server Error` response. pub fn json(val: &T) -> Json where T: Serialize, { Json { inner: serde_json::to_vec(val).map_err(|err| { tracing::error!("reply::json error: {}", err); }), } } /// A JSON formatted reply. #[allow(missing_debug_implementations)] pub struct Json { inner: Result, ()>, } impl Reply for Json { #[inline] fn into_response(self) -> Response { match self.inner { Ok(body) => { let mut res = Response::new(body.into()); res.headers_mut() .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); res } Err(()) => StatusCode::INTERNAL_SERVER_ERROR.into_response(), } } } #[derive(Debug)] pub(crate) struct ReplyJsonError; impl fmt::Display for ReplyJsonError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("warp::reply::json() failed") } } impl StdError for ReplyJsonError {} /// Reply with a body and `content-type` set to `text/html; charset=utf-8`. /// /// # Example /// /// ``` /// use warp::Filter; /// /// let body = r#" /// /// /// HTML with warp! /// /// ///

warp + HTML = ♥

/// /// /// "#; /// /// let route = warp::any() /// .map(move || { /// warp::reply::html(body) /// }); /// ``` pub fn html(body: T) -> Html where Body: From, T: Send, { Html { body } } /// An HTML reply. #[allow(missing_debug_implementations)] pub struct Html { body: T, } impl Reply for Html where Body: From, T: Send, { #[inline] fn into_response(self) -> Response { let mut res = Response::new(Body::from(self.body)); res.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("text/html; charset=utf-8"), ); res } } /// Types that can be converted into a `Response`. /// /// This trait is implemented for the following: /// /// - `http::StatusCode` /// - `http::Response>` /// - `String` /// - `&'static str` /// /// # Example /// /// ```rust /// use warp::{Filter, http::Response}; /// /// struct Message { /// msg: String /// } /// /// impl warp::Reply for Message { /// fn into_response(self) -> warp::reply::Response { /// Response::new(format!("message: {}", self.msg).into()) /// } /// } /// /// fn handler() -> Message { /// Message { msg: "Hello".to_string() } /// } /// /// let route = warp::any().map(handler); /// ``` pub trait Reply: BoxedReply + Send { /// Converts the given value into a [`Response`]. /// /// [`Response`]: type.Response.html fn into_response(self) -> Response; /* TODO: Currently unsure about having trait methods here, as it requires returning an exact type, which I'd rather not commit to. Additionally, it doesn't work great with `Box`. A possible alternative is to have wrappers, like - `WithStatus(StatusCode, R)` /// Change the status code of this `Reply`. fn with_status(self, status: StatusCode) -> Reply_ where Self: Sized, { let mut res = self.into_response(); *res.status_mut() = status; Reply_(res) } /// Add a header to this `Reply`. /// /// # Example /// /// ```rust /// use warp::Reply; /// /// let reply = warp::reply() /// .with_header("x-foo", "bar"); /// ``` fn with_header(self, name: K, value: V) -> Reply_ where Self: Sized, HeaderName: TryFrom, HeaderValue: TryFrom, { match >::try_from(name) { Ok(name) => match >::try_from(value) { Ok(value) => { let mut res = self.into_response(); res.headers_mut().append(name, value); Reply_(res) }, Err(err) => { tracing::error!("with_header value error: {}", err.into()); Reply_(::reject::server_error() .into_response()) } }, Err(err) => { tracing::error!("with_header name error: {}", err.into()); Reply_(::reject::server_error() .into_response()) } } } */ } impl Reply for Box { fn into_response(self) -> Response { self.boxed_into_response(Internal) } } fn _assert_object_safe() { fn _assert(_: &dyn Reply) {} } /// Wrap an `impl Reply` to change its `StatusCode`. /// /// # Example /// /// ``` /// use warp::Filter; /// /// let route = warp::any() /// .map(warp::reply) /// .map(|reply| { /// warp::reply::with_status(reply, warp::http::StatusCode::CREATED) /// }); /// ``` pub fn with_status(reply: T, status: StatusCode) -> WithStatus { WithStatus { reply, status } } /// Wrap an `impl Reply` to change its `StatusCode`. /// /// Returned by `warp::reply::with_status`. #[derive(Debug)] pub struct WithStatus { reply: T, status: StatusCode, } impl Reply for WithStatus { fn into_response(self) -> Response { let mut res = self.reply.into_response(); *res.status_mut() = self.status; res } } /// Wrap an `impl Reply` to add a header when rendering. /// /// # Example /// /// ``` /// use warp::Filter; /// /// let route = warp::any() /// .map(warp::reply) /// .map(|reply| { /// warp::reply::with_header(reply, "server", "warp") /// }); /// ``` pub fn with_header(reply: T, name: K, value: V) -> WithHeader where HeaderName: TryFrom, >::Error: Into, HeaderValue: TryFrom, >::Error: Into, { let header = match >::try_from(name) { Ok(name) => match >::try_from(value) { Ok(value) => Some((name, value)), Err(err) => { let err = err.into(); tracing::error!("with_header value error: {}", err); None } }, Err(err) => { let err = err.into(); tracing::error!("with_header name error: {}", err); None } }; WithHeader { header, reply } } /// Wraps an `impl Reply` and adds a header when rendering. /// /// Returned by `warp::reply::with_header`. #[derive(Debug)] pub struct WithHeader { header: Option<(HeaderName, HeaderValue)>, reply: T, } impl Reply for WithHeader { fn into_response(self) -> Response { let mut res = self.reply.into_response(); if let Some((name, value)) = self.header { res.headers_mut().insert(name, value); } res } } impl Reply for ::http::Response where Body: From, { #[inline] fn into_response(self) -> Response { self.map(Body::from) } } impl Reply for ::http::StatusCode { #[inline] fn into_response(self) -> Response { let mut res = Response::default(); *res.status_mut() = self; res } } impl Reply for Result where T: Reply + Send, { #[inline] fn into_response(self) -> Response { match self { Ok(t) => t.into_response(), Err(e) => { tracing::error!("reply error: {:?}", e); StatusCode::INTERNAL_SERVER_ERROR.into_response() } } } } fn text_plain>(body: T) -> Response { let mut response = ::http::Response::new(body.into()); response.headers_mut().insert( CONTENT_TYPE, HeaderValue::from_static("text/plain; charset=utf-8"), ); response } impl Reply for String { #[inline] fn into_response(self) -> Response { text_plain(self) } } impl Reply for Vec { #[inline] fn into_response(self) -> Response { ::http::Response::builder() .header( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"), ) .body(Body::from(self)) .unwrap() } } impl Reply for &'static str { #[inline] fn into_response(self) -> Response { text_plain(self) } } impl Reply for Cow<'static, str> { #[inline] fn into_response(self) -> Response { match self { Cow::Borrowed(s) => s.into_response(), Cow::Owned(s) => s.into_response(), } } } impl Reply for &'static [u8] { #[inline] fn into_response(self) -> Response { ::http::Response::builder() .header( CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"), ) .body(Body::from(self)) .unwrap() } } impl Reply for Either where T: Reply, U: Reply, { #[inline] fn into_response(self) -> Response { match self { Either::A(a) => a.into_response(), Either::B(b) => b.into_response(), } } } impl Reply for One where T: Reply, { #[inline] fn into_response(self) -> Response { self.0.into_response() } } impl Reply for std::convert::Infallible { #[inline(always)] fn into_response(self) -> Response { match self {} } } mod sealed { use super::{Reply, Response}; // An opaque type to return `impl Reply` from trait methods. #[allow(missing_debug_implementations)] pub struct Reply_(pub(crate) Response); impl Reply for Reply_ { #[inline] fn into_response(self) -> Response { self.0 } } #[allow(missing_debug_implementations)] pub struct Internal; // Implemented for all types that implement `Reply`. // // A user doesn't need to worry about this, it's just trait // hackery to get `Box` working. pub trait BoxedReply { fn boxed_into_response(self: Box, internal: Internal) -> Response; } impl BoxedReply for T { fn boxed_into_response(self: Box, _: Internal) -> Response { (*self).into_response() } } } #[cfg(test)] mod tests { use std::collections::HashMap; use super::*; #[test] fn json_serde_error() { // a HashMap cannot be serialized to JSON let mut map = HashMap::new(); map.insert(vec![1, 2], 45); let res = json(&map).into_response(); assert_eq!(res.status(), 500); } #[test] fn response_builder_error() { let res = ::http::Response::builder() .status(1337) .body("woops") .into_response(); assert_eq!(res.status(), 500); } #[test] fn boxed_reply() { let r: Box = Box::new(reply()); let resp = r.into_response(); assert_eq!(resp.status(), 200); } }