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