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