1 #![deny(warnings)]
2 
3 use std::convert::Infallible;
4 use std::num::NonZeroU16;
5 
6 use serde_derive::Serialize;
7 use warp::http::StatusCode;
8 use warp::{reject, Filter, Rejection, Reply};
9 
10 /// Rejections represent cases where a filter should not continue processing
11 /// the request, but a different filter *could* process it.
12 #[tokio::main]
main()13 async fn main() {
14     let math = warp::path!("math" / u16)
15         .and(div_by())
16         .map(|num: u16, denom: NonZeroU16| {
17             warp::reply::json(&Math {
18                 op: format!("{} / {}", num, denom),
19                 output: num / denom.get(),
20             })
21         });
22 
23     let routes = warp::get().and(math).recover(handle_rejection);
24 
25     warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
26 }
27 
28 /// Extract a denominator from a "div-by" header, or reject with DivideByZero.
div_by() -> impl Filter<Extract = (NonZeroU16,), Error = Rejection> + Copy29 fn div_by() -> impl Filter<Extract = (NonZeroU16,), Error = Rejection> + Copy {
30     warp::header::<u16>("div-by").and_then(|n: u16| async move {
31         if let Some(denom) = NonZeroU16::new(n) {
32             Ok(denom)
33         } else {
34             Err(reject::custom(DivideByZero))
35         }
36     })
37 }
38 
39 #[derive(Debug)]
40 struct DivideByZero;
41 
42 impl reject::Reject for DivideByZero {}
43 
44 // JSON replies
45 
46 /// A successful math operation.
47 #[derive(Serialize)]
48 struct Math {
49     op: String,
50     output: u16,
51 }
52 
53 /// An API error serializable to JSON.
54 #[derive(Serialize)]
55 struct ErrorMessage {
56     code: u16,
57     message: String,
58 }
59 
60 // This function receives a `Rejection` and tries to return a custom
61 // value, otherwise simply passes the rejection along.
handle_rejection(err: Rejection) -> Result<impl Reply, Infallible>62 async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
63     let code;
64     let message;
65 
66     if err.is_not_found() {
67         code = StatusCode::NOT_FOUND;
68         message = "NOT_FOUND";
69     } else if let Some(DivideByZero) = err.find() {
70         code = StatusCode::BAD_REQUEST;
71         message = "DIVIDE_BY_ZERO";
72     } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
73         // We can handle a specific error, here METHOD_NOT_ALLOWED,
74         // and render it however we want
75         code = StatusCode::METHOD_NOT_ALLOWED;
76         message = "METHOD_NOT_ALLOWED";
77     } else {
78         // We should have expected this... Just log and say its a 500
79         eprintln!("unhandled rejection: {:?}", err);
80         code = StatusCode::INTERNAL_SERVER_ERROR;
81         message = "UNHANDLED_REJECTION";
82     }
83 
84     let json = warp::reply::json(&ErrorMessage {
85         code: code.as_u16(),
86         message: message.into(),
87     });
88 
89     Ok(warp::reply::with_status(json, code))
90 }
91