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 = ♥</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}