summaryrefslogtreecommitdiffstats
path: root/third_party/rust/warp
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/warp')
-rw-r--r--third_party/rust/warp/.cargo-checksum.json1
-rw-r--r--third_party/rust/warp/CHANGELOG.md250
-rw-r--r--third_party/rust/warp/Cargo.lock1633
-rw-r--r--third_party/rust/warp/Cargo.toml140
-rw-r--r--third_party/rust/warp/LICENSE20
-rw-r--r--third_party/rust/warp/README.md58
-rw-r--r--third_party/rust/warp/examples/README.md41
-rw-r--r--third_party/rust/warp/examples/autoreload.rs38
-rw-r--r--third_party/rust/warp/examples/body.rs30
-rw-r--r--third_party/rust/warp/examples/dir.rs10
-rw-r--r--third_party/rust/warp/examples/dir/another.html10
-rw-r--r--third_party/rust/warp/examples/dir/index.html10
-rw-r--r--third_party/rust/warp/examples/dyn_reply.rs17
-rw-r--r--third_party/rust/warp/examples/file.rs21
-rw-r--r--third_party/rust/warp/examples/futures.rs37
-rw-r--r--third_party/rust/warp/examples/handlebars_template.rs58
-rw-r--r--third_party/rust/warp/examples/headers.rs27
-rw-r--r--third_party/rust/warp/examples/hello.rs10
-rw-r--r--third_party/rust/warp/examples/rejections.rs90
-rw-r--r--third_party/rust/warp/examples/returning.rs20
-rw-r--r--third_party/rust/warp/examples/routing.rs92
-rw-r--r--third_party/rust/warp/examples/sse.rs28
-rw-r--r--third_party/rust/warp/examples/sse_chat.rs164
-rw-r--r--third_party/rust/warp/examples/tls.rs24
-rw-r--r--third_party/rust/warp/examples/tls/cert.pem24
-rw-r--r--third_party/rust/warp/examples/tls/key.rsa27
-rw-r--r--third_party/rust/warp/examples/todos.rs295
-rw-r--r--third_party/rust/warp/examples/unix_socket.rs14
-rw-r--r--third_party/rust/warp/examples/websockets.rs27
-rw-r--r--third_party/rust/warp/examples/websockets_chat.rs168
-rw-r--r--third_party/rust/warp/src/error.rs69
-rw-r--r--third_party/rust/warp/src/filter/and.rs89
-rw-r--r--third_party/rust/warp/src/filter/and_then.rs99
-rw-r--r--third_party/rust/warp/src/filter/boxed.rs100
-rw-r--r--third_party/rust/warp/src/filter/map.rs59
-rw-r--r--third_party/rust/warp/src/filter/map_err.rs58
-rw-r--r--third_party/rust/warp/src/filter/mod.rs462
-rw-r--r--third_party/rust/warp/src/filter/or.rs112
-rw-r--r--third_party/rust/warp/src/filter/or_else.rs109
-rw-r--r--third_party/rust/warp/src/filter/recover.rs119
-rw-r--r--third_party/rust/warp/src/filter/service.rs137
-rw-r--r--third_party/rust/warp/src/filter/unify.rs53
-rw-r--r--third_party/rust/warp/src/filter/untuple_one.rs52
-rw-r--r--third_party/rust/warp/src/filter/wrap.rs27
-rw-r--r--third_party/rust/warp/src/filters/addr.rs26
-rw-r--r--third_party/rust/warp/src/filters/any.rs76
-rw-r--r--third_party/rust/warp/src/filters/body.rs341
-rw-r--r--third_party/rust/warp/src/filters/cookie.rs33
-rw-r--r--third_party/rust/warp/src/filters/cors.rs619
-rw-r--r--third_party/rust/warp/src/filters/ext.rs36
-rw-r--r--third_party/rust/warp/src/filters/fs.rs501
-rw-r--r--third_party/rust/warp/src/filters/header.rs203
-rw-r--r--third_party/rust/warp/src/filters/log.rs275
-rw-r--r--third_party/rust/warp/src/filters/method.rs150
-rw-r--r--third_party/rust/warp/src/filters/mod.rs25
-rw-r--r--third_party/rust/warp/src/filters/multipart.rs190
-rw-r--r--third_party/rust/warp/src/filters/path.rs649
-rw-r--r--third_party/rust/warp/src/filters/query.rs39
-rw-r--r--third_party/rust/warp/src/filters/reply.rs257
-rw-r--r--third_party/rust/warp/src/filters/sse.rs715
-rw-r--r--third_party/rust/warp/src/filters/ws.rs365
-rw-r--r--third_party/rust/warp/src/generic.rs237
-rw-r--r--third_party/rust/warp/src/lib.rs170
-rw-r--r--third_party/rust/warp/src/redirect.rs70
-rw-r--r--third_party/rust/warp/src/reject.rs810
-rw-r--r--third_party/rust/warp/src/reply.rs581
-rw-r--r--third_party/rust/warp/src/route.rs142
-rw-r--r--third_party/rust/warp/src/server.rs458
-rw-r--r--third_party/rust/warp/src/service.rs3
-rw-r--r--third_party/rust/warp/src/test.rs684
-rw-r--r--third_party/rust/warp/src/tls.rs314
-rw-r--r--third_party/rust/warp/src/transport.rs53
-rw-r--r--third_party/rust/warp/tests/body.rs202
-rw-r--r--third_party/rust/warp/tests/cookie.rs50
-rw-r--r--third_party/rust/warp/tests/cors.rs184
-rw-r--r--third_party/rust/warp/tests/ext.rs28
-rw-r--r--third_party/rust/warp/tests/filter.rs158
-rw-r--r--third_party/rust/warp/tests/fs.rs228
-rw-r--r--third_party/rust/warp/tests/header.rs71
-rw-r--r--third_party/rust/warp/tests/method.rs52
-rw-r--r--third_party/rust/warp/tests/multipart.rs54
-rw-r--r--third_party/rust/warp/tests/path.rs386
-rw-r--r--third_party/rust/warp/tests/query.rs123
-rw-r--r--third_party/rust/warp/tests/redirect.rs13
-rw-r--r--third_party/rust/warp/tests/reply_with.rs64
-rw-r--r--third_party/rust/warp/tests/ws.rs182
86 files changed, 14746 insertions, 0 deletions
diff --git a/third_party/rust/warp/.cargo-checksum.json b/third_party/rust/warp/.cargo-checksum.json
new file mode 100644
index 0000000000..337ad21d34
--- /dev/null
+++ b/third_party/rust/warp/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"CHANGELOG.md":"5b878821dd387e90121a16ab87d873947ef8c962a06cd22493a59b2f491b4f19","Cargo.lock":"66fed07c933163cd9a29b9008873825646d1ee9d883d8430d2b8e173b0628bae","Cargo.toml":"95dd6d85a0c3135bbc7f2205344ad58c375438905d129a2ba3d3151a68776816","LICENSE":"6dfa8def8b810ab8043bdacf09b4166b9c4651f93b4a19399ddc4c52055b1069","README.md":"305f2f26da1c75f64d29c86685a08c2442de435557bd1480614de53f02803ce3","examples/README.md":"1330dfe1aae7e4237eb8abaa25da8920ed560daefca27450e6d4f3d700377537","examples/autoreload.rs":"4ec938ed185066dac92293fb30f225c5741d8a03ef7d084cfed9038cc33266e7","examples/body.rs":"57eb99b3a59c33474c5d5c7c22964bea66c663f525c755924ed75eca8fba4aa1","examples/dir.rs":"3e1bd9abb6e317bd64433dd3440636187ec258dc8a72f5e56ee0926269256f95","examples/dir/another.html":"f58abf6f1a7b233b58b537b981b10be2dc64be3ac66f139230bed1f80f9f5277","examples/dir/index.html":"52f5b947733c9d2b7900fa8e613c123455bdbf7a25aca8e30aba56f75a837e65","examples/dyn_reply.rs":"2b85c26246322e34ba1ddce317f83e3a0fdaff295c7730f97e6c961475e93ebd","examples/file.rs":"a155c1fbeedaecf8e926a97689b2ad45f711d20f87e4c29ff84a4b1dacaeedd5","examples/futures.rs":"e80901b15731d4555d7d4290f5779e7361497900661431288ac23c28925269d0","examples/handlebars_template.rs":"f0acb19640e2a2894e08740ff5d3e12a9bf0b93d88a9fb313ef2b1749d59969b","examples/headers.rs":"651cd6425219d1d09cfb4f1114fefc2dff4435070b90f7c1d1696c95c96913b5","examples/hello.rs":"9ca9f197e2365c21729bbe74fe07590c615aaf87f07aece51b5640c97a453196","examples/rejections.rs":"ec09f48dd164427eb02cad28f9688321a24dd7c323ddcb918a58bef371f98529","examples/returning.rs":"e19e8df9ec34d7eeb60f6053e5b1f51043159dccb7f5e7148b69597eff2061ea","examples/routing.rs":"0511a80ad1874d9fda302479fb50a861d5a546da19e1f06a3befff4819fe56e1","examples/sse.rs":"4adae8335a9d3757bf4b9b4477358df299c339e958ae427ab853dc2cadbe155c","examples/sse_chat.rs":"31b33b517f72f3f34232ee39781e23ae6d4a97ac90a289a8a1eb3f91d7da0841","examples/tls.rs":"450d3e7e7da94d29a9c8e48dc0775f27931886eafd02e137cbfb3e7924edc698","examples/tls/cert.pem":"6b974e5654edca3664916613c9f932a64990cff3f604df5369275c35f64d36a2","examples/tls/key.rsa":"ffe615f53e98682bc470ae6fa964ce34a383b4509ae7bb9a7ee2ece1b1bdc7e3","examples/todos.rs":"0bc7575d7acc942635f1b7df75751ff9d2dc1f6cc73fd632036f773ce859b8be","examples/unix_socket.rs":"89128846ae31a7feef00d092edfd989dffba4647caa78f43f750510a5a5ff9bf","examples/websockets.rs":"a3677b311813dc68e7d1c1ad91779b348942cedd47db4b13a4158cdf54ce4411","examples/websockets_chat.rs":"50b3f6cc0036a83d67361889179f6f8948d2f6a80aa1ab2972d5e89f6d275941","src/error.rs":"221d8d1180ca31570416ef9593483a27fd343029fc16eaed842131671c16a754","src/filter/and.rs":"aed85d45ac92ad4e80c767340bfed436a106e80e82f6230074abfa578c60c93f","src/filter/and_then.rs":"56146227d0cb2c7d519b056a4dcb4dbb8d7245ec9fd3b0e789fa940dadf35b98","src/filter/boxed.rs":"160536a43c411110d2420a9215d6e55bd4d94323570e013ba4ba168103a8c716","src/filter/map.rs":"df0d0a6012eaef124bb7f1b21ec7a4507c582438c51c88cec8759538c61fcd86","src/filter/map_err.rs":"1bdec8f691482a531a1b85c9eaf96fddc86f076c14f5f8540fea95cadfac92eb","src/filter/mod.rs":"4bfb4110190919f260eb58a4eea6c4ec54365a2b95eef02630a44ea4b8eea17b","src/filter/or.rs":"642f1a936c3af5429bb6f15aedc505906622dd7cf2a6782cee72962cf7045db6","src/filter/or_else.rs":"8d736ff444af09846e797f0523ffbc904e8ae4466e1be16f28ab5eacac0286b8","src/filter/recover.rs":"6d65299154a7305057b70e01addce04f5afa8e563c7f655de6f310bfaeaa20af","src/filter/service.rs":"78583f2f028025dabda0636d6a59a095faad9a3d85e32fe0defe0a5d9b8b4a99","src/filter/unify.rs":"8f56fa9fe85dd68ed84b8070705f50d3f53de74e6f0dfec4fa74e1458c2a31e0","src/filter/untuple_one.rs":"696d475fd1e3f2c50b0be519196462ff7648953d5aed8cae057bc81790479f3d","src/filter/wrap.rs":"44d02002e06f9661fd5d2fa56eccd11129dcf68f7a1795f2e0e94c7219c5e873","src/filters/addr.rs":"be634906b34e26c35dc4a62b382fc34aa4a363778e77a085a0dc6be2f7efac99","src/filters/any.rs":"2c993a9866cd5e545f6dcddf27d3a5394a3484eff6c4a839af4e50f90b43b95f","src/filters/body.rs":"f1ccea8b6c4ca3db27b1cdcc85ded2d5ba8482d952738efb64b293f9dadecde6","src/filters/cookie.rs":"f785fca4d0e26af4c0807dbe440f42f7cbba4f09c87e760f1911c3f5cf83b927","src/filters/cors.rs":"14c9567cd2412d3215aaa9f342737e484e50913978e5eebc3cf96f86d719e815","src/filters/ext.rs":"0dfe1218f06c723663a649c2a0e36fe32be02191e1ef7aaeecdceede967520ff","src/filters/fs.rs":"0328510f8bd5d9b8cc044564a1e2c40c35237191d1e0cce377854b7194566f9f","src/filters/header.rs":"29f4a9944f28bbe83c9941de9da76dd3d6bbf25e3c5a199e88aeb3bbee061188","src/filters/log.rs":"1571e74b9463164e1bb93a49189fd0487eecc6d94235f9e944fc62b5821c7971","src/filters/method.rs":"b075fbb566b9a8daa62a26da55fff9aa70beb94d3a1492b5edac0c007e9b8a79","src/filters/mod.rs":"5e348ee50811c4981886fa90d7f8e9e53ab08103819289a6f00a59961a6889e8","src/filters/multipart.rs":"a08f8a1621524ca027d10fc72d12310133245222864fcf94cea6aa9aaf127b7d","src/filters/path.rs":"420ae23e448ccbab560f10eba118dfbe56f0b8b1d3554a0cdec89b1fc07f6af6","src/filters/query.rs":"d98b03128dfce822844d750870b8c1cb75a4edc8947313182031c6139db8f973","src/filters/reply.rs":"6102b63e142dfe2c55bdd37f1b3e9d25852a1ed7f1ec2ec4fec91f478cf672a4","src/filters/sse.rs":"bce025464b7eacd2ec4dd45351ff722581479dd950d4225cab3727f79120a2af","src/filters/ws.rs":"2c97875909a6f8fd9f750c18fc8122d77e15cf65aa5438a44255daa3ddecb7eb","src/generic.rs":"66be231b0cc92c41379f81d23343fd971941e3588f833f537ae5c97e445828e0","src/lib.rs":"80f3fe3560b11419f060aad118ef5f2e74aace4fede1e4392e78a20675e385e3","src/redirect.rs":"190ab1011c2771fc405a32047399cfce90b1864baaaa08d4df4f047dd9814c7b","src/reject.rs":"78da91ec2aa93c2293aced66ba921a160f0cc65da7e13fdc3b7394ea10365088","src/reply.rs":"771009494bbf2c16b7ccb09a754e4617ed71ee8168d8566fd8d49bd9ebac14f7","src/route.rs":"717c035d6e6ea98bc111524e7ed97fb3041110b9db76f4954b802be224c14cc1","src/server.rs":"bc185bb47f3f07c5859d6b2568f2fa6fb24a47e639bdbf29d25ce0a3385af29f","src/service.rs":"4564ec95e98a2314f73df24582ca8f6ec96cc8eda90fb5a5d1d83a9d5c841b86","src/test.rs":"f87cd4a248ffb85a4c3f03f04b70512508fe7e1a6ab4f638f3a0e6f4608412a5","src/tls.rs":"5e9a15960750583732eb45e8af941bdd2fb412d70fa00c4f27d1c6d005336c23","src/transport.rs":"338211e665e46ffd32e161292dd0dd89d9a5d2f814a0641273c710be1b3e8fdf","tests/body.rs":"371d58394da468aab9f2412e73b44de615a517c3fc94986d25d6a246ab461e6d","tests/cookie.rs":"3fddd2d109da20ceb9d6c389facda7bbf4d066d9a32f639c4dad9679590dcb93","tests/cors.rs":"07798bf7f30f73f19fae2b9cf7b9f94228d490bdc52767dc3413863f635c6af6","tests/ext.rs":"93d6527288f71ee20b63f6a47f616f055735373b0f203f0863f27c2b65fd8991","tests/filter.rs":"76c05031f1e0d6271ff2ac7eb596b4c97ffd0fe93d4d49af4783cc4d862dae25","tests/fs.rs":"d7ca82ec2f074e67844d7013960cf49ddeb201fe8a0b023e6d53028702809bbc","tests/header.rs":"78d7b4fd80025694cde65010cf04e4cc23ad4ac91fff3cb527542c278e6cfe4b","tests/method.rs":"6ae1f188b06b07822bbd97f671886259ee8e7008313449ec04604c8f396cf59b","tests/multipart.rs":"62cb1ded7cc1805c925162b9cc8598a7ec85e9d7ae36df5cac367110b4e951ac","tests/path.rs":"f6e0537ef7558b89768688e0c7978bed615edd2a40f7137e83ea148de0d1dd2e","tests/query.rs":"045ecd7b22afb73bc210058ab863c60189ce0af5dadc3b7999e4e527c1420163","tests/redirect.rs":"25e0c18fcc7bf1c0393708e0efc10f4508093d1b355371a66b9a59e2eb51ed13","tests/reply_with.rs":"35fdfe9653ffab0776fe9fb65e231f9ea647c9f391b17794010adbcbd5009e65","tests/ws.rs":"e130c3f95c930b5761c675b7ebf352c7f9aaf62aaeca97237cb5285921c6c71a"},"package":"54cd1e2b3eb3539284d88b76a9afcf5e20f2ef2fab74db5b21a1c30d7d945e82"} \ No newline at end of file
diff --git a/third_party/rust/warp/CHANGELOG.md b/third_party/rust/warp/CHANGELOG.md
new file mode 100644
index 0000000000..64d4c8818e
--- /dev/null
+++ b/third_party/rust/warp/CHANGELOG.md
@@ -0,0 +1,250 @@
+### v0.2.2 (March 3, 2020)
+
+- **Features**:
+ - Implement `Reply` for all `Box<T>` where `T: Reply`.
+ - Add `name` methods to `MissingHeader`, `InvalidHeader`, and `MissingCookie` rejections.
+ - Add `warp::ext::optional()` filter that optionally retrieves an extension from the request.
+- **Fixes**:
+ - Fix the sending of pings when a user sends a `ws::Message::ping()`.
+
+### v0.2.1 (January 23, 2020)
+
+- **Features**:
+ - Add `close` and `close_with` constructors to `warp::ws::Message`.
+- **Fixes**:
+ - Fix `warp::fs` filters using a very small read buffer.
+
+## v0.2.0 (January 16, 2020)
+
+- **Features**:
+ - Update to `std::future`, adding `async`/`await` support!
+ - Add `warp::service()` to convert a `Filter` into a `tower::Service`.
+ - Implement `Reply` for `Box<dyn Reply>`.
+- **Changes**:
+ - Refactored Rejection system (#311).
+ - Change `path!` macro to assume a `path::end()` by default, with explicit `/ ..` to allow building a prefix (#359).
+ - Change `warp::path(str)` to accept any `AsRef<str>` argument.
+ - Rename "2"-suffixed filters and types (`get2` to `get`, `ws2` to `ws`, etc).
+ - `Filter::{or, or_else, recover}` now require `Self::Error=Rejection`. This helps catch filters that didn't make sense (like `warp::any().or(warp::get())`).
+ - Change several `warp::body` filters (#345).
+ - Change `warp::cors()` to return a `warp::cors::Builder` which still implements `Wrap`, but can also `build` a cheaper-to-clone wrapper.
+ - Change `warp::multipart` stream API to allow for errors when streaming.
+ - Change `warp::sse` to no longer return a `Filter`, adds `warp::sse::reply` to do what `Sse::reply` did.
+ - Change `Server::tls()` to return a TLS server builder (#340).
+ - Change internal `warp::never::Never` usage with `std::convert::Infallible`.
+ - Remove `warp::ext::set()` function (#222).
+ - Remove deprecated `warp::cookie::optional_value()`.
+
+
+### v0.1.20 (September 17, 2019)
+
+- **Features**:
+ - Implement `Clone` for the `warp::cors` filter.
+ - Add `into_bytes` method for `warp::ws::Message`.
+
+### v0.1.19 (August 16, 2019)
+
+- **Features**:
+ - Make `warp::multipart` and `wrap::ws` support optional, though enabled by default.
+- **Fixes**:
+ - Fix `warp::fs::dir` filter to reject paths containing backslashes.
+
+### v0.1.18 (July 25, 2019)
+
+- **Features**:
+ - Add `warp::multipart` support.
+
+### v0.1.17 (July 8, 2019)
+
+- **Features**:
+ - Export all built-in Rejection causes in the `warp::reject` module.
+ - Add `Server::try_bind` as fallible bind methods.
+
+### v0.1.16 (June 11, 2019)
+
+- **Features**:
+ - Unseal the `Reply` trait: custom types can now implement `Reply`.
+ - Add `warp::sse::keep_alive()` replacement for `warp::sse::keep()` which allows customizing keep-alive behavior.
+ - Add `warp::log::Info::host()` accessor.
+- **Fixes**:
+ - Fix `warp::fs` filters from sending some headers for `304` responses.
+
+### v0.1.15 (April 2, 2019)
+
+- **Features**:
+ - Add more accessors to `warp::log::Info` type for building custom log formats.
+ - Implement `Reply` for `Cow<'static, str>`.
+
+### v0.1.14 (March 19, 2019)
+
+- **Features**:
+ - Add `warp::header::optional` filter.
+
+### v0.1.13 (February 13, 2019)
+
+- **Features**:
+ - Implement `Reply` for `Vec<u8>` and `&'static [u8]`.
+ - Set `content-type` header automatically for string and bytes replies.
+ - Add `expose_headers` to `warp::cors` filter.
+
+### v0.1.12 (January 29, 2019)
+
+- **Features**:
+ - Implement `PartialEq`, `Eq`, and `Clone` for `warp::ws::Message`.
+- **Fixes**:
+ - Fix panic when incoming request URI may not have a path (such as `CONNECT` requests).
+
+### v0.1.11 (January 14, 2019)
+
+- **Features**:
+ - Add `warp::sse` filters for handling Server-Sent-Events.
+ - Add `allow_headers` to `warp::cors` filter.
+- **Fixes**:
+ - Fix TLS handshake to close the connection if handshake fails.
+
+### v0.1.10 (December 17, 2018)
+
+- **Features**:
+ - Add optional TLS support. Enable the `tls` feature, and then use `Server::tls`.
+ - Add `warp::cors` filter for CORS support.
+ - Add `warp::addr::remote` to access the remote address of a request.
+ - Add `warp::log::custom` to support customizing of access logging.
+ - Add `warp::test::ws` to improve testing Websocket filters.
+
+### v0.1.9 (October 30, 2018)
+
+- **Features**:
+ - Add `warp::ext::get` and `warp::ext::set` to set request extensions.
+ - Add `Filter::untuple_one` to unroll nested tuple layers from extractions.
+ - Add `Ws2::max_send_queue` configuration method.
+ - Add `ws::Message::is_ping` method, and yield pings to user code.
+- **Fixes**:
+ - Fix panic in debug mode when receiving a websocket ping.
+
+### v0.1.8 (October 25, 2018)
+
+- **Features**:
+ - Improved flexibility of `Rejection` system.
+
+ The `Rejection` type can now nest and combine arbitrary rejections,
+ so it is no longer bound to a small set of meanings. The ranking of
+ status codes is still used to determine which rejection gets priority.
+
+ A different priority can be implemented by handling rejections with
+ a `Filter::recover`, and searching for causes in order via
+ `Rejection::find_cause`.
+ - Adds `warp::reject::custom()` to create a `Rejection` with
+ any `Into<Box<std::error::Error>>`. These rejections should be
+ handled with an eventual `Filter::recover`. Any unhandled
+ custom rejections are considered a server error.
+ - Deprecates `Rejection::with`. Use custom rejections instead.
+ - Deprecates `Rejection::into_cause`, as it can no longer work. Always
+ returns `Err(Rejection)`.
+ - Deprecates `Rejection::json`, since the format needed is too generic.
+ The `errors.rs` example shows how to send custom JSON when recovering
+ from rejections.
+ - Deprecates `warp::reject()`, since it current signals a `400 Bad
+ Request`, but in newer versions, it will signal `404 Not Found`.
+ It's deprecated simply to warn that the semantics are changing,
+ but the function won't actually go away.
+ - Deprecates `reject::bad_request()`, `reject::forbidden()`, and
+ `reject::server_error()`. Uses custom rejections instead.
+ - Renamed `warp::path::index` to `warp::path::end`.
+
+
+### v0.1.7 (October 15, 2018)
+
+- **Features**:
+ - Export the types returned from the `warp::body::stream()` filter, `BodyStream` and `StreamBuf`.
+ - Deprecated `Rejection::into_cause`, since an upcoming Rejection refactor will make it impossible to support.
+
+- **Fixes**:
+ - Fix websocket filters to do a case-insensitive match of the `Connection` header.
+
+### v0.1.6 (October 5, 2018)
+
+- **Features**:
+ - Add Conditional and Range request support for `warp::fs` filters.
+ - Relaxed bounds on `Rejection::with` to no longer need to be `Sized`.
+ - Add `warp::path::peek()` which gets the unmatched tail without adjusting the currently matched path.
+
+### v0.1.5 (October 3, 2018)
+
+- **Features**:
+ - Serve `index.html` automatically with `warp::fs::dir` filter.
+ - Include `last-modified` header with `warp::fs` filters.
+ - Add `warp::redirect` to easily reply with redirections.
+ - Add `warp::reply::{with_status, with_header}` to wrap `impl Reply`s directly with a new status code or header.
+ - Add support for running a warp `Server` with a custom source of incoming connections.
+ - `Server::run_incoming` to have the runtime started automatically.
+ - `Server::serve_incoming` to get a future to run on existing runtime.
+ - These can be used to support Unix Domain Sockets, TLS, and other transports.
+ - Add `Rejection::into_cause()` to retrieve the original error of a rejection back.
+ - Add `Rejection::json()` to convert a rejection into a JSON response.
+
+- **Fixes**
+ - Internal errors in warp that result in rendering a `500 Internal Server Error` are now also logged at the `error` level.
+
+
+### v0.1.4 (September 25, 2018)
+
+- **Features**:
+ - Add `warp::reply::with::headers(HeaderMap)` filter wrapper.
+ - Add `warp::cookie::optional()` to get an optional cookie value.
+ - Add `warp::path::full()` to be able to extract the full request path without affecting route matching.
+ - Add graceful shutdown support to the `Server`.
+ - Allow empty query strings to be treated as for `warp::query()`.
+
+### v0.1.3 (August 28, 2018)
+
+- **Features**:
+ - Add `warp::reject::forbidden()` to represent `403 Forbidden` responses.
+ - Add `Rejection::with(cause)` to customize rejection messages.
+- **Fixes**:
+ - Fix `warp::body::form` to allow charsets in the `content-type` header.
+
+### v0.1.2 (August 14, 2018)
+
+- **Features**:
+ - Implemented `Reply` for `Response<impl Into<hyper::Body>`, allowing streaming response bodies.
+ - Add `warp::body::stream()` filter to access the request body as an `impl Stream`.
+ - Add `warp::ws2()` as a more flexible websocket filter.
+ - This allows passing other extracted values to the upgrade callback, such as a value from a header or path.
+ - Deprecates `warp::ws()`, and `ws2()` will become `ws()` in 0.2.
+ - Add `warp::get2()`, `warp::post2()`, `warp::put2()`, and `warp::delete2()` as more standard method filters that are used via chaining instead of nesting.
+ - `get()`, `post()`, `put()`, and `delete()` are deprecated, and the new versions will become them in 0.2.
+ - Add `Filter::unify()` for when a filter returns `Either<T, T>`, converting the `Either` into the inner `T`, regardless of which variant it was.
+ - This requires that both sides of the `Either` be the same type.
+ - This can be useful when extracting a value that might be present in different places of the request.
+
+ ```rust
+ // Allow `MyId` to be a path parameter or a header...
+ let id = warp::path::param::<MyId>()
+ .or(warp::header::<MyId>())
+ .unify();
+
+ // A way of providing default values...
+ let dnt = warp::header::<bool>("dnt")
+ .or(warp::any().map(|| true))
+ .unify();
+ ```
+ - Add `content-type` header automatically to replies from `file` and `dir` filters based on file extension.
+ - Add `warp::head()`, `warp::options()`, and `warp::patch()` as new Method filters.
+ - Try to use OS blocksize in `warp::fs` filters.
+- **Fixes**:
+ - Chaining filters that try to consume the request body will log that the body is already consumed, and return a `500 Internal Server Error` rejection.
+
+### v0.1.1 (August 7, 2018)
+
+- **Features**:
+ - Add `warp::query::raw()` filter to get query as a `String`.
+ - Add `Filter::recover()` to ease customizing of rejected responses.
+ - Add `warp::header::headers_clone()` filter to get a clone of request's `HeaderMap`.
+ - Add `warp::path::tail()` filter to get remaining "tail" of the request path.
+- **Fixes**:
+ - URL decode path segments in `warp::fs` filters.
+
+
+## v0.1.0 (August 1, 2018)
+
+- Intial release.
diff --git a/third_party/rust/warp/Cargo.lock b/third_party/rust/warp/Cargo.lock
new file mode 100644
index 0000000000..c35f495174
--- /dev/null
+++ b/third_party/rust/warp/Cargo.lock
@@ -0,0 +1,1633 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "aho-corasick"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "autocfg"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "autocfg"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "base64"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "base64"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "block-buffer"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "buf_redux"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "byte-tools"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "byteorder"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "bytes"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "c2-chacha"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "chrono"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cloudabi"
+version = "0.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "digest"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "dtoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "env_logger"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fake-simd"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fnv"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fuchsia-cprng"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "fuchsia-zircon"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "fuchsia-zircon-sys"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-task"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "futures-util"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "h2"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "handlebars"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "headers"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "headers-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "headers-core"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "http"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "http-body"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "httparse"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "humantime"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "hyper"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "idna"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "input_buffer"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "iovec"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "itoa"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "js-sys"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "kernel32-sys"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.67"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "listenfd"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "log"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "maplit"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "matches"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "memchr"
+version = "2.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "mime"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "mime_guess"
+version = "1.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mime_guess"
+version = "2.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "mio"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "miow"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "multipart"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime_guess 1.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "opaque-debug"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "percent-encoding"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pest"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_derive"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_generator 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_generator"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pest_meta"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.7.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pin-project"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0-alpha.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "pretty_env_logger"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro-nested"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quote"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_isaac"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_jitter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_os"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rdrand"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "regex"
+version = "1.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ring"
+version = "0.16.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "web-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustls"
+version = "0.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "safemem"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "sct"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "serde_derive"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.48"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "sha-1"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "siphasher"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "slab"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "smallvec"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syn"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "time"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio"
+version = "0.2.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "tower-service"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "try-lock"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "tungstenite"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "twoway"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "typenum"
+version = "1.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicase"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicase"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-bidi"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-normalization"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "untrusted"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "url"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "urlencoding"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "utf-8"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "uuid"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "version_check"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "warp"
+version = "0.2.2"
+dependencies = [
+ "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "handlebars 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "headers 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "listenfd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "multipart 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-rustls 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tokio-tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "urlencoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen-macro 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen-macro-support 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen-backend 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "web-sys"
+version = "0.3.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "js-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "webpki"
+version = "0.21.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-build"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "ws2_32-sys"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec"
+"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
+"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
+"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
+"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
+"checksum buf_redux 0.8.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f"
+"checksum bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742"
+"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
+"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
+"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
+"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
+"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
+"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
+"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
+"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3"
+"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
+"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
+"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
+"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
+"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
+"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
+"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
+"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
+"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
+"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
+"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
+"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
+"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
+"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
+"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
+"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+"checksum h2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9d5c295d1c0c68e4e42003d75f908f5e16a1edd1cbe0b0d02e4dc2006a384f47"
+"checksum handlebars 3.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ba758d094d31274eb49d15da6f326b96bf3185239a6359bf684f3d5321148900"
+"checksum headers 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a72b4bd7cbbf0c22190e82f02517f456a6b9be24c25a6827b5802e478b8c2d70"
+"checksum headers-core 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
+"checksum hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
+"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b"
+"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
+"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
+"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
+"checksum hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fa1c527bbc634be72aa7ba31e4e4def9bbb020f5416916279b7c705cd838893e"
+"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
+"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
+"checksum input_buffer 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754"
+"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
+"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
+"checksum js-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb931d43e71f560c81badb0191596562bafad2be06a3f9025b845c847c60df5"
+"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
+"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
+"checksum listenfd 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "492158e732f2e2de81c592f0a2427e57e12cd3d59877378fe7af624b6bbe0ca1"
+"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
+"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
+"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
+"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
+"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
+"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
+"checksum mime_guess 1.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3"
+"checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
+"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
+"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
+"checksum multipart 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "136eed74cadb9edd2651ffba732b19a450316b680e4f48d6c79e905799e19d01"
+"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
+"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
+"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
+"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
+"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+"checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
+"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
+"checksum pest_generator 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "27e5277315f6b4f27e0e6744feb5d5ba1891e7164871033d3c8344c6783b349a"
+"checksum pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
+"checksum phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
+"checksum phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
+"checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
+"checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
+"checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c"
+"checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f"
+"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
+"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
+"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
+"checksum pretty_env_logger 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "717ee476b1690853d222af4634056d830b5197ffd747726a9a1eee6da9f49074"
+"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
+"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
+"checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
+"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
+"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
+"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+"checksum rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
+"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
+"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
+"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
+"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
+"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
+"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
+"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
+"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+"checksum regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8"
+"checksum regex-syntax 0.6.16 (registry+https://github.com/rust-lang/crates.io-index)" = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1"
+"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
+"checksum ring 0.16.11 (registry+https://github.com/rust-lang/crates.io-index)" = "741ba1704ae21999c00942f9f5944f801e977f54302af346b596287599ad1862"
+"checksum rustls 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b25a18b1bf7387f0145e7f8324e700805aade3842dd3db2e74e4cdeb4677c09e"
+"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
+"checksum scoped-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+"checksum sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
+"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
+"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
+"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
+"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
+"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
+"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
+"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+"checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
+"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+"checksum syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)" = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859"
+"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
+"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
+"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
+"checksum tokio 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616"
+"checksum tokio-macros 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389"
+"checksum tokio-rustls 0.12.2 (registry+https://github.com/rust-lang/crates.io-index)" = "141afec0978abae6573065a48882c6bae44c5cc61db9b511ac4abf6a09bfd9cc"
+"checksum tokio-tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a"
+"checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
+"checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
+"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
+"checksum tungstenite 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e"
+"checksum twoway 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1"
+"checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9"
+"checksum ucd-trie 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8f00ed7be0c1ff1e24f46c3d2af4859f7e863672ba3a6e92e7cff702bf9f06c2"
+"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
+"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
+"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
+"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece"
+"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
+"checksum urlencoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3df3561629a8bb4c57e5a2e4c43348d9e29c7c29d9b1c4c1f47166deca8f37ed"
+"checksum utf-8 0.7.5 (registry+https://github.com/rust-lang/crates.io-index)" = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+"checksum uuid 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e1436e58182935dcd9ce0add9ea0b558e8a87befe01c1a301e6020aeb0876363"
+"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
+"checksum want 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+"checksum wasm-bindgen 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "3557c397ab5a8e347d434782bcd31fc1483d927a6826804cec05cc792ee2519d"
+"checksum wasm-bindgen-backend 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8"
+"checksum wasm-bindgen-macro 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "0f6fde1d36e75a714b5fe0cffbb78978f222ea6baebb726af13c78869fdb4205"
+"checksum wasm-bindgen-macro-support 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "25bda4168030a6412ea8a047e27238cadf56f0e53516e1e83fec0a8b7c786f6d"
+"checksum wasm-bindgen-shared 0.2.59 (registry+https://github.com/rust-lang/crates.io-index)" = "fc9f36ad51f25b0219a3d4d13b90eb44cd075dff8b6280cca015775d7acaddd8"
+"checksum web-sys 0.3.36 (registry+https://github.com/rust-lang/crates.io-index)" = "721c6263e2c66fd44501cc5efbfa2b7dfa775d13e4ea38c46299646ed1f9c70a"
+"checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef"
+"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
+"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfbf554c6ad11084fb7517daca16cfdcaccbdadba4fc336f032a8b12c2ad80"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
diff --git a/third_party/rust/warp/Cargo.toml b/third_party/rust/warp/Cargo.toml
new file mode 100644
index 0000000000..aac7e0b207
--- /dev/null
+++ b/third_party/rust/warp/Cargo.toml
@@ -0,0 +1,140 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "warp"
+version = "0.2.2"
+authors = ["Sean McArthur <sean@seanmonstar.com>"]
+autoexamples = true
+autotests = true
+description = "serve the web at warp speeds"
+documentation = "https://docs.rs/warp"
+readme = "README.md"
+keywords = ["warp", "server", "http", "hyper"]
+categories = ["web-programming::http-server"]
+license = "MIT"
+repository = "https://github.com/seanmonstar/warp"
+[package.metadata.docs.rs]
+features = ["tls"]
+[profile.bench]
+codegen-units = 1
+incremental = false
+
+[profile.release]
+codegen-units = 1
+incremental = false
+
+[[example]]
+name = "unix_socket"
+required-features = ["tokio/uds"]
+
+[[example]]
+name = "websockets"
+required-features = ["websocket"]
+
+[[example]]
+name = "websockets_chat"
+required-features = ["websocket"]
+
+[[test]]
+name = "multipart"
+required-features = ["multipart"]
+
+[[test]]
+name = "ws"
+required-features = ["websocket"]
+[dependencies.bytes]
+version = "0.5"
+
+[dependencies.futures]
+version = "0.3"
+features = ["alloc"]
+default-features = false
+
+[dependencies.headers]
+version = "0.3"
+
+[dependencies.http]
+version = "0.2"
+
+[dependencies.hyper]
+version = "0.13"
+
+[dependencies.log]
+version = "0.4"
+
+[dependencies.mime]
+version = "0.3"
+
+[dependencies.mime_guess]
+version = "2.0.0"
+
+[dependencies.multipart]
+version = "0.16"
+features = ["server"]
+optional = true
+default-features = false
+
+[dependencies.pin-project]
+version = "0.4.5"
+
+[dependencies.scoped-tls]
+version = "1.0"
+
+[dependencies.serde]
+version = "1.0"
+
+[dependencies.serde_json]
+version = "1.0"
+
+[dependencies.serde_urlencoded]
+version = "0.6"
+
+[dependencies.tokio]
+version = "0.2"
+features = ["blocking", "fs", "stream", "sync", "time"]
+
+[dependencies.tokio-rustls]
+version = "0.12.2"
+optional = true
+
+[dependencies.tokio-tungstenite]
+version = "0.10"
+optional = true
+default-features = false
+
+[dependencies.tower-service]
+version = "0.3"
+
+[dependencies.urlencoding]
+version = "1.0.0"
+[dev-dependencies.handlebars]
+version = "3.0.0"
+
+[dev-dependencies.listenfd]
+version = "0.3"
+
+[dev-dependencies.pretty_env_logger]
+version = "0.3"
+
+[dev-dependencies.serde_derive]
+version = "1.0"
+
+[dev-dependencies.tokio]
+version = "0.2"
+features = ["macros"]
+
+[features]
+default = ["multipart", "websocket"]
+tls = ["tokio-rustls"]
+websocket = ["tokio-tungstenite"]
diff --git a/third_party/rust/warp/LICENSE b/third_party/rust/warp/LICENSE
new file mode 100644
index 0000000000..2c77834221
--- /dev/null
+++ b/third_party/rust/warp/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2018 Sean McArthur
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
diff --git a/third_party/rust/warp/README.md b/third_party/rust/warp/README.md
new file mode 100644
index 0000000000..dd19ba1d81
--- /dev/null
+++ b/third_party/rust/warp/README.md
@@ -0,0 +1,58 @@
+# warp
+
+[![GHA Build Status](https://github.com/seanmonstar/warp/workflows/CI/badge.svg)](https://github.com/seanmonstar/warp/actions?query=workflow%3ACI)
+[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
+[![crates.io](https://img.shields.io/crates/v/warp.svg)](https://crates.io/crates/warp)
+[![Released API docs](https://docs.rs/warp/badge.svg)](https://docs.rs/warp)
+
+A super-easy, composable, web server framework for warp speeds.
+
+The fundamental building block of `warp` is the `Filter`: they can be combined
+and composed to express rich requirements on requests.
+
+Thanks to its `Filter` system, warp provides these out of the box:
+
+* Path routing and parameter extraction
+* Header requirements and extraction
+* Query string deserialization
+* JSON and Form bodies
+* Multipart form data
+* Static Files and Directories
+* Websockets
+* Access logging
+
+Since it builds on top of [hyper](https://hyper.rs), you automatically get:
+
+- HTTP/1
+- HTTP/2
+- Asynchronous
+- One of the fastest HTTP implementations
+- Tested and **correct**
+
+## Example
+
+Add warp and Tokio to your dependencies:
+
+```toml
+tokio = { version = "0.2", features = ["macros"] }
+warp = "0.2"
+```
+
+And then get started in your `main.rs`:
+
+```rust
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ // GET /hello/warp => 200 OK with body "Hello, warp!"
+ let hello = warp::path!("hello" / String)
+ .map(|name| format!("Hello, {}!", name));
+
+ warp::serve(hello)
+ .run(([127, 0, 0, 1], 3030))
+ .await;
+}
+```
+
+For more information you can check the [docs](https://docs.rs/warp) or the [examples](https://github.com/seanmonstar/warp/tree/master/examples).
diff --git a/third_party/rust/warp/examples/README.md b/third_party/rust/warp/examples/README.md
new file mode 100644
index 0000000000..3f5ecb95eb
--- /dev/null
+++ b/third_party/rust/warp/examples/README.md
@@ -0,0 +1,41 @@
+# Examples
+
+Welcome to the examples! These show off `warp`'s functionality and explain how to use it.
+
+## Getting Started
+
+- [`hello.rs`](./hello.rs) - Just a basic "Hello World" API
+- [`routing.rs`](./routing.rs) - Builds up a more complex set of routes and shows how to combine filters
+- [`body.rs`](./body.rs) - What's a good API without parsing data from the request body?
+- [`headers.rs`](./headers.rs) - Parsing data from the request headers
+- [`rejections.rs`](./rejections.rs) - Your APIs are obviously perfect, but for silly others who call them incorrectly you'll want to define errors for them
+- [`futures.rs`](./futures.rs) - Wait, wait! ... Or how to integrate futures into filters
+- [`todos.rs`](./todos.rs) - Putting this all together with a proper app
+
+## Further Use Cases
+
+### Serving HTML and Other Files
+
+- [`file.rs`](./file.rs) - Serving static files
+- [`dir.rs`](./dir.rs) - Or a whole directory of files
+- [`handlebars_template.rs`](./handlebars_template.rs) - Using Handlebars to fill in an HTML template
+
+### Websockets
+
+Hooray! `warp` also includes built-in support for WebSockets
+
+- [`websockets.rs`](./websockets.rs) - Basic handling of a WebSocket upgrade
+- [`websockets_chat.rs`](./websockets_chat.rs) - Full WebSocket app
+
+### Server-Side Events
+
+- [`sse.rs`](./sse.rs) - Basic Server-Side Event
+- [`sse_chat.rs`](./sse_chat.rs) - Full SSE app
+
+### TLS
+
+- [`tls.rs`](./tls.rs) - can i haz security?
+
+### Autoreloading
+
+- [`autoreload.rs`](./autoreload.rs) - Change some code and watch the server reload automatically!
diff --git a/third_party/rust/warp/examples/autoreload.rs b/third_party/rust/warp/examples/autoreload.rs
new file mode 100644
index 0000000000..4aabfdb6a2
--- /dev/null
+++ b/third_party/rust/warp/examples/autoreload.rs
@@ -0,0 +1,38 @@
+#![deny(warnings)]
+use hyper::server::Server;
+use listenfd::ListenFd;
+use std::convert::Infallible;
+use warp::Filter;
+
+extern crate listenfd;
+/// You'll need to install `systemfd` and `cargo-watch`:
+/// ```
+/// cargo install systemfd cargo-watch
+/// ```
+/// And run with:
+/// ```
+/// systemfd --no-pid -s http::3030 -- cargo watch -x 'run --example autoreload'
+/// ```
+#[tokio::main]
+async fn main() {
+ // Match any request and return hello world!
+ let routes = warp::any().map(|| "Hello, World!");
+
+ // hyper let's us build a server from a TcpListener (which will be
+ // useful shortly). Thus, we'll need to convert our `warp::Filter` into
+ // a `hyper::service::MakeService` for use with a `hyper::server::Server`.
+ let svc = warp::service(routes);
+ let make_svc = hyper::service::make_service_fn(|_: _| async move { Ok::<_, Infallible>(svc) });
+
+ let mut listenfd = ListenFd::from_env();
+ // if listenfd doesn't take a TcpListener (i.e. we're not running via
+ // the command above), we fall back to explicitly binding to a given
+ // host:port.
+ let server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
+ Server::from_tcp(l).unwrap()
+ } else {
+ Server::bind(&([127, 0, 0, 1], 3030).into())
+ };
+
+ server.serve(make_svc).await.unwrap();
+}
diff --git a/third_party/rust/warp/examples/body.rs b/third_party/rust/warp/examples/body.rs
new file mode 100644
index 0000000000..174d928ab8
--- /dev/null
+++ b/third_party/rust/warp/examples/body.rs
@@ -0,0 +1,30 @@
+#![deny(warnings)]
+
+use serde_derive::{Deserialize, Serialize};
+
+use warp::Filter;
+
+#[derive(Deserialize, Serialize)]
+struct Employee {
+ name: String,
+ rate: u32,
+}
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // POST /employees/:rate {"name":"Sean","rate":2}
+ let promote = warp::post()
+ .and(warp::path("employees"))
+ .and(warp::path::param::<u32>())
+ // Only accept bodies smaller than 16kb...
+ .and(warp::body::content_length_limit(1024 * 16))
+ .and(warp::body::json())
+ .map(|rate, mut employee: Employee| {
+ employee.rate = rate;
+ warp::reply::json(&employee)
+ });
+
+ warp::serve(promote).run(([127, 0, 0, 1], 3030)).await
+}
diff --git a/third_party/rust/warp/examples/dir.rs b/third_party/rust/warp/examples/dir.rs
new file mode 100644
index 0000000000..30261a220e
--- /dev/null
+++ b/third_party/rust/warp/examples/dir.rs
@@ -0,0 +1,10 @@
+#![deny(warnings)]
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ warp::serve(warp::fs::dir("examples/dir"))
+ .run(([127, 0, 0, 1], 3030))
+ .await;
+}
diff --git a/third_party/rust/warp/examples/dir/another.html b/third_party/rust/warp/examples/dir/another.html
new file mode 100644
index 0000000000..941c9a5937
--- /dev/null
+++ b/third_party/rust/warp/examples/dir/another.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>dir/another.html</title>
+ </head>
+ <body>
+ <h1>Welcome to Another Page</h1>
+ <a href="/">back</a>
+ </body>
+</html> \ No newline at end of file
diff --git a/third_party/rust/warp/examples/dir/index.html b/third_party/rust/warp/examples/dir/index.html
new file mode 100644
index 0000000000..cb86323446
--- /dev/null
+++ b/third_party/rust/warp/examples/dir/index.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>dir/index.html</title>
+ </head>
+ <body>
+ <h1>Welcome to Dir</h1>
+ <a href="/another.html">another page</a>
+ </body>
+</html> \ No newline at end of file
diff --git a/third_party/rust/warp/examples/dyn_reply.rs b/third_party/rust/warp/examples/dyn_reply.rs
new file mode 100644
index 0000000000..4f59cf8ba9
--- /dev/null
+++ b/third_party/rust/warp/examples/dyn_reply.rs
@@ -0,0 +1,17 @@
+#![deny(warnings)]
+use warp::{http::StatusCode, Filter};
+
+async fn dyn_reply(word: String) -> Result<Box<dyn warp::Reply>, warp::Rejection> {
+ if &word == "hello" {
+ Ok(Box::new("world"))
+ } else {
+ Ok(Box::new(StatusCode::BAD_REQUEST))
+ }
+}
+
+#[tokio::main]
+async fn main() {
+ let routes = warp::path::param().and_then(dyn_reply);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/file.rs b/third_party/rust/warp/examples/file.rs
new file mode 100644
index 0000000000..a0cf2afa45
--- /dev/null
+++ b/third_party/rust/warp/examples/file.rs
@@ -0,0 +1,21 @@
+#![deny(warnings)]
+
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ let readme = warp::get()
+ .and(warp::path::end())
+ .and(warp::fs::file("./README.md"));
+
+ // dir already requires GET...
+ let examples = warp::path("ex").and(warp::fs::dir("./examples/"));
+
+ // GET / => README.md
+ // GET /ex/... => ./examples/..
+ let routes = readme.or(examples);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/futures.rs b/third_party/rust/warp/examples/futures.rs
new file mode 100644
index 0000000000..0134280934
--- /dev/null
+++ b/third_party/rust/warp/examples/futures.rs
@@ -0,0 +1,37 @@
+#![deny(warnings)]
+
+use std::convert::Infallible;
+use std::str::FromStr;
+use std::time::Duration;
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ // Match `/:Seconds`...
+ let routes = warp::path::param()
+ // and_then create a `Future` that will simply wait N seconds...
+ .and_then(sleepy);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
+
+async fn sleepy(Seconds(seconds): Seconds) -> Result<impl warp::Reply, Infallible> {
+ tokio::time::delay_for(Duration::from_secs(seconds)).await;
+ Ok(format!("I waited {} seconds!", seconds))
+}
+
+/// A newtype to enforce our maximum allowed seconds.
+struct Seconds(u64);
+
+impl FromStr for Seconds {
+ type Err = ();
+ fn from_str(src: &str) -> Result<Self, Self::Err> {
+ src.parse::<u64>().map_err(|_| ()).and_then(|num| {
+ if num <= 5 {
+ Ok(Seconds(num))
+ } else {
+ Err(())
+ }
+ })
+ }
+}
diff --git a/third_party/rust/warp/examples/handlebars_template.rs b/third_party/rust/warp/examples/handlebars_template.rs
new file mode 100644
index 0000000000..3740a32c34
--- /dev/null
+++ b/third_party/rust/warp/examples/handlebars_template.rs
@@ -0,0 +1,58 @@
+#![deny(warnings)]
+use std::sync::Arc;
+
+use handlebars::Handlebars;
+use serde::Serialize;
+use serde_json::json;
+use warp::Filter;
+
+struct WithTemplate<T: Serialize> {
+ name: &'static str,
+ value: T,
+}
+
+fn render<T>(template: WithTemplate<T>, hbs: Arc<Handlebars>) -> impl warp::Reply
+where
+ T: Serialize,
+{
+ let render = hbs
+ .render(template.name, &template.value)
+ .unwrap_or_else(|err| err.to_string());
+ warp::reply::html(render)
+}
+
+#[tokio::main]
+async fn main() {
+ let template = "<!DOCTYPE html>
+ <html>
+ <head>
+ <title>Warp Handlebars template example</title>
+ </head>
+ <body>
+ <h1>Hello {{user}}!</h1>
+ </body>
+ </html>";
+
+ let mut hb = Handlebars::new();
+ // register the template
+ hb.register_template_string("template.html", template)
+ .unwrap();
+
+ // Turn Handlebars instance into a Filter so we can combine it
+ // easily with others...
+ let hb = Arc::new(hb);
+
+ // Create a reusable closure to render template
+ let handlebars = move |with_template| render(with_template, hb.clone());
+
+ //GET /
+ let route = warp::get()
+ .and(warp::path::end())
+ .map(|| WithTemplate {
+ name: "template.html",
+ value: json!({"user" : "Warp"}),
+ })
+ .map(handlebars);
+
+ warp::serve(route).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/headers.rs b/third_party/rust/warp/examples/headers.rs
new file mode 100644
index 0000000000..2b3dca7b50
--- /dev/null
+++ b/third_party/rust/warp/examples/headers.rs
@@ -0,0 +1,27 @@
+#![deny(warnings)]
+use std::net::SocketAddr;
+use warp::Filter;
+
+/// Create a server that requires header conditions:
+///
+/// - `Host` is a `SocketAddr`
+/// - `Accept` is exactly `*/*`
+///
+/// If these conditions don't match, a 404 is returned.
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // For this example, we assume no DNS was used,
+ // so the Host header should be an address.
+ let host = warp::header::<SocketAddr>("host");
+
+ // Match when we get `accept: */*` exactly.
+ let accept_stars = warp::header::exact("accept", "*/*");
+
+ let routes = host
+ .and(accept_stars)
+ .map(|addr| format!("accepting stars on {}", addr));
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/hello.rs b/third_party/rust/warp/examples/hello.rs
new file mode 100644
index 0000000000..27aa2e51c8
--- /dev/null
+++ b/third_party/rust/warp/examples/hello.rs
@@ -0,0 +1,10 @@
+#![deny(warnings)]
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ // Match any request and return hello world!
+ let routes = warp::any().map(|| "Hello, World!");
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/rejections.rs b/third_party/rust/warp/examples/rejections.rs
new file mode 100644
index 0000000000..286b7772b6
--- /dev/null
+++ b/third_party/rust/warp/examples/rejections.rs
@@ -0,0 +1,90 @@
+#![deny(warnings)]
+
+use std::convert::Infallible;
+use std::num::NonZeroU16;
+
+use serde_derive::Serialize;
+use warp::http::StatusCode;
+use warp::{reject, Filter, Rejection, Reply};
+
+/// Rejections represent cases where a filter should not continue processing
+/// the request, but a different filter *could* process it.
+#[tokio::main]
+async fn main() {
+ let math = warp::path!("math" / u16)
+ .and(div_by())
+ .map(|num: u16, denom: NonZeroU16| {
+ warp::reply::json(&Math {
+ op: format!("{} / {}", num, denom),
+ output: num / denom.get(),
+ })
+ });
+
+ let routes = warp::get().and(math).recover(handle_rejection);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
+
+/// Extract a denominator from a "div-by" header, or reject with DivideByZero.
+fn div_by() -> impl Filter<Extract = (NonZeroU16,), Error = Rejection> + Copy {
+ warp::header::<u16>("div-by").and_then(|n: u16| async move {
+ if let Some(denom) = NonZeroU16::new(n) {
+ Ok(denom)
+ } else {
+ Err(reject::custom(DivideByZero))
+ }
+ })
+}
+
+#[derive(Debug)]
+struct DivideByZero;
+
+impl reject::Reject for DivideByZero {}
+
+// JSON replies
+
+/// A successful math operation.
+#[derive(Serialize)]
+struct Math {
+ op: String,
+ output: u16,
+}
+
+/// An API error serializable to JSON.
+#[derive(Serialize)]
+struct ErrorMessage {
+ code: u16,
+ message: String,
+}
+
+// This function receives a `Rejection` and tries to return a custom
+// value, otherwise simply passes the rejection along.
+async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
+ let code;
+ let message;
+
+ if err.is_not_found() {
+ code = StatusCode::NOT_FOUND;
+ message = "NOT_FOUND";
+ } else if let Some(DivideByZero) = err.find() {
+ code = StatusCode::BAD_REQUEST;
+ message = "DIVIDE_BY_ZERO";
+ } else if let Some(_) = err.find::<warp::reject::MethodNotAllowed>() {
+ // We can handle a specific error, here METHOD_NOT_ALLOWED,
+ // and render it however we want
+ code = StatusCode::METHOD_NOT_ALLOWED;
+ message = "METHOD_NOT_ALLOWED";
+ } else {
+ // We should have expected this... Just log and say its a 500
+ eprintln!("unhandled rejection: {:?}", err);
+ code = StatusCode::INTERNAL_SERVER_ERROR;
+ message = "UNHANDLED_REJECTION";
+ }
+
+ let json = warp::reply::json(&ErrorMessage {
+ code: code.as_u16(),
+ message: message.into(),
+ });
+
+ Ok(warp::reply::with_status(json, code))
+}
diff --git a/third_party/rust/warp/examples/returning.rs b/third_party/rust/warp/examples/returning.rs
new file mode 100644
index 0000000000..f4f61e60fc
--- /dev/null
+++ b/third_party/rust/warp/examples/returning.rs
@@ -0,0 +1,20 @@
+use warp::{filters::BoxedFilter, Filter, Rejection, Reply};
+
+// Option 1: BoxedFilter
+// Note that this may be useful for shortening compile times when you are composing many filters.
+// Boxing the filters will use dynamic dispatch and speed up compilation while
+// making it slightly slower at runtime.
+pub fn assets_filter() -> BoxedFilter<(impl Reply,)> {
+ warp::path("assets").and(warp::fs::dir("./assets")).boxed()
+}
+
+// Option 2: impl Filter + Clone
+pub fn index_filter() -> impl Filter<Extract = (&'static str,), Error = Rejection> + Clone {
+ warp::path::end().map(|| "Index page")
+}
+
+#[tokio::main]
+async fn main() {
+ let routes = index_filter().or(assets_filter());
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/routing.rs b/third_party/rust/warp/examples/routing.rs
new file mode 100644
index 0000000000..6d1caa5bfe
--- /dev/null
+++ b/third_party/rust/warp/examples/routing.rs
@@ -0,0 +1,92 @@
+#![deny(warnings)]
+
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // We'll start simple, and gradually show how you combine these powers
+ // into super powers!
+
+ // GET /hi
+ let hi = warp::path("hi").map(|| "Hello, World!");
+
+ // How about multiple segments? First, we could use the `path!` macro:
+ //
+ // GET /hello/from/warp
+ let hello_from_warp = warp::path!("hello" / "from" / "warp").map(|| "Hello from warp!");
+
+ // Fine, but how do I handle parameters in paths?
+ //
+ // GET /sum/:u32/:u32
+ let sum = warp::path!("sum" / u32 / u32).map(|a, b| format!("{} + {} = {}", a, b, a + b));
+
+ // Any type that implements FromStr can be used, and in any order:
+ //
+ // GET /:u16/times/:u16
+ let times =
+ warp::path!(u16 / "times" / u16).map(|a, b| format!("{} times {} = {}", a, b, a * b));
+
+ // Oh shoot, those math routes should be mounted at a different path,
+ // is that possible? Yep.
+ //
+ // GET /math/sum/:u32/:u32
+ // GET /math/:u16/times/:u16
+ let math = warp::path("math");
+ let _sum = math.and(sum);
+ let _times = math.and(times);
+
+ // What! And? What's that do?
+ //
+ // It combines the filters in a sort of "this and then that" order. In
+ // fact, it's exactly what the `path!` macro has been doing internally.
+ //
+ // GET /bye/:string
+ let bye = warp::path("bye")
+ .and(warp::path::param())
+ .map(|name: String| format!("Good bye, {}!", name));
+
+ // Ah, can filters do things besides `and`?
+ //
+ // Why, yes they can! They can also `or`! As you might expect, `or` creates
+ // a "this or else that" chain of filters. If the first doesn't succeed,
+ // then it tries the other.
+ //
+ // So, those `math` routes could have been mounted all as one, with `or`.
+ //
+ // GET /math/sum/:u32/:u32
+ // GET /math/:u16/times/:u16
+ let math = warp::path("math").and(sum.or(times));
+
+ // We can use the end() filter to match a shorter path
+ let help = warp::path("math")
+ // Careful! Omitting the following line would make this filter match
+ // requests to /math/sum/:u32/:u32 and /math/:u16/times/:u16
+ .and(warp::path::end())
+ .map(|| "This is the Math API. Try calling /math/sum/:u32/:u32 or /math/:u16/times/:u16");
+ let math = help.or(math);
+
+ // Let's let people know that the `sum` and `times` routes are under `math`.
+ let sum = sum.map(|output| format!("(This route has moved to /math/sum/:u16/:u16) {}", output));
+ let times =
+ times.map(|output| format!("(This route has moved to /math/:u16/times/:u16) {}", output));
+
+ // It turns out, using `or` is how you combine everything together into
+ // a single API. (We also actually haven't been enforcing the that the
+ // method is GET, so we'll do that too!)
+ //
+ // GET /hi
+ // GET /hello/from/warp
+ // GET /bye/:string
+ // GET /math/sum/:u32/:u32
+ // GET /math/:u16/times/:u16
+
+ let routes = warp::get().and(hi.or(hello_from_warp).or(bye).or(math).or(sum).or(times));
+
+ // Note that composing filters for many routes may increase compile times (because it uses a lot of generics).
+ // If you wish to use dynamic dispatch instead and speed up compile times while
+ // making it slightly slower at runtime, you can use Filter::boxed().
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/sse.rs b/third_party/rust/warp/examples/sse.rs
new file mode 100644
index 0000000000..9c883a7c9a
--- /dev/null
+++ b/third_party/rust/warp/examples/sse.rs
@@ -0,0 +1,28 @@
+use futures::StreamExt;
+use std::convert::Infallible;
+use std::time::Duration;
+use tokio::time::interval;
+use warp::{sse::ServerSentEvent, Filter};
+
+// create server-sent event
+fn sse_counter(counter: u64) -> Result<impl ServerSentEvent, Infallible> {
+ Ok(warp::sse::data(counter))
+}
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ let routes = warp::path("ticks").and(warp::get()).map(|| {
+ let mut counter: u64 = 0;
+ // create server event source
+ let event_stream = interval(Duration::from_secs(1)).map(move |_| {
+ counter += 1;
+ sse_counter(counter)
+ });
+ // reply using server-sent events
+ warp::sse::reply(event_stream)
+ });
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/sse_chat.rs b/third_party/rust/warp/examples/sse_chat.rs
new file mode 100644
index 0000000000..693a87f552
--- /dev/null
+++ b/third_party/rust/warp/examples/sse_chat.rs
@@ -0,0 +1,164 @@
+use futures::{Stream, StreamExt};
+use std::collections::HashMap;
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc, Mutex,
+};
+use tokio::sync::mpsc;
+use warp::{sse::ServerSentEvent, Filter};
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // Keep track of all connected users, key is usize, value
+ // is an event stream sender.
+ let users = Arc::new(Mutex::new(HashMap::new()));
+ // Turn our "state" into a new Filter...
+ let users = warp::any().map(move || users.clone());
+
+ // POST /chat -> send message
+ let chat_send = warp::path("chat")
+ .and(warp::post())
+ .and(warp::path::param::<usize>())
+ .and(warp::body::content_length_limit(500))
+ .and(
+ warp::body::bytes().and_then(|body: bytes::Bytes| async move {
+ std::str::from_utf8(&body)
+ .map(String::from)
+ .map_err(|_e| warp::reject::custom(NotUtf8))
+ }),
+ )
+ .and(users.clone())
+ .map(|my_id, msg, users| {
+ user_message(my_id, msg, &users);
+ warp::reply()
+ });
+
+ // GET /chat -> messages stream
+ let chat_recv = warp::path("chat").and(warp::get()).and(users).map(|users| {
+ // reply using server-sent events
+ let stream = user_connected(users);
+ warp::sse::reply(warp::sse::keep_alive().stream(stream))
+ });
+
+ // GET / -> index html
+ let index = warp::path::end().map(|| {
+ warp::http::Response::builder()
+ .header("content-type", "text/html; charset=utf-8")
+ .body(INDEX_HTML)
+ });
+
+ let routes = index.or(chat_recv).or(chat_send);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
+
+/// Our global unique user id counter.
+static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
+
+/// Message variants.
+#[derive(Debug)]
+enum Message {
+ UserId(usize),
+ Reply(String),
+}
+
+#[derive(Debug)]
+struct NotUtf8;
+impl warp::reject::Reject for NotUtf8 {}
+
+/// Our state of currently connected users.
+///
+/// - Key is their id
+/// - Value is a sender of `Message`
+type Users = Arc<Mutex<HashMap<usize, mpsc::UnboundedSender<Message>>>>;
+
+fn user_connected(
+ users: Users,
+) -> impl Stream<Item = Result<impl ServerSentEvent + Send + 'static, warp::Error>> + Send + 'static
+{
+ // Use a counter to assign a new unique ID for this user.
+ let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
+
+ eprintln!("new chat user: {}", my_id);
+
+ // Use an unbounded channel to handle buffering and flushing of messages
+ // to the event source...
+ let (tx, rx) = mpsc::unbounded_channel();
+
+ tx.send(Message::UserId(my_id))
+ // rx is right above, so this cannot fail
+ .unwrap();
+
+ // Save the sender in our list of connected users.
+ users.lock().unwrap().insert(my_id, tx);
+
+ // Convert messages into Server-Sent Events and return resulting stream.
+ rx.map(|msg| match msg {
+ Message::UserId(my_id) => Ok((warp::sse::event("user"), warp::sse::data(my_id)).into_a()),
+ Message::Reply(reply) => Ok(warp::sse::data(reply).into_b()),
+ })
+}
+
+fn user_message(my_id: usize, msg: String, users: &Users) {
+ let new_msg = format!("<User#{}>: {}", my_id, msg);
+
+ // New message from this user, send it to everyone else (except same uid)...
+ //
+ // We use `retain` instead of a for loop so that we can reap any user that
+ // appears to have disconnected.
+ users.lock().unwrap().retain(|uid, tx| {
+ if my_id == *uid {
+ // don't send to same user, but do retain
+ true
+ } else {
+ // If not `is_ok`, the SSE stream is gone, and so don't retain
+ tx.send(Message::Reply(new_msg.clone())).is_ok()
+ }
+ });
+}
+
+static INDEX_HTML: &str = r#"
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Warp Chat</title>
+ </head>
+ <body>
+ <h1>warp chat</h1>
+ <div id="chat">
+ <p><em>Connecting...</em></p>
+ </div>
+ <input type="text" id="text" />
+ <button type="button" id="send">Send</button>
+ <script type="text/javascript">
+ var uri = 'http://' + location.host + '/chat';
+ var sse = new EventSource(uri);
+ function message(data) {
+ var line = document.createElement('p');
+ line.innerText = data;
+ chat.appendChild(line);
+ }
+ sse.onopen = function() {
+ chat.innerHTML = "<p><em>Connected!</em></p>";
+ }
+ var user_id;
+ sse.addEventListener("user", function(msg) {
+ user_id = msg.data;
+ });
+ sse.onmessage = function(msg) {
+ message(msg.data);
+ };
+ send.onclick = function() {
+ var msg = text.value;
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", uri + '/' + user_id, true);
+ xhr.send(msg);
+ text.value = '';
+ message('<You>: ' + msg);
+ };
+ </script>
+ </body>
+</html>
+"#;
diff --git a/third_party/rust/warp/examples/tls.rs b/third_party/rust/warp/examples/tls.rs
new file mode 100644
index 0000000000..9000263842
--- /dev/null
+++ b/third_party/rust/warp/examples/tls.rs
@@ -0,0 +1,24 @@
+#![deny(warnings)]
+
+// Don't copy this `cfg`, it's only needed because this file is within
+// the warp repository.
+#[cfg(feature = "tls")]
+#[tokio::main]
+async fn main() {
+ use warp::Filter;
+
+ // Match any request and return hello world!
+ let routes = warp::any().map(|| "Hello, World!");
+
+ warp::serve(routes)
+ .tls()
+ .cert_path("examples/tls/cert.pem")
+ .key_path("examples/tls/key.rsa")
+ .run(([127, 0, 0, 1], 3030))
+ .await;
+}
+
+#[cfg(not(feature = "tls"))]
+fn main() {
+ eprintln!("Requires the `tls` feature.");
+}
diff --git a/third_party/rust/warp/examples/tls/cert.pem b/third_party/rust/warp/examples/tls/cert.pem
new file mode 100644
index 0000000000..03af12ff81
--- /dev/null
+++ b/third_party/rust/warp/examples/tls/cert.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEADCCAmigAwIBAgICAcgwDQYJKoZIhvcNAQELBQAwLDEqMCgGA1UEAwwhcG9u
+eXRvd24gUlNBIGxldmVsIDIgaW50ZXJtZWRpYXRlMB4XDTE2MDgxMzE2MDcwNFoX
+DTIyMDIwMzE2MDcwNFowGTEXMBUGA1UEAwwOdGVzdHNlcnZlci5jb20wggEiMA0G
+CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCpVhh1/FNP2qvWenbZSghari/UThwe
+dynfnHG7gc3JmygkEdErWBO/CHzHgsx7biVE5b8sZYNEDKFojyoPHGWK2bQM/FTy
+niJCgNCLdn6hUqqxLAml3cxGW77hAWu94THDGB1qFe+eFiAUnDmob8gNZtAzT6Ky
+b/JGJdrEU0wj+Rd7wUb4kpLInNH/Jc+oz2ii2AjNbGOZXnRz7h7Kv3sO9vABByYe
+LcCj3qnhejHMqVhbAT1MD6zQ2+YKBjE52MsQKU/xhUpu9KkUyLh0cxkh3zrFiKh4
+Vuvtc+n7aeOv2jJmOl1dr0XLlSHBlmoKqH6dCTSbddQLmlK7dms8vE01AgMBAAGj
+gb4wgbswDAYDVR0TAQH/BAIwADALBgNVHQ8EBAMCBsAwHQYDVR0OBBYEFMeUzGYV
+bXwJNQVbY1+A8YXYZY8pMEIGA1UdIwQ7MDmAFJvEsUi7+D8vp8xcWvnEdVBGkpoW
+oR6kHDAaMRgwFgYDVQQDDA9wb255dG93biBSU0EgQ0GCAXswOwYDVR0RBDQwMoIO
+dGVzdHNlcnZlci5jb22CFXNlY29uZC50ZXN0c2VydmVyLmNvbYIJbG9jYWxob3N0
+MA0GCSqGSIb3DQEBCwUAA4IBgQBsk5ivAaRAcNgjc7LEiWXFkMg703AqDDNx7kB1
+RDgLalLvrjOfOp2jsDfST7N1tKLBSQ9bMw9X4Jve+j7XXRUthcwuoYTeeo+Cy0/T
+1Q78ctoX74E2nB958zwmtRykGrgE/6JAJDwGcgpY9kBPycGxTlCN926uGxHsDwVs
+98cL6ZXptMLTR6T2XP36dAJZuOICSqmCSbFR8knc/gjUO36rXTxhwci8iDbmEVaf
+BHpgBXGU5+SQ+QM++v6bHGf4LNQC5NZ4e4xvGax8ioYu/BRsB/T3Lx+RlItz4zdU
+XuxCNcm3nhQV2ZHquRdbSdoyIxV5kJXel4wCmOhWIq7A2OBKdu5fQzIAzzLi65EN
+RPAKsKB4h7hGgvciZQ7dsMrlGw0DLdJ6UrFyiR5Io7dXYT/+JP91lP5xsl6Lhg9O
+FgALt7GSYRm2cZdgi9pO9rRr83Br1VjQT1vHz6yoZMXSqc4A2zcN2a2ZVq//rHvc
+FZygs8miAhWPzqnpmgTj1cPiU1M=
+-----END CERTIFICATE-----
diff --git a/third_party/rust/warp/examples/tls/key.rsa b/third_party/rust/warp/examples/tls/key.rsa
new file mode 100644
index 0000000000..b13bf5d07f
--- /dev/null
+++ b/third_party/rust/warp/examples/tls/key.rsa
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpAIBAAKCAQEAqVYYdfxTT9qr1np22UoIWq4v1E4cHncp35xxu4HNyZsoJBHR
+K1gTvwh8x4LMe24lROW/LGWDRAyhaI8qDxxlitm0DPxU8p4iQoDQi3Z+oVKqsSwJ
+pd3MRlu+4QFrveExwxgdahXvnhYgFJw5qG/IDWbQM0+ism/yRiXaxFNMI/kXe8FG
++JKSyJzR/yXPqM9ootgIzWxjmV50c+4eyr97DvbwAQcmHi3Ao96p4XoxzKlYWwE9
+TA+s0NvmCgYxOdjLEClP8YVKbvSpFMi4dHMZId86xYioeFbr7XPp+2njr9oyZjpd
+Xa9Fy5UhwZZqCqh+nQk0m3XUC5pSu3ZrPLxNNQIDAQABAoIBAFKtZJgGsK6md4vq
+kyiYSufrcBLaaEQ/rkQtYCJKyC0NAlZKFLRy9oEpJbNLm4cQSkYPXn3Qunx5Jj2k
+2MYz+SgIDy7f7KHgr52Ew020dzNQ52JFvBgt6NTZaqL1TKOS1fcJSSNIvouTBerK
+NCSXHzfb4P+MfEVe/w1c4ilE+kH9SzdEo2jK/sRbzHIY8TX0JbmQ4SCLLayr22YG
+usIxtIYcWt3MMP/G2luRnYzzBCje5MXdpAhlHLi4TB6x4h5PmBKYc57uOVNngKLd
+YyrQKcszW4Nx5v0a4HG3A5EtUXNCco1+5asXOg2lYphQYVh2R+1wgu5WiDjDVu+6
+EYgjFSkCgYEA0NBk6FDoxE/4L/4iJ4zIhu9BptN8Je/uS5c6wRejNC/VqQyw7SHb
+hRFNrXPvq5Y+2bI/DxtdzZLKAMXOMjDjj0XEgfOIn2aveOo3uE7zf1i+njxwQhPu
+uSYA9AlBZiKGr2PCYSDPnViHOspVJjxRuAgyWM1Qf+CTC0D95aj0oz8CgYEAz5n4
+Cb3/WfUHxMJLljJ7PlVmlQpF5Hk3AOR9+vtqTtdxRjuxW6DH2uAHBDdC3OgppUN4
+CFj55kzc2HUuiHtmPtx8mK6G+otT7Lww+nLSFL4PvZ6CYxqcio5MPnoYd+pCxrXY
+JFo2W7e4FkBOxb5PF5So5plg+d0z/QiA7aFP1osCgYEAtgi1rwC5qkm8prn4tFm6
+hkcVCIXc+IWNS0Bu693bXKdGr7RsmIynff1zpf4ntYGpEMaeymClCY0ppDrMYlzU
+RBYiFNdlBvDRj6s/H+FTzHRk2DT/99rAhY9nzVY0OQFoQIXK8jlURGrkmI/CYy66
+XqBmo5t4zcHM7kaeEBOWEKkCgYAYnO6VaRtPNQfYwhhoFFAcUc+5t+AVeHGW/4AY
+M5qlAlIBu64JaQSI5KqwS0T4H+ZgG6Gti68FKPO+DhaYQ9kZdtam23pRVhd7J8y+
+xMI3h1kiaBqZWVxZ6QkNFzizbui/2mtn0/JB6YQ/zxwHwcpqx0tHG8Qtm5ZAV7PB
+eLCYhQKBgQDALJxU/6hMTdytEU5CLOBSMby45YD/RrfQrl2gl/vA0etPrto4RkVq
+UrkDO/9W4mZORClN3knxEFSTlYi8YOboxdlynpFfhcs82wFChs+Ydp1eEsVHAqtu
+T+uzn0sroycBiBfVB949LExnzGDFUkhG0i2c2InarQYLTsIyHCIDEA==
+-----END RSA PRIVATE KEY-----
diff --git a/third_party/rust/warp/examples/todos.rs b/third_party/rust/warp/examples/todos.rs
new file mode 100644
index 0000000000..d32db3a245
--- /dev/null
+++ b/third_party/rust/warp/examples/todos.rs
@@ -0,0 +1,295 @@
+#![deny(warnings)]
+
+use std::env;
+use warp::Filter;
+
+/// Provides a RESTful web server managing some Todos.
+///
+/// API will be:
+///
+/// - `GET /todos`: return a JSON list of Todos.
+/// - `POST /todos`: create a new Todo.
+/// - `PUT /todos/:id`: update a specific Todo.
+/// - `DELETE /todos/:id`: delete a specific Todo.
+#[tokio::main]
+async fn main() {
+ if env::var_os("RUST_LOG").is_none() {
+ // Set `RUST_LOG=todos=debug` to see debug logs,
+ // this only shows access logs.
+ env::set_var("RUST_LOG", "todos=info");
+ }
+ pretty_env_logger::init();
+
+ let db = models::blank_db();
+
+ let api = filters::todos(db);
+
+ // View access logs by setting `RUST_LOG=todos`.
+ let routes = api.with(warp::log("todos"));
+ // Start up the server...
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
+
+mod filters {
+ use super::handlers;
+ use super::models::{Db, ListOptions, Todo};
+ use warp::Filter;
+
+ /// The 4 TODOs filters combined.
+ pub fn todos(
+ db: Db,
+ ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ todos_list(db.clone())
+ .or(todos_create(db.clone()))
+ .or(todos_update(db.clone()))
+ .or(todos_delete(db))
+ }
+
+ /// GET /todos?offset=3&limit=5
+ pub fn todos_list(
+ db: Db,
+ ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("todos")
+ .and(warp::get())
+ .and(warp::query::<ListOptions>())
+ .and(with_db(db))
+ .and_then(handlers::list_todos)
+ }
+
+ /// POST /todos with JSON body
+ pub fn todos_create(
+ db: Db,
+ ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("todos")
+ .and(warp::post())
+ .and(json_body())
+ .and(with_db(db))
+ .and_then(handlers::create_todo)
+ }
+
+ /// PUT /todos/:id with JSON body
+ pub fn todos_update(
+ db: Db,
+ ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ warp::path!("todos" / u64)
+ .and(warp::put())
+ .and(json_body())
+ .and(with_db(db))
+ .and_then(handlers::update_todo)
+ }
+
+ /// DELETE /todos/:id
+ pub fn todos_delete(
+ db: Db,
+ ) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
+ // We'll make one of our endpoints admin-only to show how authentication filters are used
+ let admin_only = warp::header::exact("authorization", "Bearer admin");
+
+ warp::path!("todos" / u64)
+ // It is important to put the auth check _after_ the path filters.
+ // If we put the auth check before, the request `PUT /todos/invalid-string`
+ // would try this filter and reject because the authorization header doesn't match,
+ // rather because the param is wrong for that other path.
+ .and(admin_only)
+ .and(warp::delete())
+ .and(with_db(db))
+ .and_then(handlers::delete_todo)
+ }
+
+ fn with_db(db: Db) -> impl Filter<Extract = (Db,), Error = std::convert::Infallible> + Clone {
+ warp::any().map(move || db.clone())
+ }
+
+ fn json_body() -> impl Filter<Extract = (Todo,), Error = warp::Rejection> + Clone {
+ // When accepting a body, we want a JSON body
+ // (and to reject huge payloads)...
+ warp::body::content_length_limit(1024 * 16).and(warp::body::json())
+ }
+}
+
+/// These are our API handlers, the ends of each filter chain.
+/// Notice how thanks to using `Filter::and`, we can define a function
+/// with the exact arguments we'd expect from each filter in the chain.
+/// No tuples are needed, it's auto flattened for the functions.
+mod handlers {
+ use super::models::{Db, ListOptions, Todo};
+ use std::convert::Infallible;
+ use warp::http::StatusCode;
+
+ pub async fn list_todos(opts: ListOptions, db: Db) -> Result<impl warp::Reply, Infallible> {
+ // Just return a JSON array of todos, applying the limit and offset.
+ let todos = db.lock().await;
+ let todos: Vec<Todo> = todos
+ .clone()
+ .into_iter()
+ .skip(opts.offset.unwrap_or(0))
+ .take(opts.limit.unwrap_or(std::usize::MAX))
+ .collect();
+ Ok(warp::reply::json(&todos))
+ }
+
+ pub async fn create_todo(create: Todo, db: Db) -> Result<impl warp::Reply, Infallible> {
+ log::debug!("create_todo: {:?}", create);
+
+ let mut vec = db.lock().await;
+
+ for todo in vec.iter() {
+ if todo.id == create.id {
+ log::debug!(" -> id already exists: {}", create.id);
+ // Todo with id already exists, return `400 BadRequest`.
+ return Ok(StatusCode::BAD_REQUEST);
+ }
+ }
+
+ // No existing Todo with id, so insert and return `201 Created`.
+ vec.push(create);
+
+ Ok(StatusCode::CREATED)
+ }
+
+ pub async fn update_todo(
+ id: u64,
+ update: Todo,
+ db: Db,
+ ) -> Result<impl warp::Reply, Infallible> {
+ log::debug!("update_todo: id={}, todo={:?}", id, update);
+ let mut vec = db.lock().await;
+
+ // Look for the specified Todo...
+ for todo in vec.iter_mut() {
+ if todo.id == id {
+ *todo = update;
+ return Ok(StatusCode::OK);
+ }
+ }
+
+ log::debug!(" -> todo id not found!");
+
+ // If the for loop didn't return OK, then the ID doesn't exist...
+ Ok(StatusCode::NOT_FOUND)
+ }
+
+ pub async fn delete_todo(id: u64, db: Db) -> Result<impl warp::Reply, Infallible> {
+ log::debug!("delete_todo: id={}", id);
+
+ let mut vec = db.lock().await;
+
+ let len = vec.len();
+ vec.retain(|todo| {
+ // Retain all Todos that aren't this id...
+ // In other words, remove all that *are* this id...
+ todo.id != id
+ });
+
+ // If the vec is smaller, we found and deleted a Todo!
+ let deleted = vec.len() != len;
+
+ if deleted {
+ // respond with a `204 No Content`, which means successful,
+ // yet no body expected...
+ Ok(StatusCode::NO_CONTENT)
+ } else {
+ log::debug!(" -> todo id not found!");
+ Ok(StatusCode::NOT_FOUND)
+ }
+ }
+}
+
+mod models {
+ use serde_derive::{Deserialize, Serialize};
+ use std::sync::Arc;
+ use tokio::sync::Mutex;
+
+ /// So we don't have to tackle how different database work, we'll just use
+ /// a simple in-memory DB, a vector synchronized by a mutex.
+ pub type Db = Arc<Mutex<Vec<Todo>>>;
+
+ pub fn blank_db() -> Db {
+ Arc::new(Mutex::new(Vec::new()))
+ }
+
+ #[derive(Debug, Deserialize, Serialize, Clone)]
+ pub struct Todo {
+ pub id: u64,
+ pub text: String,
+ pub completed: bool,
+ }
+
+ // The query parameters for list_todos.
+ #[derive(Debug, Deserialize)]
+ pub struct ListOptions {
+ pub offset: Option<usize>,
+ pub limit: Option<usize>,
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use warp::http::StatusCode;
+ use warp::test::request;
+
+ use super::{
+ filters,
+ models::{self, Todo},
+ };
+
+ #[tokio::test]
+ async fn test_post() {
+ let db = models::blank_db();
+ let api = filters::todos(db);
+
+ let resp = request()
+ .method("POST")
+ .path("/todos")
+ .json(&Todo {
+ id: 1,
+ text: "test 1".into(),
+ completed: false,
+ })
+ .reply(&api)
+ .await;
+
+ assert_eq!(resp.status(), StatusCode::CREATED);
+ }
+
+ #[tokio::test]
+ async fn test_post_conflict() {
+ let db = models::blank_db();
+ db.lock().await.push(todo1());
+ let api = filters::todos(db);
+
+ let resp = request()
+ .method("POST")
+ .path("/todos")
+ .json(&todo1())
+ .reply(&api)
+ .await;
+
+ assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+ }
+
+ #[tokio::test]
+ async fn test_put_unknown() {
+ let _ = pretty_env_logger::try_init();
+ let db = models::blank_db();
+ let api = filters::todos(db);
+
+ let resp = request()
+ .method("PUT")
+ .path("/todos/1")
+ .header("authorization", "Bearer admin")
+ .json(&todo1())
+ .reply(&api)
+ .await;
+
+ assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+ }
+
+ fn todo1() -> Todo {
+ Todo {
+ id: 1,
+ text: "test 1".into(),
+ completed: false,
+ }
+ }
+}
diff --git a/third_party/rust/warp/examples/unix_socket.rs b/third_party/rust/warp/examples/unix_socket.rs
new file mode 100644
index 0000000000..951a28782e
--- /dev/null
+++ b/third_party/rust/warp/examples/unix_socket.rs
@@ -0,0 +1,14 @@
+#![deny(warnings)]
+
+use tokio::net::UnixListener;
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ let mut listener = UnixListener::bind("/tmp/warp.sock").unwrap();
+ let incoming = listener.incoming();
+ warp::serve(warp::fs::dir("examples/dir"))
+ .run_incoming(incoming)
+ .await;
+}
diff --git a/third_party/rust/warp/examples/websockets.rs b/third_party/rust/warp/examples/websockets.rs
new file mode 100644
index 0000000000..6387041f06
--- /dev/null
+++ b/third_party/rust/warp/examples/websockets.rs
@@ -0,0 +1,27 @@
+#![deny(warnings)]
+
+use futures::{FutureExt, StreamExt};
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ let routes = warp::path("echo")
+ // The `ws()` filter will prepare the Websocket handshake.
+ .and(warp::ws())
+ .map(|ws: warp::ws::Ws| {
+ // And then our closure will be called when it completes...
+ ws.on_upgrade(|websocket| {
+ // Just echo all messages back...
+ let (tx, rx) = websocket.split();
+ rx.forward(tx).map(|result| {
+ if let Err(e) = result {
+ eprintln!("websocket error: {:?}", e);
+ }
+ })
+ })
+ });
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/websockets_chat.rs b/third_party/rust/warp/examples/websockets_chat.rs
new file mode 100644
index 0000000000..7f4e1d5513
--- /dev/null
+++ b/third_party/rust/warp/examples/websockets_chat.rs
@@ -0,0 +1,168 @@
+// #![deny(warnings)]
+use std::collections::HashMap;
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+};
+
+use futures::{FutureExt, StreamExt};
+use tokio::sync::{mpsc, Mutex};
+use warp::ws::{Message, WebSocket};
+use warp::Filter;
+
+/// Our global unique user id counter.
+static NEXT_USER_ID: AtomicUsize = AtomicUsize::new(1);
+
+/// Our state of currently connected users.
+///
+/// - Key is their id
+/// - Value is a sender of `warp::ws::Message`
+type Users = Arc<Mutex<HashMap<usize, mpsc::UnboundedSender<Result<Message, warp::Error>>>>>;
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // Keep track of all connected users, key is usize, value
+ // is a websocket sender.
+ let users = Arc::new(Mutex::new(HashMap::new()));
+ // Turn our "state" into a new Filter...
+ let users = warp::any().map(move || users.clone());
+
+ // GET /chat -> websocket upgrade
+ let chat = warp::path("chat")
+ // The `ws()` filter will prepare Websocket handshake...
+ .and(warp::ws())
+ .and(users)
+ .map(|ws: warp::ws::Ws, users| {
+ // This will call our function if the handshake succeeds.
+ ws.on_upgrade(move |socket| user_connected(socket, users))
+ });
+
+ // GET / -> index html
+ let index = warp::path::end().map(|| warp::reply::html(INDEX_HTML));
+
+ let routes = index.or(chat);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
+
+async fn user_connected(ws: WebSocket, users: Users) {
+ // Use a counter to assign a new unique ID for this user.
+ let my_id = NEXT_USER_ID.fetch_add(1, Ordering::Relaxed);
+
+ eprintln!("new chat user: {}", my_id);
+
+ // Split the socket into a sender and receive of messages.
+ let (user_ws_tx, mut user_ws_rx) = ws.split();
+
+ // Use an unbounded channel to handle buffering and flushing of messages
+ // to the websocket...
+ let (tx, rx) = mpsc::unbounded_channel();
+ tokio::task::spawn(rx.forward(user_ws_tx).map(|result| {
+ if let Err(e) = result {
+ eprintln!("websocket send error: {}", e);
+ }
+ }));
+
+ // Save the sender in our list of connected users.
+ users.lock().await.insert(my_id, tx);
+
+ // Return a `Future` that is basically a state machine managing
+ // this specific user's connection.
+
+ // Make an extra clone to give to our disconnection handler...
+ let users2 = users.clone();
+
+ // Every time the user sends a message, broadcast it to
+ // all other users...
+ while let Some(result) = user_ws_rx.next().await {
+ let msg = match result {
+ Ok(msg) => msg,
+ Err(e) => {
+ eprintln!("websocket error(uid={}): {}", my_id, e);
+ break;
+ }
+ };
+ user_message(my_id, msg, &users).await;
+ }
+
+ // user_ws_rx stream will keep processing as long as the user stays
+ // connected. Once they disconnect, then...
+ user_disconnected(my_id, &users2).await;
+}
+
+async fn user_message(my_id: usize, msg: Message, users: &Users) {
+ // Skip any non-Text messages...
+ let msg = if let Ok(s) = msg.to_str() {
+ s
+ } else {
+ return;
+ };
+
+ let new_msg = format!("<User#{}>: {}", my_id, msg);
+
+ // New message from this user, send it to everyone else (except same uid)...
+ //
+ // We use `retain` instead of a for loop so that we can reap any user that
+ // appears to have disconnected.
+ for (&uid, tx) in users.lock().await.iter_mut() {
+ if my_id != uid {
+ if let Err(_disconnected) = tx.send(Ok(Message::text(new_msg.clone()))) {
+ // The tx is disconnected, our `user_disconnected` code
+ // should be happening in another task, nothing more to
+ // do here.
+ }
+ }
+ }
+}
+
+async fn user_disconnected(my_id: usize, users: &Users) {
+ eprintln!("good bye user: {}", my_id);
+
+ // Stream closed up, so remove from the user list
+ users.lock().await.remove(&my_id);
+}
+
+static INDEX_HTML: &str = r#"
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Warp Chat</title>
+ </head>
+ <body>
+ <h1>warp chat</h1>
+ <div id="chat">
+ <p><em>Connecting...</em></p>
+ </div>
+ <input type="text" id="text" />
+ <button type="button" id="send">Send</button>
+ <script type="text/javascript">
+ var uri = 'ws://' + location.host + '/chat';
+ var ws = new WebSocket(uri);
+
+ function message(data) {
+ var line = document.createElement('p');
+ line.innerText = data;
+ chat.appendChild(line);
+ }
+
+ ws.onopen = function() {
+ chat.innerHTML = "<p><em>Connected!</em></p>";
+ }
+
+ ws.onmessage = function(msg) {
+ message(msg.data);
+ };
+
+ send.onclick = function() {
+ var msg = text.value;
+ ws.send(msg);
+ text.value = '';
+
+ message('<You>: ' + msg);
+ };
+ </script>
+ </body>
+</html>
+"#;
diff --git a/third_party/rust/warp/src/error.rs b/third_party/rust/warp/src/error.rs
new file mode 100644
index 0000000000..b17b8bc482
--- /dev/null
+++ b/third_party/rust/warp/src/error.rs
@@ -0,0 +1,69 @@
+use std::convert::Infallible;
+use std::error::Error as StdError;
+use std::fmt;
+
+type BoxError = Box<dyn std::error::Error + Send + Sync>;
+
+/// Errors that can happen inside warp.
+pub struct Error {
+ inner: BoxError,
+}
+
+impl Error {
+ pub(crate) fn new<E: Into<BoxError>>(err: E) -> Error {
+ Error { inner: err.into() }
+ }
+}
+
+impl fmt::Debug for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ // Skip showing worthless `Error { .. }` wrapper.
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Display::fmt(&self.inner, f)
+ }
+}
+
+impl StdError for Error {}
+
+impl From<Infallible> for Error {
+ fn from(infallible: Infallible) -> Error {
+ match infallible {}
+ }
+}
+
+#[test]
+fn error_size_of() {
+ assert_eq!(
+ ::std::mem::size_of::<Error>(),
+ ::std::mem::size_of::<usize>() * 2
+ );
+}
+
+macro_rules! unit_error {
+ (
+ $(#[$docs:meta])*
+ $pub:vis $typ:ident: $display:literal
+ ) => (
+ $(#[$docs])*
+ $pub struct $typ { _p: (), }
+
+ impl ::std::fmt::Debug for $typ {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.debug_struct(stringify!($typ)).finish()
+ }
+ }
+
+ impl ::std::fmt::Display for $typ {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.write_str($display)
+ }
+ }
+
+ impl ::std::error::Error for $typ {}
+ )
+}
diff --git a/third_party/rust/warp/src/filter/and.rs b/third_party/rust/warp/src/filter/and.rs
new file mode 100644
index 0000000000..96973ad865
--- /dev/null
+++ b/third_party/rust/warp/src/filter/and.rs
@@ -0,0 +1,89 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::ready;
+use pin_project::{pin_project, project};
+
+use super::{Combine, Filter, FilterBase, HList, Internal, Tuple};
+use crate::reject::CombineRejection;
+
+#[derive(Clone, Copy, Debug)]
+pub struct And<T, U> {
+ pub(super) first: T,
+ pub(super) second: U,
+}
+
+impl<T, U> FilterBase for And<T, U>
+where
+ T: Filter,
+ T::Extract: Send,
+ U: Filter + Clone + Send,
+ <T::Extract as Tuple>::HList: Combine<<U::Extract as Tuple>::HList> + Send,
+ <<<T::Extract as Tuple>::HList as Combine<<U::Extract as Tuple>::HList>>::Output as HList>::Tuple: Send,
+ U::Error: CombineRejection<T::Error>,
+{
+ type Extract = <<<T::Extract as Tuple>::HList as Combine<<U::Extract as Tuple>::HList>>::Output as HList>::Tuple;
+ type Error = <U::Error as CombineRejection<T::Error>>::One;
+ type Future = AndFuture<T, U>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ AndFuture {
+ state: State::First(self.first.filter(Internal), self.second.clone()),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct AndFuture<T: Filter, U: Filter> {
+ #[pin]
+ state: State<T, U>,
+}
+
+#[pin_project]
+enum State<T: Filter, U: Filter> {
+ First(#[pin] T::Future, U),
+ Second(Option<T::Extract>, #[pin] U::Future),
+ Done,
+}
+
+impl<T, U> Future for AndFuture<T, U>
+where
+ T: Filter,
+ U: Filter,
+ <T::Extract as Tuple>::HList: Combine<<U::Extract as Tuple>::HList> + Send,
+ U::Error: CombineRejection<T::Error>,
+{
+ type Output = Result<
+ <<<T::Extract as Tuple>::HList as Combine<<U::Extract as Tuple>::HList>>::Output as HList>::Tuple,
+ <U::Error as CombineRejection<T::Error>>::One>;
+
+ #[project]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ #[project]
+ let (ex1, fut2) = match pin.state.project() {
+ State::First(first, second) => match ready!(first.poll(cx)) {
+ Ok(first) => (first, second.filter(Internal)),
+ Err(err) => return Poll::Ready(Err(From::from(err))),
+ },
+ State::Second(ex1, second) => {
+ let ex2 = match ready!(second.poll(cx)) {
+ Ok(second) => second,
+ Err(err) => return Poll::Ready(Err(From::from(err))),
+ };
+ let ex3 = ex1.take().unwrap().hlist().combine(ex2.hlist()).flatten();
+ self.set(AndFuture { state: State::Done });
+ return Poll::Ready(Ok(ex3));
+ }
+ State::Done => panic!("polled after complete"),
+ };
+
+ self.set(AndFuture {
+ state: State::Second(Some(ex1), fut2),
+ });
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/and_then.rs b/third_party/rust/warp/src/filter/and_then.rs
new file mode 100644
index 0000000000..535053c17e
--- /dev/null
+++ b/third_party/rust/warp/src/filter/and_then.rs
@@ -0,0 +1,99 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::{pin_project, project};
+
+use super::{Filter, FilterBase, Func, Internal};
+use crate::reject::CombineRejection;
+
+#[derive(Clone, Copy, Debug)]
+pub struct AndThen<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F> FilterBase for AndThen<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract> + Clone + Send,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ type Extract = (<F::Output as TryFuture>::Ok,);
+ type Error = <<F::Output as TryFuture>::Error as CombineRejection<T::Error>>::One;
+ type Future = AndThenFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ AndThenFuture {
+ state: State::First(self.filter.filter(Internal), self.callback.clone()),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct AndThenFuture<T: Filter, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ #[pin]
+ state: State<T, F>,
+}
+
+#[pin_project]
+enum State<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ First(#[pin] T::Future, F),
+ Second(#[pin] F::Output),
+ Done,
+}
+
+impl<T, F> Future for AndThenFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ type Output = Result<
+ (<F::Output as TryFuture>::Ok,),
+ <<F::Output as TryFuture>::Error as CombineRejection<T::Error>>::One,
+ >;
+
+ #[project]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ #[project]
+ let (ex1, second) = match pin.state.project() {
+ State::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(first) => (first, second),
+ Err(err) => return Poll::Ready(Err(From::from(err))),
+ },
+ State::Second(second) => {
+ let ex3 = match ready!(second.try_poll(cx)) {
+ Ok(item) => Ok((item,)),
+ Err(err) => Err(From::from(err)),
+ };
+ self.set(AndThenFuture { state: State::Done });
+ return Poll::Ready(ex3);
+ }
+ State::Done => panic!("polled after complete"),
+ };
+ let fut2 = second.call(ex1);
+ self.set(AndThenFuture {
+ state: State::Second(fut2),
+ });
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/boxed.rs b/third_party/rust/warp/src/filter/boxed.rs
new file mode 100644
index 0000000000..322262cf60
--- /dev/null
+++ b/third_party/rust/warp/src/filter/boxed.rs
@@ -0,0 +1,100 @@
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::sync::Arc;
+
+use futures::TryFutureExt;
+
+use super::{Filter, FilterBase, Internal, Tuple};
+use crate::reject::Rejection;
+
+/// A type representing a boxed `Filter` trait object.
+///
+/// The filter inside is a dynamic trait object. The purpose of this type is
+/// to ease returning `Filter`s from other functions.
+///
+/// To create one, call `Filter::boxed` on any filter.
+///
+/// # Examples
+///
+/// ```
+/// use warp::{Filter, filters::BoxedFilter, Reply};
+///
+/// pub fn assets_filter() -> BoxedFilter<(impl Reply,)> {
+/// warp::path("assets")
+/// .and(warp::fs::dir("./assets"))
+/// .boxed()
+/// }
+/// ```
+///
+pub struct BoxedFilter<T: Tuple> {
+ filter: Arc<
+ dyn Filter<
+ Extract = T,
+ Error = Rejection,
+ Future = Pin<Box<dyn Future<Output = Result<T, Rejection>> + Send>>,
+ > + Send
+ + Sync,
+ >,
+}
+
+impl<T: Tuple + Send> BoxedFilter<T> {
+ pub(super) fn new<F>(filter: F) -> BoxedFilter<T>
+ where
+ F: Filter<Extract = T> + Send + Sync + 'static,
+ F::Error: Into<Rejection>,
+ {
+ BoxedFilter {
+ filter: Arc::new(BoxingFilter {
+ filter: filter.map_err(super::Internal, Into::into),
+ }),
+ }
+ }
+}
+
+impl<T: Tuple> Clone for BoxedFilter<T> {
+ fn clone(&self) -> BoxedFilter<T> {
+ BoxedFilter {
+ filter: self.filter.clone(),
+ }
+ }
+}
+
+impl<T: Tuple> fmt::Debug for BoxedFilter<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("BoxedFilter").finish()
+ }
+}
+
+fn _assert_send() {
+ fn _assert<T: Send>() {}
+ _assert::<BoxedFilter<()>>();
+}
+
+impl<T: Tuple + Send> FilterBase for BoxedFilter<T> {
+ type Extract = T;
+ type Error = Rejection;
+ type Future = Pin<Box<dyn Future<Output = Result<T, Rejection>> + Send>>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ self.filter.filter(Internal)
+ }
+}
+
+struct BoxingFilter<F> {
+ filter: F,
+}
+
+impl<F> FilterBase for BoxingFilter<F>
+where
+ F: Filter,
+ F::Future: Send + 'static,
+{
+ type Extract = F::Extract;
+ type Error = F::Error;
+ type Future = Pin<Box<dyn Future<Output = Result<Self::Extract, Self::Error>> + Send>>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ Box::pin(self.filter.filter(Internal).into_future())
+ }
+}
diff --git a/third_party/rust/warp/src/filter/map.rs b/third_party/rust/warp/src/filter/map.rs
new file mode 100644
index 0000000000..4bacba9e28
--- /dev/null
+++ b/third_party/rust/warp/src/filter/map.rs
@@ -0,0 +1,59 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::pin_project;
+
+use super::{Filter, FilterBase, Func, Internal};
+
+#[derive(Clone, Copy, Debug)]
+pub struct Map<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F> FilterBase for Map<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract> + Clone + Send,
+{
+ type Extract = (F::Output,);
+ type Error = T::Error;
+ type Future = MapFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ MapFuture {
+ extract: self.filter.filter(Internal),
+ callback: self.callback.clone(),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct MapFuture<T: Filter, F> {
+ #[pin]
+ extract: T::Future,
+ callback: F,
+}
+
+impl<T, F> Future for MapFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+{
+ type Output = Result<(F::Output,), T::Error>;
+
+ #[inline]
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let pin = self.project();
+ match ready!(pin.extract.try_poll(cx)) {
+ Ok(ex) => {
+ let ex = (pin.callback.call(ex),);
+ Poll::Ready(Ok(ex))
+ }
+ Err(err) => Poll::Ready(Err(err)),
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/map_err.rs b/third_party/rust/warp/src/filter/map_err.rs
new file mode 100644
index 0000000000..3d3e6c3047
--- /dev/null
+++ b/third_party/rust/warp/src/filter/map_err.rs
@@ -0,0 +1,58 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::TryFuture;
+use pin_project::pin_project;
+
+use super::{Filter, FilterBase, Internal};
+use crate::reject::IsReject;
+
+#[derive(Clone, Copy, Debug)]
+pub struct MapErr<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F, E> FilterBase for MapErr<T, F>
+where
+ T: Filter,
+ F: Fn(T::Error) -> E + Clone + Send,
+ E: IsReject,
+{
+ type Extract = T::Extract;
+ type Error = E;
+ type Future = MapErrFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ MapErrFuture {
+ extract: self.filter.filter(Internal),
+ callback: self.callback.clone(),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct MapErrFuture<T: Filter, F> {
+ #[pin]
+ extract: T::Future,
+ callback: F,
+}
+
+impl<T, F, E> Future for MapErrFuture<T, F>
+where
+ T: Filter,
+ F: Fn(T::Error) -> E,
+{
+ type Output = Result<T::Extract, E>;
+
+ #[inline]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ self.as_mut()
+ .project()
+ .extract
+ .try_poll(cx)
+ .map_err(|err| (self.callback)(err))
+ }
+}
diff --git a/third_party/rust/warp/src/filter/mod.rs b/third_party/rust/warp/src/filter/mod.rs
new file mode 100644
index 0000000000..f4818d9607
--- /dev/null
+++ b/third_party/rust/warp/src/filter/mod.rs
@@ -0,0 +1,462 @@
+mod and;
+mod and_then;
+mod boxed;
+mod map;
+mod map_err;
+mod or;
+mod or_else;
+mod recover;
+pub(crate) mod service;
+mod unify;
+mod untuple_one;
+mod wrap;
+
+use std::future::Future;
+use std::pin::Pin;
+
+use futures::{future, TryFuture, TryFutureExt};
+
+pub(crate) use crate::generic::{one, Combine, Either, Func, HList, One, Tuple};
+use crate::reject::{CombineRejection, IsReject, Rejection};
+use crate::route::{self, Route};
+
+pub(crate) use self::and::And;
+use self::and_then::AndThen;
+pub use self::boxed::BoxedFilter;
+pub(crate) use self::map::Map;
+pub(crate) use self::map_err::MapErr;
+pub(crate) use self::or::Or;
+use self::or_else::OrElse;
+use self::recover::Recover;
+use self::unify::Unify;
+use self::untuple_one::UntupleOne;
+pub(crate) use self::wrap::{Wrap, WrapSealed};
+
+// A crate-private base trait, allowing the actual `filter` method to change
+// signatures without it being a breaking change.
+pub trait FilterBase {
+ type Extract: Tuple; // + Send;
+ type Error: IsReject;
+ type Future: Future<Output = Result<Self::Extract, Self::Error>> + Send;
+
+ fn filter(&self, internal: Internal) -> Self::Future;
+
+ fn map_err<F, E>(self, _internal: Internal, fun: F) -> MapErr<Self, F>
+ where
+ Self: Sized,
+ F: Fn(Self::Error) -> E + Clone,
+ E: ::std::fmt::Debug + Send,
+ {
+ MapErr {
+ filter: self,
+ callback: fun,
+ }
+ }
+}
+
+// A crate-private argument to prevent users from calling methods on
+// the `FilterBase` trait.
+//
+// For instance, this innocent user code could otherwise call `filter`:
+//
+// ```
+// async fn with_filter<F: Filter>(f: F) -> Result<F::Extract, F::Error> {
+// f.filter().await
+// }
+// ```
+#[allow(missing_debug_implementations)]
+pub struct Internal;
+
+/// Composable request filters.
+///
+/// A `Filter` can optionally extract some data from a request, combine
+/// it with others, mutate it, and return back some value as a reply. The
+/// power of `Filter`s come from being able to isolate small subsets, and then
+/// chain and reuse them in various parts of your app.
+///
+/// # Extracting Tuples
+///
+/// You may notice that several of these filters extract some tuple, often
+/// times a tuple of just 1 item! Why?
+///
+/// If a filter extracts a `(String,)`, that simply means that it
+/// extracts a `String`. If you were to `map` the filter, the argument type
+/// would be exactly that, just a `String`.
+///
+/// What is it? It's just some type magic that allows for automatic combining
+/// and flattening of tuples. Without it, combining two filters together with
+/// `and`, where one extracted `()`, and another `String`, would mean the
+/// `map` would be given a single argument of `((), String,)`, which is just
+/// no fun.
+pub trait Filter: FilterBase {
+ /// Composes a new `Filter` that requires both this and the other to filter a request.
+ ///
+ /// Additionally, this will join together the extracted values of both
+ /// filters, so that `map` and `and_then` receive them as separate arguments.
+ ///
+ /// If a `Filter` extracts nothing (so, `()`), combining with any other
+ /// filter will simply discard the `()`. If a `Filter` extracts one or
+ /// more items, combining will mean it extracts the values of itself
+ /// combined with the other.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// // Match `/hello/:name`...
+ /// warp::path("hello")
+ /// .and(warp::path::param::<String>());
+ /// ```
+ fn and<F>(self, other: F) -> And<Self, F>
+ where
+ Self: Sized,
+ <Self::Extract as Tuple>::HList: Combine<<F::Extract as Tuple>::HList>,
+ F: Filter + Clone,
+ F::Error: CombineRejection<Self::Error>,
+ {
+ And {
+ first: self,
+ second: other,
+ }
+ }
+
+ /// Composes a new `Filter` of either this or the other filter.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use std::net::SocketAddr;
+ /// use warp::Filter;
+ ///
+ /// // Match either `/:u32` or `/:socketaddr`
+ /// warp::path::param::<u32>()
+ /// .or(warp::path::param::<SocketAddr>());
+ /// ```
+ fn or<F>(self, other: F) -> Or<Self, F>
+ where
+ Self: Filter<Error = Rejection> + Sized,
+ F: Filter,
+ F::Error: CombineRejection<Self::Error>,
+ {
+ Or {
+ first: self,
+ second: other,
+ }
+ }
+
+ /// Composes this `Filter` with a function receiving the extracted value.
+ ///
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// // Map `/:id`
+ /// warp::path::param().map(|id: u64| {
+ /// format!("Hello #{}", id)
+ /// });
+ /// ```
+ ///
+ /// # `Func`
+ ///
+ /// The generic `Func` trait is implemented for any function that receives
+ /// the same arguments as this `Filter` extracts. In practice, this
+ /// shouldn't ever bother you, and simply makes things feel more natural.
+ ///
+ /// For example, if three `Filter`s were combined together, suppose one
+ /// extracts nothing (so `()`), and the other two extract two integers,
+ /// a function that accepts exactly two integer arguments is allowed.
+ /// Specifically, any `Fn(u32, u32)`.
+ ///
+ /// Without `Product` and `Func`, this would be a lot messier. First of
+ /// all, the `()`s couldn't be discarded, and the tuples would be nested.
+ /// So, instead, you'd need to pass an `Fn(((), (u32, u32)))`. That's just
+ /// a single argument. Bleck!
+ ///
+ /// Even worse, the tuples would shuffle the types around depending on
+ /// the exact invocation of `and`s. So, `unit.and(int).and(int)` would
+ /// result in a different extracted type from `unit.and(int.and(int)`,
+ /// or from `int.and(unit).and(int)`. If you changed around the order
+ /// of filters, while still having them be semantically equivalent, you'd
+ /// need to update all your `map`s as well.
+ ///
+ /// `Product`, `HList`, and `Func` do all the heavy work so that none of
+ /// this is a bother to you. What's more, the types are enforced at
+ /// compile-time, and tuple flattening is optimized away to nothing by
+ /// LLVM.
+ fn map<F>(self, fun: F) -> Map<Self, F>
+ where
+ Self: Sized,
+ F: Func<Self::Extract> + Clone,
+ {
+ Map {
+ filter: self,
+ callback: fun,
+ }
+ }
+
+ /// Composes this `Filter` with a function receiving the extracted value.
+ ///
+ /// The function should return some `TryFuture` type.
+ ///
+ /// The `Error` type of the return `Future` needs be a `Rejection`, which
+ /// means most futures will need to have their error mapped into one.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// // Validate after `/:id`
+ /// warp::path::param().and_then(|id: u64| async move {
+ /// if id != 0 {
+ /// Ok(format!("Hello #{}", id))
+ /// } else {
+ /// Err(warp::reject::not_found())
+ /// }
+ /// });
+ /// ```
+ fn and_then<F>(self, fun: F) -> AndThen<Self, F>
+ where
+ Self: Sized,
+ F: Func<Self::Extract> + Clone,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<Self::Error>,
+ {
+ AndThen {
+ filter: self,
+ callback: fun,
+ }
+ }
+
+ /// Compose this `Filter` with a function receiving an error.
+ ///
+ /// The function should return some `TryFuture` type yielding the
+ /// same item and error types.
+ fn or_else<F>(self, fun: F) -> OrElse<Self, F>
+ where
+ Self: Filter<Error = Rejection> + Sized,
+ F: Func<Rejection>,
+ F::Output: TryFuture<Ok = Self::Extract> + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+ {
+ OrElse {
+ filter: self,
+ callback: fun,
+ }
+ }
+
+ /// Compose this `Filter` with a function receiving an error and
+ /// returning a *new* type, instead of the *same* type.
+ ///
+ /// This is useful for "customizing" rejections into new response types.
+ /// See also the [rejections example][ex].
+ ///
+ /// [ex]: https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs
+ fn recover<F>(self, fun: F) -> Recover<Self, F>
+ where
+ Self: Filter<Error = Rejection> + Sized,
+ F: Func<Rejection>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+ {
+ Recover {
+ filter: self,
+ callback: fun,
+ }
+ }
+
+ /// Unifies the extracted value of `Filter`s composed with `or`.
+ ///
+ /// When a `Filter` extracts some `Either<T, T>`, where both sides
+ /// are the same type, this combinator can be used to grab the
+ /// inner value, regardless of which side of `Either` it was. This
+ /// is useful for values that could be extracted from multiple parts
+ /// of a request, and the exact place isn't important.
+ ///
+ /// # Example
+ ///
+ /// ```rust
+ /// use std::net::SocketAddr;
+ /// use warp::Filter;
+ ///
+ /// let client_ip = warp::header("x-real-ip")
+ /// .or(warp::header("x-forwarded-for"))
+ /// .unify()
+ /// .map(|ip: SocketAddr| {
+ /// // Get the IP from either header,
+ /// // and unify into the inner type.
+ /// });
+ /// ```
+ fn unify<T>(self) -> Unify<Self>
+ where
+ Self: Filter<Extract = (Either<T, T>,)> + Sized,
+ T: Tuple,
+ {
+ Unify { filter: self }
+ }
+
+ /// Convenience method to remove one layer of tupling.
+ ///
+ /// This is useful for when things like `map` don't return a new value,
+ /// but just `()`, since warp will wrap it up into a `((),)`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// let route = warp::path::param()
+ /// .map(|num: u64| {
+ /// println!("just logging: {}", num);
+ /// // returning "nothing"
+ /// })
+ /// .untuple_one()
+ /// .map(|| {
+ /// println!("the ((),) was removed");
+ /// warp::reply()
+ /// });
+ /// ```
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// let route = warp::any()
+ /// .map(|| {
+ /// // wanting to return a tuple
+ /// (true, 33)
+ /// })
+ /// .untuple_one()
+ /// .map(|is_enabled: bool, count: i32| {
+ /// println!("untupled: ({}, {})", is_enabled, count);
+ /// });
+ /// ```
+ fn untuple_one<T>(self) -> UntupleOne<Self>
+ where
+ Self: Filter<Extract = (T,)> + Sized,
+ T: Tuple,
+ {
+ UntupleOne { filter: self }
+ }
+
+ /// Wraps the current filter with some wrapper.
+ ///
+ /// The wrapper may do some preparation work before starting this filter,
+ /// and may do post-processing after the filter completes.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// let route = warp::any()
+ /// .map(warp::reply);
+ ///
+ /// // Wrap the route with a log wrapper.
+ /// let route = route.with(warp::log("example"));
+ /// ```
+ fn with<W>(self, wrapper: W) -> W::Wrapped
+ where
+ Self: Sized,
+ W: Wrap<Self>,
+ {
+ wrapper.wrap(self)
+ }
+
+ /// Boxes this filter into a trait object, making it easier to name the type.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// fn impl_reply() -> warp::filters::BoxedFilter<(impl warp::Reply,)> {
+ /// warp::any()
+ /// .map(warp::reply)
+ /// .boxed()
+ /// }
+ ///
+ /// fn named_i32() -> warp::filters::BoxedFilter<(i32,)> {
+ /// warp::path::param::<i32>()
+ /// .boxed()
+ /// }
+ ///
+ /// fn named_and() -> warp::filters::BoxedFilter<(i32, String)> {
+ /// warp::path::param::<i32>()
+ /// .and(warp::header::<String>("host"))
+ /// .boxed()
+ /// }
+ /// ```
+ fn boxed(self) -> BoxedFilter<Self::Extract>
+ where
+ Self: Sized + Send + Sync + 'static,
+ Self::Extract: Send,
+ Self::Error: Into<Rejection>,
+ {
+ BoxedFilter::new(self)
+ }
+}
+
+impl<T: FilterBase> Filter for T {}
+
+pub trait FilterClone: Filter + Clone {}
+
+impl<T: Filter + Clone> FilterClone for T {}
+
+fn _assert_object_safe() {
+ fn _assert(_f: &dyn Filter<Extract = (), Error = (), Future = future::Ready<()>>) {}
+}
+
+// ===== FilterFn =====
+
+pub(crate) fn filter_fn<F, U>(func: F) -> FilterFn<F>
+where
+ F: Fn(&mut Route) -> U,
+ U: TryFuture,
+ U::Ok: Tuple,
+ U::Error: IsReject,
+{
+ FilterFn { func }
+}
+
+pub(crate) fn filter_fn_one<F, U>(
+ func: F,
+) -> FilterFn<impl Fn(&mut Route) -> future::MapOk<U, fn(U::Ok) -> (U::Ok,)> + Copy>
+where
+ F: Fn(&mut Route) -> U + Copy,
+ U: TryFuture,
+ U::Error: IsReject,
+{
+ filter_fn(move |route| func(route).map_ok(tup_one as _))
+}
+
+fn tup_one<T>(item: T) -> (T,) {
+ (item,)
+}
+
+#[derive(Copy, Clone)]
+#[allow(missing_debug_implementations)]
+pub(crate) struct FilterFn<F> {
+ // TODO: could include a `debug_str: &'static str` to be used in Debug impl
+ func: F,
+}
+
+impl<F, U> FilterBase for FilterFn<F>
+where
+ F: Fn(&mut Route) -> U,
+ U: TryFuture + Send + 'static,
+ U::Ok: Tuple + Send,
+ U::Error: IsReject,
+{
+ type Extract = U::Ok;
+ type Error = U::Error;
+ type Future =
+ Pin<Box<dyn Future<Output = Result<Self::Extract, Self::Error>> + Send + 'static>>;
+
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ Box::pin(route::with(|route| (self.func)(route)).into_future())
+ }
+}
diff --git a/third_party/rust/warp/src/filter/or.rs b/third_party/rust/warp/src/filter/or.rs
new file mode 100644
index 0000000000..b091282206
--- /dev/null
+++ b/third_party/rust/warp/src/filter/or.rs
@@ -0,0 +1,112 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::{pin_project, project};
+
+use super::{Filter, FilterBase, Internal};
+use crate::generic::Either;
+use crate::reject::CombineRejection;
+use crate::route;
+
+type Combined<E1, E2> = <E1 as CombineRejection<E2>>::Combined;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Or<T, U> {
+ pub(super) first: T,
+ pub(super) second: U,
+}
+
+impl<T, U> FilterBase for Or<T, U>
+where
+ T: Filter,
+ U: Filter + Clone + Send,
+ U::Error: CombineRejection<T::Error>,
+{
+ type Extract = (Either<T::Extract, U::Extract>,);
+ //type Error = <U::Error as CombineRejection<T::Error>>::Combined;
+ type Error = Combined<U::Error, T::Error>;
+ type Future = EitherFuture<T, U>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ let idx = route::with(|route| route.matched_path_index());
+ EitherFuture {
+ state: State::First(self.first.filter(Internal), self.second.clone()),
+ original_path_index: PathIndex(idx),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct EitherFuture<T: Filter, U: Filter> {
+ #[pin]
+ state: State<T, U>,
+ original_path_index: PathIndex,
+}
+
+#[pin_project]
+enum State<T: Filter, U: Filter> {
+ First(#[pin] T::Future, U),
+ Second(Option<T::Error>, #[pin] U::Future),
+ Done,
+}
+
+#[derive(Copy, Clone)]
+struct PathIndex(usize);
+
+impl PathIndex {
+ fn reset_path(&self) {
+ route::with(|route| route.reset_matched_path_index(self.0));
+ }
+}
+
+impl<T, U> Future for EitherFuture<T, U>
+where
+ T: Filter,
+ U: Filter,
+ U::Error: CombineRejection<T::Error>,
+{
+ type Output = Result<(Either<T::Extract, U::Extract>,), Combined<U::Error, T::Error>>;
+
+ #[project]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ #[project]
+ let (err1, fut2) = match pin.state.project() {
+ State::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(ex1) => {
+ return Poll::Ready(Ok((Either::A(ex1),)));
+ }
+ Err(e) => {
+ pin.original_path_index.reset_path();
+ (e, second.filter(Internal))
+ }
+ },
+ State::Second(err1, second) => {
+ let ex2 = match ready!(second.try_poll(cx)) {
+ Ok(ex2) => Ok((Either::B(ex2),)),
+ Err(e) => {
+ pin.original_path_index.reset_path();
+ let err1 = err1.take().expect("polled after complete");
+ Err(e.combine(err1))
+ }
+ };
+ self.set(EitherFuture {
+ state: State::Done,
+ ..*self
+ });
+ return Poll::Ready(ex2);
+ }
+ State::Done => panic!("polled after complete"),
+ };
+
+ self.set(EitherFuture {
+ state: State::Second(Some(err1), fut2),
+ ..*self
+ });
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/or_else.rs b/third_party/rust/warp/src/filter/or_else.rs
new file mode 100644
index 0000000000..da43ea0eac
--- /dev/null
+++ b/third_party/rust/warp/src/filter/or_else.rs
@@ -0,0 +1,109 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::{pin_project, project};
+
+use super::{Filter, FilterBase, Func, Internal};
+use crate::reject::IsReject;
+use crate::route;
+
+#[derive(Clone, Copy, Debug)]
+pub struct OrElse<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F> FilterBase for OrElse<T, F>
+where
+ T: Filter,
+ F: Func<T::Error> + Clone + Send,
+ F::Output: TryFuture<Ok = T::Extract> + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+{
+ type Extract = <F::Output as TryFuture>::Ok;
+ type Error = <F::Output as TryFuture>::Error;
+ type Future = OrElseFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ let idx = route::with(|route| route.matched_path_index());
+ OrElseFuture {
+ state: State::First(self.filter.filter(Internal), self.callback.clone()),
+ original_path_index: PathIndex(idx),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct OrElseFuture<T: Filter, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture<Ok = T::Extract> + Send,
+{
+ #[pin]
+ state: State<T, F>,
+ original_path_index: PathIndex,
+}
+
+#[pin_project]
+enum State<T, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture<Ok = T::Extract> + Send,
+{
+ First(#[pin] T::Future, F),
+ Second(#[pin] F::Output),
+ Done,
+}
+
+#[derive(Copy, Clone)]
+struct PathIndex(usize);
+
+impl PathIndex {
+ fn reset_path(&self) {
+ route::with(|route| route.reset_matched_path_index(self.0));
+ }
+}
+
+impl<T, F> Future for OrElseFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture<Ok = T::Extract> + Send,
+{
+ type Output = Result<<F::Output as TryFuture>::Ok, <F::Output as TryFuture>::Error>;
+
+ #[project]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ #[project]
+ let (err, second) = match pin.state.project() {
+ State::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(ex) => return Poll::Ready(Ok(ex)),
+ Err(err) => (err, second),
+ },
+ State::Second(second) => {
+ let ex2 = ready!(second.try_poll(cx));
+ self.set(OrElseFuture {
+ state: State::Done,
+ ..*self
+ });
+ return Poll::Ready(ex2);
+ }
+ State::Done => panic!("polled after complete"),
+ };
+
+ pin.original_path_index.reset_path();
+ let fut2 = second.call(err);
+ self.set(OrElseFuture {
+ state: State::Second(fut2),
+ ..*self
+ });
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/recover.rs b/third_party/rust/warp/src/filter/recover.rs
new file mode 100644
index 0000000000..7f9108105d
--- /dev/null
+++ b/third_party/rust/warp/src/filter/recover.rs
@@ -0,0 +1,119 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::{pin_project, project};
+
+use super::{Filter, FilterBase, Func, Internal};
+use crate::generic::Either;
+use crate::reject::IsReject;
+use crate::route;
+
+#[derive(Clone, Copy, Debug)]
+pub struct Recover<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F> FilterBase for Recover<T, F>
+where
+ T: Filter,
+ F: Func<T::Error> + Clone + Send,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+{
+ type Extract = (Either<T::Extract, (<F::Output as TryFuture>::Ok,)>,);
+ type Error = <F::Output as TryFuture>::Error;
+ type Future = RecoverFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ let idx = route::with(|route| route.matched_path_index());
+ RecoverFuture {
+ state: State::First(self.filter.filter(Internal), self.callback.clone()),
+ original_path_index: PathIndex(idx),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct RecoverFuture<T: Filter, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+{
+ #[pin]
+ state: State<T, F>,
+ original_path_index: PathIndex,
+}
+
+#[pin_project]
+enum State<T, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+{
+ First(#[pin] T::Future, F),
+ Second(#[pin] F::Output),
+ Done,
+}
+
+#[derive(Copy, Clone)]
+struct PathIndex(usize);
+
+impl PathIndex {
+ fn reset_path(&self) {
+ route::with(|route| route.reset_matched_path_index(self.0));
+ }
+}
+
+impl<T, F> Future for RecoverFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Error>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: IsReject,
+{
+ type Output = Result<
+ (Either<T::Extract, (<F::Output as TryFuture>::Ok,)>,),
+ <F::Output as TryFuture>::Error,
+ >;
+
+ #[project]
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ #[project]
+ let (err, second) = match pin.state.project() {
+ State::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(ex) => return Poll::Ready(Ok((Either::A(ex),))),
+ Err(err) => (err, second),
+ },
+ State::Second(second) => {
+ let ex2 = match ready!(second.try_poll(cx)) {
+ Ok(ex2) => Ok((Either::B((ex2,)),)),
+ Err(e) => Err(e),
+ };
+ self.set(RecoverFuture {
+ state: State::Done,
+ ..*self
+ });
+ return Poll::Ready(ex2);
+ }
+ State::Done => panic!("polled after complete"),
+ };
+
+ pin.original_path_index.reset_path();
+ let fut2 = second.call(err);
+ self.set(RecoverFuture {
+ state: State::Second(fut2),
+ ..*self
+ });
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/service.rs b/third_party/rust/warp/src/filter/service.rs
new file mode 100644
index 0000000000..c2dea4ee17
--- /dev/null
+++ b/third_party/rust/warp/src/filter/service.rs
@@ -0,0 +1,137 @@
+use std::convert::Infallible;
+use std::future::Future;
+use std::net::SocketAddr;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::future::TryFuture;
+use hyper::service::Service;
+use pin_project::pin_project;
+
+use crate::reject::IsReject;
+use crate::reply::{Reply, Response};
+use crate::route::{self, Route};
+use crate::{Filter, Request};
+
+/// Convert a `Filter` into a `Service`.
+///
+/// Filters are normally what APIs are built on in warp. However, it can be
+/// useful to convert a `Filter` into a [`Service`][Service], such as if
+/// further customizing a `hyper::Service`, or if wanting to make use of
+/// the greater [Tower][tower] set of middleware.
+///
+/// # Example
+///
+/// Running a `warp::Filter` on a regular `hyper::Server`:
+///
+/// ```
+/// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
+/// use std::convert::Infallible;
+/// use warp::Filter;
+///
+/// // Our Filter...
+/// let route = warp::any().map(|| "Hello From Warp!");
+///
+/// // Convert it into a `Service`...
+/// let svc = warp::service(route);
+///
+/// // Typical hyper setup...
+/// let make_svc = hyper::service::make_service_fn(move |_| async move {
+/// Ok::<_, Infallible>(svc)
+/// });
+///
+/// hyper::Server::bind(&([127, 0, 0, 1], 3030).into())
+/// .serve(make_svc)
+/// .await?;
+/// # Ok(())
+/// # }
+/// ```
+///
+/// [Service]: https://docs.rs/hyper/0.13.*/hyper/service/trait.Service.html
+/// [tower]: https://docs.rs/tower
+pub fn service<F>(filter: F) -> FilteredService<F>
+where
+ F: Filter,
+ <F::Future as TryFuture>::Ok: Reply,
+ <F::Future as TryFuture>::Error: IsReject,
+{
+ FilteredService { filter }
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct FilteredService<F> {
+ filter: F,
+}
+
+impl<F> FilteredService<F>
+where
+ F: Filter,
+ <F::Future as TryFuture>::Ok: Reply,
+ <F::Future as TryFuture>::Error: IsReject,
+{
+ #[inline]
+ pub(crate) fn call_with_addr(
+ &self,
+ req: Request,
+ remote_addr: Option<SocketAddr>,
+ ) -> FilteredFuture<F::Future> {
+ debug_assert!(!route::is_set(), "nested route::set calls");
+
+ let route = Route::new(req, remote_addr);
+ let fut = route::set(&route, || self.filter.filter(super::Internal));
+ FilteredFuture { future: fut, route }
+ }
+}
+
+impl<F> Service<Request> for FilteredService<F>
+where
+ F: Filter,
+ <F::Future as TryFuture>::Ok: Reply,
+ <F::Future as TryFuture>::Error: IsReject,
+{
+ type Response = Response;
+ type Error = Infallible;
+ type Future = FilteredFuture<F::Future>;
+
+ fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ #[inline]
+ fn call(&mut self, req: Request) -> Self::Future {
+ self.call_with_addr(req, None)
+ }
+}
+
+#[pin_project]
+#[derive(Debug)]
+pub struct FilteredFuture<F> {
+ #[pin]
+ future: F,
+ route: ::std::cell::RefCell<Route>,
+}
+
+impl<F> Future for FilteredFuture<F>
+where
+ F: TryFuture,
+ F::Ok: Reply,
+ F::Error: IsReject,
+{
+ type Output = Result<Response, Infallible>;
+
+ #[inline]
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ debug_assert!(!route::is_set(), "nested route::set calls");
+
+ let pin = self.project();
+ let fut = pin.future;
+ match route::set(&pin.route, || fut.try_poll(cx)) {
+ Poll::Ready(Ok(ok)) => Poll::Ready(Ok(ok.into_response())),
+ Poll::Pending => Poll::Pending,
+ Poll::Ready(Err(err)) => {
+ log::debug!("rejected: {:?}", err);
+ Poll::Ready(Ok(err.into_response()))
+ }
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/unify.rs b/third_party/rust/warp/src/filter/unify.rs
new file mode 100644
index 0000000000..cdd1361080
--- /dev/null
+++ b/third_party/rust/warp/src/filter/unify.rs
@@ -0,0 +1,53 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::pin_project;
+
+use super::{Either, Filter, FilterBase, Internal, Tuple};
+
+#[derive(Clone, Copy, Debug)]
+pub struct Unify<F> {
+ pub(super) filter: F,
+}
+
+impl<F, T> FilterBase for Unify<F>
+where
+ F: Filter<Extract = (Either<T, T>,)>,
+ T: Tuple,
+{
+ type Extract = T;
+ type Error = F::Error;
+ type Future = UnifyFuture<F::Future>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ UnifyFuture {
+ inner: self.filter.filter(Internal),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct UnifyFuture<F> {
+ #[pin]
+ inner: F,
+}
+
+impl<F, T> Future for UnifyFuture<F>
+where
+ F: TryFuture<Ok = (Either<T, T>,)>,
+{
+ type Output = Result<T, F::Error>;
+
+ #[inline]
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let unified = match ready!(self.project().inner.try_poll(cx)) {
+ Ok((Either::A(a),)) => Ok(a),
+ Ok((Either::B(b),)) => Ok(b),
+ Err(err) => Err(err),
+ };
+ Poll::Ready(unified)
+ }
+}
diff --git a/third_party/rust/warp/src/filter/untuple_one.rs b/third_party/rust/warp/src/filter/untuple_one.rs
new file mode 100644
index 0000000000..07876736d9
--- /dev/null
+++ b/third_party/rust/warp/src/filter/untuple_one.rs
@@ -0,0 +1,52 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures::{ready, TryFuture};
+use pin_project::pin_project;
+
+use super::{Filter, FilterBase, Internal, Tuple};
+
+#[derive(Clone, Copy, Debug)]
+pub struct UntupleOne<F> {
+ pub(super) filter: F,
+}
+
+impl<F, T> FilterBase for UntupleOne<F>
+where
+ F: Filter<Extract = (T,)>,
+ T: Tuple,
+{
+ type Extract = T;
+ type Error = F::Error;
+ type Future = UntupleOneFuture<F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ UntupleOneFuture {
+ extract: self.filter.filter(Internal),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct UntupleOneFuture<F: Filter> {
+ #[pin]
+ extract: F::Future,
+}
+
+impl<F, T> Future for UntupleOneFuture<F>
+where
+ F: Filter<Extract = (T,)>,
+ T: Tuple,
+{
+ type Output = Result<T, F::Error>;
+
+ #[inline]
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ match ready!(self.project().extract.try_poll(cx)) {
+ Ok((t,)) => Poll::Ready(Ok(t)),
+ Err(err) => Poll::Ready(Err(err)),
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/wrap.rs b/third_party/rust/warp/src/filter/wrap.rs
new file mode 100644
index 0000000000..2226286e25
--- /dev/null
+++ b/third_party/rust/warp/src/filter/wrap.rs
@@ -0,0 +1,27 @@
+use super::Filter;
+
+pub trait WrapSealed<F: Filter> {
+ type Wrapped: Filter;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped;
+}
+
+impl<'a, T, F> WrapSealed<F> for &'a T
+where
+ T: WrapSealed<F>,
+ F: Filter,
+{
+ type Wrapped = T::Wrapped;
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ (*self).wrap(filter)
+ }
+}
+
+pub trait Wrap<F: Filter>: WrapSealed<F> {}
+
+impl<T, F> Wrap<F> for T
+where
+ T: WrapSealed<F>,
+ F: Filter,
+{
+}
diff --git a/third_party/rust/warp/src/filters/addr.rs b/third_party/rust/warp/src/filters/addr.rs
new file mode 100644
index 0000000000..8d2ab0aa18
--- /dev/null
+++ b/third_party/rust/warp/src/filters/addr.rs
@@ -0,0 +1,26 @@
+//! Socket Address filters.
+
+use std::convert::Infallible;
+use std::net::SocketAddr;
+
+use crate::filter::{filter_fn_one, Filter};
+
+/// Creates a `Filter` to get the remote address of the connection.
+///
+/// If the underlying transport doesn't use socket addresses, this will yield
+/// `None`.
+///
+/// # Example
+///
+/// ```
+/// use std::net::SocketAddr;
+/// use warp::Filter;
+///
+/// let route = warp::addr::remote()
+/// .map(|addr: Option<SocketAddr>| {
+/// println!("remote address = {:?}", addr);
+/// });
+/// ```
+pub fn remote() -> impl Filter<Extract = (Option<SocketAddr>,), Error = Infallible> + Copy {
+ filter_fn_one(|route| futures::future::ok(route.remote_addr()))
+}
diff --git a/third_party/rust/warp/src/filters/any.rs b/third_party/rust/warp/src/filters/any.rs
new file mode 100644
index 0000000000..65db6661d5
--- /dev/null
+++ b/third_party/rust/warp/src/filters/any.rs
@@ -0,0 +1,76 @@
+//! A filter that matches any route.
+use std::convert::Infallible;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use crate::filter::{Filter, FilterBase, Internal};
+
+/// A filter that matches any route.
+///
+/// This can be a useful building block to build new filters from,
+/// since [`Filter`](crate::Filter) is otherwise a sealed trait.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::any()
+/// .map(|| {
+/// "I always return this string!"
+/// });
+/// ```
+///
+/// This could allow creating a single `impl Filter` returning a specific
+/// reply, that can then be used as the end of several different filter
+/// chains.
+///
+/// Another use case is turning some clone-able resource into a `Filter`,
+/// thus allowing to easily `and` it together with others.
+///
+/// ```
+/// use std::sync::Arc;
+/// use warp::Filter;
+///
+/// let state = Arc::new(vec![33, 41]);
+/// let with_state = warp::any().map(move || state.clone());
+///
+/// // Now we could `and` with any other filter:
+///
+/// let route = warp::path::param()
+/// .and(with_state)
+/// .map(|param_id: u32, db: Arc<Vec<u32>>| {
+/// db.contains(&param_id)
+/// });
+/// ```
+pub fn any() -> impl Filter<Extract = (), Error = Infallible> + Copy {
+ Any
+}
+
+#[derive(Copy, Clone)]
+#[allow(missing_debug_implementations)]
+struct Any;
+
+impl FilterBase for Any {
+ type Extract = ();
+ type Error = Infallible;
+ type Future = AnyFut;
+
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ AnyFut
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct AnyFut;
+
+impl Future for AnyFut {
+ type Output = Result<(), Infallible>;
+
+ #[inline]
+ fn poll(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
+ Poll::Ready(Ok(()))
+ }
+}
diff --git a/third_party/rust/warp/src/filters/body.rs b/third_party/rust/warp/src/filters/body.rs
new file mode 100644
index 0000000000..771ddce48f
--- /dev/null
+++ b/third_party/rust/warp/src/filters/body.rs
@@ -0,0 +1,341 @@
+//! Body filters
+//!
+//! Filters that extract a body for a route.
+
+use std::error::Error as StdError;
+use std::fmt;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use bytes::{buf::BufExt, Buf, Bytes};
+use futures::{future, ready, Stream, TryFutureExt};
+use headers::ContentLength;
+use http::header::CONTENT_TYPE;
+use hyper::Body;
+use mime;
+use serde::de::DeserializeOwned;
+use serde_json;
+use serde_urlencoded;
+
+use crate::filter::{filter_fn, filter_fn_one, Filter, FilterBase};
+use crate::reject::{self, Rejection};
+
+type BoxError = Box<dyn StdError + Send + Sync>;
+
+// Extracts the `Body` Stream from the route.
+//
+// Does not consume any of it.
+pub(crate) fn body() -> impl Filter<Extract = (Body,), Error = Rejection> + Copy {
+ filter_fn_one(|route| {
+ future::ready(route.take_body().ok_or_else(|| {
+ log::error!("request body already taken in previous filter");
+ reject::known(BodyConsumedMultipleTimes { _p: () })
+ }))
+ })
+}
+
+/// Require a `content-length` header to have a value no greater than some limit.
+///
+/// Rejects if `content-length` header is missing, is invalid, or has a number
+/// larger than the limit provided.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Limit the upload to 4kb...
+/// let upload = warp::body::content_length_limit(4096)
+/// .and(warp::body::aggregate());
+/// ```
+pub fn content_length_limit(limit: u64) -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ crate::filters::header::header2()
+ .map_err(crate::filter::Internal, |_| {
+ log::debug!("content-length missing");
+ reject::length_required()
+ })
+ .and_then(move |ContentLength(length)| {
+ if length <= limit {
+ future::ok(())
+ } else {
+ log::debug!("content-length: {} is over limit {}", length, limit);
+ future::err(reject::payload_too_large())
+ }
+ })
+ .untuple_one()
+}
+
+/// Create a `Filter` that extracts the request body as a `futures::Stream`.
+///
+/// If other filters have already extracted the body, this filter will reject
+/// with a `500 Internal Server Error`.
+///
+/// # Warning
+///
+/// This does not have a default size limit, it would be wise to use one to
+/// prevent a overly large request from using too much memory.
+pub fn stream(
+) -> impl Filter<Extract = (impl Stream<Item = Result<impl Buf, crate::Error>>,), Error = Rejection> + Copy
+{
+ body().map(|body: Body| BodyStream { body })
+}
+
+/// Returns a `Filter` that matches any request and extracts a `Future` of a
+/// concatenated body.
+///
+/// The contents of the body will be flattened into a single contiguous
+/// `Bytes`, which may require memory copies. If you don't require a
+/// contiguous buffer, using `aggregate` can be give better performance.
+///
+/// # Warning
+///
+/// This does not have a default size limit, it would be wise to use one to
+/// prevent a overly large request from using too much memory.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Buf, Filter};
+///
+/// let route = warp::body::content_length_limit(1024 * 32)
+/// .and(warp::body::bytes())
+/// .map(|bytes: bytes::Bytes| {
+/// println!("bytes = {:?}", bytes);
+/// });
+/// ```
+pub fn bytes() -> impl Filter<Extract = (Bytes,), Error = Rejection> + Copy {
+ body().and_then(|body: hyper::Body| {
+ hyper::body::to_bytes(body).map_err(|err| {
+ log::debug!("to_bytes error: {}", err);
+ reject::known(BodyReadError(err))
+ })
+ })
+}
+
+/// Returns a `Filter` that matches any request and extracts a `Future` of an
+/// aggregated body.
+///
+/// The `Buf` may contain multiple, non-contiguous buffers. This can be more
+/// performant (by reducing copies) when receiving large bodies.
+///
+/// # Warning
+///
+/// This does not have a default size limit, it would be wise to use one to
+/// prevent a overly large request from using too much memory.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Buf, Filter};
+///
+/// fn full_body(mut body: impl Buf) {
+/// // It could have several non-contiguous slices of memory...
+/// while body.has_remaining() {
+/// println!("slice = {:?}", body.bytes());
+/// let cnt = body.bytes().len();
+/// body.advance(cnt);
+/// }
+/// }
+///
+/// let route = warp::body::content_length_limit(1024 * 32)
+/// .and(warp::body::aggregate())
+/// .map(full_body);
+/// ```
+pub fn aggregate() -> impl Filter<Extract = (impl Buf,), Error = Rejection> + Copy {
+ body().and_then(|body: ::hyper::Body| {
+ hyper::body::aggregate(body).map_err(|err| {
+ log::debug!("aggregate error: {}", err);
+ reject::known(BodyReadError(err))
+ })
+ })
+}
+
+/// Returns a `Filter` that matches any request and extracts a `Future` of a
+/// JSON-decoded body.
+///
+/// # Warning
+///
+/// This does not have a default size limit, it would be wise to use one to
+/// prevent a overly large request from using too much memory.
+///
+/// # Example
+///
+/// ```
+/// use std::collections::HashMap;
+/// use warp::Filter;
+///
+/// let route = warp::body::content_length_limit(1024 * 32)
+/// .and(warp::body::json())
+/// .map(|simple_map: HashMap<String, String>| {
+/// "Got a JSON body!"
+/// });
+/// ```
+pub fn json<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
+ is_content_type::<Json>()
+ .and(aggregate())
+ .and_then(|buf| async move {
+ Json::decode(buf).map_err(|err| {
+ log::debug!("request json body error: {}", err);
+ reject::known(BodyDeserializeError { cause: err })
+ })
+ })
+}
+
+/// Returns a `Filter` that matches any request and extracts a
+/// `Future` of a form encoded body.
+///
+/// # Note
+///
+/// This filter is for the simpler `application/x-www-form-urlencoded` format,
+/// not `multipart/form-data`.
+///
+/// # Warning
+///
+/// This does not have a default size limit, it would be wise to use one to
+/// prevent a overly large request from using too much memory.
+///
+///
+/// ```
+/// use std::collections::HashMap;
+/// use warp::Filter;
+///
+/// let route = warp::body::content_length_limit(1024 * 32)
+/// .and(warp::body::form())
+/// .map(|simple_map: HashMap<String, String>| {
+/// "Got a urlencoded body!"
+/// });
+/// ```
+pub fn form<T: DeserializeOwned + Send>() -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
+ is_content_type::<Form>()
+ .and(aggregate())
+ .and_then(|buf| async move {
+ Form::decode(buf).map_err(|err| {
+ log::debug!("request form body error: {}", err);
+ reject::known(BodyDeserializeError { cause: err })
+ })
+ })
+}
+
+// ===== Decoders =====
+
+trait Decode {
+ const MIME: (mime::Name<'static>, mime::Name<'static>);
+ const WITH_NO_CONTENT_TYPE: bool;
+
+ fn decode<B: Buf, T: DeserializeOwned>(buf: B) -> Result<T, BoxError>;
+}
+
+struct Json;
+
+impl Decode for Json {
+ const MIME: (mime::Name<'static>, mime::Name<'static>) = (mime::APPLICATION, mime::JSON);
+ const WITH_NO_CONTENT_TYPE: bool = true;
+
+ fn decode<B: Buf, T: DeserializeOwned>(buf: B) -> Result<T, BoxError> {
+ serde_json::from_reader(buf.reader()).map_err(Into::into)
+ }
+}
+
+struct Form;
+
+impl Decode for Form {
+ const MIME: (mime::Name<'static>, mime::Name<'static>) =
+ (mime::APPLICATION, mime::WWW_FORM_URLENCODED);
+ const WITH_NO_CONTENT_TYPE: bool = true;
+
+ fn decode<B: Buf, T: DeserializeOwned>(buf: B) -> Result<T, BoxError> {
+ serde_urlencoded::from_reader(buf.reader()).map_err(Into::into)
+ }
+}
+
+// Require the `content-type` header to be this type (or, if there's no `content-type`
+// header at all, optimistically hope it's the right type).
+fn is_content_type<D: Decode>() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ filter_fn(move |route| {
+ let (type_, subtype) = D::MIME;
+ if let Some(value) = route.headers().get(CONTENT_TYPE) {
+ log::trace!("is_content_type {}/{}? {:?}", type_, subtype, value);
+ let ct = value
+ .to_str()
+ .ok()
+ .and_then(|s| s.parse::<mime::Mime>().ok());
+ if let Some(ct) = ct {
+ if ct.type_() == type_ && ct.subtype() == subtype {
+ future::ok(())
+ } else {
+ log::debug!(
+ "content-type {:?} doesn't match {}/{}",
+ value,
+ type_,
+ subtype
+ );
+ future::err(reject::unsupported_media_type())
+ }
+ } else {
+ log::debug!("content-type {:?} couldn't be parsed", value);
+ future::err(reject::unsupported_media_type())
+ }
+ } else if D::WITH_NO_CONTENT_TYPE {
+ // Optimistically assume its correct!
+ log::trace!("no content-type header, assuming {}/{}", type_, subtype);
+ future::ok(())
+ } else {
+ log::debug!("no content-type found");
+ future::err(reject::unsupported_media_type())
+ }
+ })
+}
+
+// ===== BodyStream =====
+
+struct BodyStream {
+ body: Body,
+}
+
+impl Stream for BodyStream {
+ type Item = Result<Bytes, crate::Error>;
+
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ let opt_item = ready!(Pin::new(&mut self.get_mut().body).poll_next(cx));
+
+ match opt_item {
+ None => Poll::Ready(None),
+ Some(item) => {
+ let stream_buf = item.map_err(crate::Error::new);
+
+ Poll::Ready(Some(stream_buf))
+ }
+ }
+ }
+}
+
+// ===== Rejections =====
+
+/// An error used in rejections when deserializing a request body fails.
+#[derive(Debug)]
+pub struct BodyDeserializeError {
+ cause: BoxError,
+}
+
+impl fmt::Display for BodyDeserializeError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "Request body deserialize error: {}", self.cause)
+ }
+}
+
+impl StdError for BodyDeserializeError {}
+
+#[derive(Debug)]
+pub(crate) struct BodyReadError(::hyper::Error);
+
+impl ::std::fmt::Display for BodyReadError {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Request body read error: {}", self.0)
+ }
+}
+
+impl StdError for BodyReadError {}
+
+unit_error! {
+ pub(crate) BodyConsumedMultipleTimes: "Request body consumed multiple times"
+}
diff --git a/third_party/rust/warp/src/filters/cookie.rs b/third_party/rust/warp/src/filters/cookie.rs
new file mode 100644
index 0000000000..650914536f
--- /dev/null
+++ b/third_party/rust/warp/src/filters/cookie.rs
@@ -0,0 +1,33 @@
+//! Cookie Filters
+
+use futures::future;
+use headers::Cookie;
+
+use super::header;
+use crate::filter::{Filter, One};
+use crate::reject::Rejection;
+use std::convert::Infallible;
+
+/// Creates a `Filter` that requires a cookie by name.
+///
+/// If found, extracts the value of the cookie, otherwise rejects.
+pub fn cookie(name: &'static str) -> impl Filter<Extract = One<String>, Error = Rejection> + Copy {
+ header::header2().and_then(move |cookie: Cookie| {
+ let cookie = cookie
+ .get(name)
+ .map(String::from)
+ .ok_or_else(|| crate::reject::missing_cookie(name));
+ future::ready(cookie)
+ })
+}
+
+/// Creates a `Filter` that looks for an optional cookie by name.
+///
+/// If found, extracts the value of the cookie, otherwise continues
+/// the request, extracting `None`.
+pub fn optional(
+ name: &'static str,
+) -> impl Filter<Extract = One<Option<String>>, Error = Infallible> + Copy {
+ header::optional2()
+ .map(move |opt: Option<Cookie>| opt.and_then(|cookie| cookie.get(name).map(String::from)))
+}
diff --git a/third_party/rust/warp/src/filters/cors.rs b/third_party/rust/warp/src/filters/cors.rs
new file mode 100644
index 0000000000..d6e2a7d646
--- /dev/null
+++ b/third_party/rust/warp/src/filters/cors.rs
@@ -0,0 +1,619 @@
+//! CORS Filters
+
+use std::collections::HashSet;
+use std::convert::TryFrom;
+use std::error::Error as StdError;
+use std::sync::Arc;
+
+use headers::{
+ AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlExposeHeaders, HeaderMapExt,
+};
+use http::{
+ self,
+ header::{self, HeaderName, HeaderValue},
+};
+
+use crate::filter::{Filter, WrapSealed};
+use crate::reject::{CombineRejection, Rejection};
+use crate::reply::Reply;
+
+use self::internal::{CorsFilter, IntoOrigin, Seconds};
+
+/// Create a wrapping filter that exposes [CORS][] behavior for a wrapped
+/// filter.
+///
+/// [CORS]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let cors = warp::cors()
+/// .allow_origin("https://hyper.rs")
+/// .allow_methods(vec!["GET", "POST", "DELETE"]);
+///
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(cors);
+/// ```
+/// If you want to allow any route:
+/// ```
+/// use warp::Filter;
+/// let cors = warp::cors()
+/// .allow_any_origin();
+/// ```
+/// You can find more usage examples [here](https://github.com/seanmonstar/warp/blob/7fa54eaecd0fe12687137372791ff22fc7995766/tests/cors.rs).
+pub fn cors() -> Builder {
+ Builder {
+ credentials: false,
+ allowed_headers: HashSet::new(),
+ exposed_headers: HashSet::new(),
+ max_age: None,
+ methods: HashSet::new(),
+ origins: None,
+ }
+}
+
+/// A wrapping filter constructed via `warp::cors()`.
+#[derive(Clone, Debug)]
+pub struct Cors {
+ config: Arc<Configured>,
+}
+
+/// A constructed via `warp::cors()`.
+#[derive(Clone, Debug)]
+pub struct Builder {
+ credentials: bool,
+ allowed_headers: HashSet<HeaderName>,
+ exposed_headers: HashSet<HeaderName>,
+ max_age: Option<u64>,
+ methods: HashSet<http::Method>,
+ origins: Option<HashSet<HeaderValue>>,
+}
+
+impl Builder {
+ /// Sets whether to add the `Access-Control-Allow-Credentials` header.
+ pub fn allow_credentials(mut self, allow: bool) -> Self {
+ self.credentials = allow;
+ self
+ }
+
+ /// Adds a method to the existing list of allowed request methods.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `http::Method`.
+ pub fn allow_method<M>(mut self, method: M) -> Self
+ where
+ http::Method: TryFrom<M>,
+ {
+ let method = match TryFrom::try_from(method) {
+ Ok(m) => m,
+ Err(_) => panic!("illegal Method"),
+ };
+ self.methods.insert(method);
+ self
+ }
+
+ /// Adds multiple methods to the existing list of allowed request methods.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `http::Method`.
+ pub fn allow_methods<I>(mut self, methods: I) -> Self
+ where
+ I: IntoIterator,
+ http::Method: TryFrom<I::Item>,
+ {
+ let iter = methods.into_iter().map(|m| match TryFrom::try_from(m) {
+ Ok(m) => m,
+ Err(_) => panic!("illegal Method"),
+ });
+ self.methods.extend(iter);
+ self
+ }
+
+ /// Adds a header to the list of allowed request headers.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `http::header::HeaderName`.
+ pub fn allow_header<H>(mut self, header: H) -> Self
+ where
+ HeaderName: TryFrom<H>,
+ {
+ let header = match TryFrom::try_from(header) {
+ Ok(m) => m,
+ Err(_) => panic!("illegal Header"),
+ };
+ self.allowed_headers.insert(header);
+ self
+ }
+
+ /// Adds multiple headers to the list of allowed request headers.
+ ///
+ /// # Panics
+ ///
+ /// Panics if any of the headers are not a valid `http::header::HeaderName`.
+ pub fn allow_headers<I>(mut self, headers: I) -> Self
+ where
+ I: IntoIterator,
+ HeaderName: TryFrom<I::Item>,
+ {
+ let iter = headers.into_iter().map(|h| match TryFrom::try_from(h) {
+ Ok(h) => h,
+ Err(_) => panic!("illegal Header"),
+ });
+ self.allowed_headers.extend(iter);
+ self
+ }
+
+ /// Adds a header to the list of exposed headers.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `http::header::HeaderName`.
+ pub fn expose_header<H>(mut self, header: H) -> Self
+ where
+ HeaderName: TryFrom<H>,
+ {
+ let header = match TryFrom::try_from(header) {
+ Ok(m) => m,
+ Err(_) => panic!("illegal Header"),
+ };
+ self.exposed_headers.insert(header);
+ self
+ }
+
+ /// Adds multiple headers to the list of exposed headers.
+ ///
+ /// # Panics
+ ///
+ /// Panics if any of the headers are not a valid `http::header::HeaderName`.
+ pub fn expose_headers<I>(mut self, headers: I) -> Self
+ where
+ I: IntoIterator,
+ HeaderName: TryFrom<I::Item>,
+ {
+ let iter = headers.into_iter().map(|h| match TryFrom::try_from(h) {
+ Ok(h) => h,
+ Err(_) => panic!("illegal Header"),
+ });
+ self.exposed_headers.extend(iter);
+ self
+ }
+
+ /// Sets that *any* `Origin` header is allowed.
+ ///
+ /// # Warning
+ ///
+ /// This can allow websites you didn't instead to access this resource,
+ /// it is usually better to set an explicit list.
+ pub fn allow_any_origin(mut self) -> Self {
+ self.origins = None;
+ self
+ }
+
+ /// Add an origin to the existing list of allowed `Origin`s.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `Origin`.
+ pub fn allow_origin(self, origin: impl IntoOrigin) -> Self {
+ self.allow_origins(Some(origin))
+ }
+
+ /// Add multiple origins to the existing list of allowed `Origin`s.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the provided argument is not a valid `Origin`.
+ pub fn allow_origins<I>(mut self, origins: I) -> Self
+ where
+ I: IntoIterator,
+ I::Item: IntoOrigin,
+ {
+ let iter = origins
+ .into_iter()
+ .map(IntoOrigin::into_origin)
+ .map(|origin| {
+ origin
+ .to_string()
+ .parse()
+ .expect("Origin is always a valid HeaderValue")
+ });
+
+ self.origins.get_or_insert_with(HashSet::new).extend(iter);
+
+ self
+ }
+
+ /// Sets the `Access-Control-Max-Age` header.
+ ///
+ /// # Example
+ ///
+ ///
+ /// ```
+ /// use std::time::Duration;
+ /// use warp::Filter;
+ ///
+ /// let cors = warp::cors()
+ /// .max_age(30) // 30u32 seconds
+ /// .max_age(Duration::from_secs(30)); // or a Duration
+ /// ```
+ pub fn max_age(mut self, seconds: impl Seconds) -> Self {
+ self.max_age = Some(seconds.seconds());
+ self
+ }
+
+ /// Builds the `Cors` wrapper from the configured settings.
+ ///
+ /// This step isn't *required*, as the `Builder` itself can be passed
+ /// to `Filter::with`. This just allows constructing once, thus not needing
+ /// to pay the cost of "building" every time.
+ pub fn build(self) -> Cors {
+ let expose_headers_header = if self.exposed_headers.is_empty() {
+ None
+ } else {
+ Some(self.exposed_headers.iter().cloned().collect())
+ };
+ let allowed_headers_header = self.allowed_headers.iter().cloned().collect();
+ let methods_header = self.methods.iter().cloned().collect();
+
+ let config = Arc::new(Configured {
+ cors: self,
+ allowed_headers_header,
+ expose_headers_header,
+ methods_header,
+ });
+
+ Cors { config }
+ }
+}
+
+impl<F> WrapSealed<F> for Builder
+where
+ F: Filter + Clone + Send + Sync + 'static,
+ F::Extract: Reply,
+ F::Error: CombineRejection<Rejection>,
+ <F::Error as CombineRejection<Rejection>>::One: CombineRejection<Rejection>,
+{
+ type Wrapped = CorsFilter<F>;
+
+ fn wrap(&self, inner: F) -> Self::Wrapped {
+ let Cors { config } = self.clone().build();
+
+ CorsFilter { config, inner }
+ }
+}
+
+impl<F> WrapSealed<F> for Cors
+where
+ F: Filter + Clone + Send + Sync + 'static,
+ F::Extract: Reply,
+ F::Error: CombineRejection<Rejection>,
+ <F::Error as CombineRejection<Rejection>>::One: CombineRejection<Rejection>,
+{
+ type Wrapped = CorsFilter<F>;
+
+ fn wrap(&self, inner: F) -> Self::Wrapped {
+ let config = self.config.clone();
+
+ CorsFilter { config, inner }
+ }
+}
+
+/// An error used to reject requests that are forbidden by a `cors` filter.
+pub struct CorsForbidden {
+ kind: Forbidden,
+}
+
+#[derive(Debug)]
+enum Forbidden {
+ OriginNotAllowed,
+ MethodNotAllowed,
+ HeaderNotAllowed,
+}
+
+impl ::std::fmt::Debug for CorsForbidden {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.debug_tuple("CorsForbidden").field(&self.kind).finish()
+ }
+}
+
+impl ::std::fmt::Display for CorsForbidden {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ let detail = match self.kind {
+ Forbidden::OriginNotAllowed => "origin not allowed",
+ Forbidden::MethodNotAllowed => "request-method not allowed",
+ Forbidden::HeaderNotAllowed => "header not allowed",
+ };
+ write!(f, "CORS request forbidden: {}", detail)
+ }
+}
+
+impl StdError for CorsForbidden {}
+
+#[derive(Clone, Debug)]
+struct Configured {
+ cors: Builder,
+ allowed_headers_header: AccessControlAllowHeaders,
+ expose_headers_header: Option<AccessControlExposeHeaders>,
+ methods_header: AccessControlAllowMethods,
+}
+
+enum Validated {
+ Preflight(HeaderValue),
+ Simple(HeaderValue),
+ NotCors,
+}
+
+impl Configured {
+ fn check_request(
+ &self,
+ method: &http::Method,
+ headers: &http::HeaderMap,
+ ) -> Result<Validated, Forbidden> {
+ match (headers.get(header::ORIGIN), method) {
+ (Some(origin), &http::Method::OPTIONS) => {
+ // OPTIONS requests are preflight CORS requests...
+
+ if !self.is_origin_allowed(origin) {
+ return Err(Forbidden::OriginNotAllowed);
+ }
+
+ if let Some(req_method) = headers.get(header::ACCESS_CONTROL_REQUEST_METHOD) {
+ if !self.is_method_allowed(req_method) {
+ return Err(Forbidden::MethodNotAllowed);
+ }
+ } else {
+ log::trace!("preflight request missing access-control-request-method header");
+ return Err(Forbidden::MethodNotAllowed);
+ }
+
+ if let Some(req_headers) = headers.get(header::ACCESS_CONTROL_REQUEST_HEADERS) {
+ let headers = req_headers
+ .to_str()
+ .map_err(|_| Forbidden::HeaderNotAllowed)?;
+ for header in headers.split(',') {
+ if !self.is_header_allowed(header) {
+ return Err(Forbidden::HeaderNotAllowed);
+ }
+ }
+ }
+
+ Ok(Validated::Preflight(origin.clone()))
+ }
+ (Some(origin), _) => {
+ // Any other method, simply check for a valid origin...
+
+ log::trace!("origin header: {:?}", origin);
+ if self.is_origin_allowed(origin) {
+ Ok(Validated::Simple(origin.clone()))
+ } else {
+ Err(Forbidden::OriginNotAllowed)
+ }
+ }
+ (None, _) => {
+ // No `ORIGIN` header means this isn't CORS!
+ Ok(Validated::NotCors)
+ }
+ }
+ }
+
+ fn is_method_allowed(&self, header: &HeaderValue) -> bool {
+ http::Method::from_bytes(header.as_bytes())
+ .map(|method| self.cors.methods.contains(&method))
+ .unwrap_or(false)
+ }
+
+ fn is_header_allowed(&self, header: &str) -> bool {
+ HeaderName::from_bytes(header.as_bytes())
+ .map(|header| self.cors.allowed_headers.contains(&header))
+ .unwrap_or(false)
+ }
+
+ fn is_origin_allowed(&self, origin: &HeaderValue) -> bool {
+ if let Some(ref allowed) = self.cors.origins {
+ allowed.contains(origin)
+ } else {
+ true
+ }
+ }
+
+ fn append_preflight_headers(&self, headers: &mut http::HeaderMap) {
+ self.append_common_headers(headers);
+
+ headers.typed_insert(self.allowed_headers_header.clone());
+ headers.typed_insert(self.methods_header.clone());
+
+ if let Some(max_age) = self.cors.max_age {
+ headers.insert(header::ACCESS_CONTROL_MAX_AGE, max_age.into());
+ }
+ }
+
+ fn append_common_headers(&self, headers: &mut http::HeaderMap) {
+ if self.cors.credentials {
+ headers.insert(
+ header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
+ HeaderValue::from_static("true"),
+ );
+ }
+ if let Some(expose_headers_header) = &self.expose_headers_header {
+ headers.typed_insert(expose_headers_header.clone())
+ }
+ }
+}
+
+mod internal {
+ use std::future::Future;
+ use std::pin::Pin;
+ use std::sync::Arc;
+ use std::task::{Context, Poll};
+
+ use futures::{future, ready, TryFuture};
+ use headers::Origin;
+ use http::header;
+ use pin_project::pin_project;
+
+ use super::{Configured, CorsForbidden, Validated};
+ use crate::filter::{Filter, FilterBase, Internal, One};
+ use crate::generic::Either;
+ use crate::reject::{CombineRejection, Rejection};
+ use crate::route;
+
+ #[derive(Clone, Debug)]
+ pub struct CorsFilter<F> {
+ pub(super) config: Arc<Configured>,
+ pub(super) inner: F,
+ }
+
+ impl<F> FilterBase for CorsFilter<F>
+ where
+ F: Filter,
+ F::Extract: Send,
+ F::Future: Future,
+ F::Error: CombineRejection<Rejection>,
+ {
+ type Extract =
+ One<Either<One<Preflight>, One<Either<One<Wrapped<F::Extract>>, F::Extract>>>>;
+ type Error = <F::Error as CombineRejection<Rejection>>::One;
+ type Future = future::Either<
+ future::Ready<Result<Self::Extract, Self::Error>>,
+ WrappedFuture<F::Future>,
+ >;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ let validated =
+ route::with(|route| self.config.check_request(route.method(), route.headers()));
+
+ match validated {
+ Ok(Validated::Preflight(origin)) => {
+ let preflight = Preflight {
+ config: self.config.clone(),
+ origin,
+ };
+ future::Either::Left(future::ok((Either::A((preflight,)),)))
+ }
+ Ok(Validated::Simple(origin)) => future::Either::Right(WrappedFuture {
+ inner: self.inner.filter(Internal),
+ wrapped: Some((self.config.clone(), origin)),
+ }),
+ Ok(Validated::NotCors) => future::Either::Right(WrappedFuture {
+ inner: self.inner.filter(Internal),
+ wrapped: None,
+ }),
+ Err(err) => {
+ let rejection = crate::reject::known(CorsForbidden { kind: err });
+ future::Either::Left(future::err(rejection.into()))
+ }
+ }
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Preflight {
+ config: Arc<Configured>,
+ origin: header::HeaderValue,
+ }
+
+ impl crate::reply::Reply for Preflight {
+ fn into_response(self) -> crate::reply::Response {
+ let mut res = crate::reply::Response::default();
+ self.config.append_preflight_headers(res.headers_mut());
+ res.headers_mut()
+ .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, self.origin);
+ res
+ }
+ }
+
+ #[derive(Debug)]
+ pub struct Wrapped<R> {
+ config: Arc<Configured>,
+ inner: R,
+ origin: header::HeaderValue,
+ }
+
+ impl<R> crate::reply::Reply for Wrapped<R>
+ where
+ R: crate::reply::Reply,
+ {
+ fn into_response(self) -> crate::reply::Response {
+ let mut res = self.inner.into_response();
+ self.config.append_common_headers(res.headers_mut());
+ res.headers_mut()
+ .insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, self.origin);
+ res
+ }
+ }
+
+ #[pin_project]
+ #[derive(Debug)]
+ pub struct WrappedFuture<F> {
+ #[pin]
+ inner: F,
+ wrapped: Option<(Arc<Configured>, header::HeaderValue)>,
+ }
+
+ impl<F> Future for WrappedFuture<F>
+ where
+ F: TryFuture,
+ F::Error: CombineRejection<Rejection>,
+ {
+ type Output = Result<
+ One<Either<One<Preflight>, One<Either<One<Wrapped<F::Ok>>, F::Ok>>>>,
+ <F::Error as CombineRejection<Rejection>>::One,
+ >;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let pin = self.project();
+ match ready!(pin.inner.try_poll(cx)) {
+ Ok(inner) => {
+ let item = if let Some((config, origin)) = pin.wrapped.take() {
+ (Either::A((Wrapped {
+ config,
+ inner,
+ origin,
+ },)),)
+ } else {
+ (Either::B(inner),)
+ };
+ let item = (Either::B(item),);
+ Poll::Ready(Ok(item))
+ }
+ Err(err) => Poll::Ready(Err(err.into())),
+ }
+ }
+ }
+
+ pub trait Seconds {
+ fn seconds(self) -> u64;
+ }
+
+ impl Seconds for u32 {
+ fn seconds(self) -> u64 {
+ self.into()
+ }
+ }
+
+ impl Seconds for ::std::time::Duration {
+ fn seconds(self) -> u64 {
+ self.as_secs()
+ }
+ }
+
+ pub trait IntoOrigin {
+ fn into_origin(self) -> Origin;
+ }
+
+ impl<'a> IntoOrigin for &'a str {
+ fn into_origin(self) -> Origin {
+ let mut parts = self.splitn(2, "://");
+ let scheme = parts.next().expect("missing scheme");
+ let rest = parts.next().expect("missing scheme");
+
+ Origin::try_from_parts(scheme, rest, None).expect("invalid Origin")
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filters/ext.rs b/third_party/rust/warp/src/filters/ext.rs
new file mode 100644
index 0000000000..483a6db918
--- /dev/null
+++ b/third_party/rust/warp/src/filters/ext.rs
@@ -0,0 +1,36 @@
+//! Request Extensions
+
+use std::convert::Infallible;
+
+use futures::future;
+
+use crate::filter::{filter_fn_one, Filter};
+use crate::reject::{self, Rejection};
+
+/// Get a previously set extension of the current route.
+///
+/// If the extension doesn't exist, this rejects with a `MissingExtension`.
+pub fn get<T: Clone + Send + Sync + 'static>(
+) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
+ filter_fn_one(|route| {
+ let route = route
+ .extensions()
+ .get::<T>()
+ .cloned()
+ .ok_or_else(|| reject::known(MissingExtension { _p: () }));
+ future::ready(route)
+ })
+}
+
+/// Get a previously set extension of the current route.
+///
+/// If the extension doesn't exist, it yields `None`.
+pub fn optional<T: Clone + Send + Sync + 'static>(
+) -> impl Filter<Extract = (Option<T>,), Error = Infallible> + Copy {
+ filter_fn_one(|route| future::ok(route.extensions().get::<T>().cloned()))
+}
+
+unit_error! {
+ /// An error used to reject if `get` cannot find the extension.
+ pub MissingExtension: "Missing request extension"
+}
diff --git a/third_party/rust/warp/src/filters/fs.rs b/third_party/rust/warp/src/filters/fs.rs
new file mode 100644
index 0000000000..c79725c68a
--- /dev/null
+++ b/third_party/rust/warp/src/filters/fs.rs
@@ -0,0 +1,501 @@
+//! File System Filters
+
+use std::cmp;
+use std::convert::Infallible;
+use std::fs::Metadata;
+use std::future::Future;
+use std::io;
+use std::path::{Path, PathBuf};
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::Poll;
+
+use bytes::{Bytes, BytesMut};
+use futures::future::Either;
+use futures::{future, ready, stream, FutureExt, Stream, StreamExt, TryFutureExt};
+use headers::{
+ AcceptRanges, ContentLength, ContentRange, ContentType, HeaderMapExt, IfModifiedSince, IfRange,
+ IfUnmodifiedSince, LastModified, Range,
+};
+use http::StatusCode;
+use hyper::Body;
+use mime_guess;
+use tokio::fs::File as TkFile;
+use tokio::io::AsyncRead;
+use urlencoding::decode;
+
+use crate::filter::{Filter, FilterClone, One};
+use crate::reject::{self, Rejection};
+use crate::reply::{Reply, Response};
+
+/// Creates a `Filter` that serves a File at the `path`.
+///
+/// Does not filter out based on any information of the request. Always serves
+/// the file at the exact `path` provided. Thus, this can be used to serve a
+/// single file with `GET`s, but could also be used in combination with other
+/// filters, such as after validating in `POST` request, wanting to return a
+/// specific file as the body.
+///
+/// For serving a directory, see [dir](dir).
+///
+/// # Example
+///
+/// ```
+/// // Always serves this file from the file system.
+/// let route = warp::fs::file("/www/static/app.js");
+/// ```
+pub fn file(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
+ let path = Arc::new(path.into());
+ crate::any()
+ .map(move || {
+ log::trace!("file: {:?}", path);
+ ArcPath(path.clone())
+ })
+ .and(conditionals())
+ .and_then(|path, conditionals| file_reply(path, conditionals))
+}
+
+/// Creates a `Filter` that serves a directory at the base `path` joined
+/// by the request path.
+///
+/// This can be used to serve "static files" from a directory. By far the most
+/// common pattern of serving static files is for `GET` requests, so this
+/// filter automatically includes a `GET` check.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Matches requests that start with `/static`,
+/// // and then uses the rest of that path to lookup
+/// // and serve a file from `/www/static`.
+/// let route = warp::path("static")
+/// .and(warp::fs::dir("/www/static"));
+///
+/// // For example:
+/// // - `GET /static/app.js` would serve the file `/www/static/app.js`
+/// // - `GET /static/css/app.css` would serve the file `/www/static/css/app.css`
+/// ```
+pub fn dir(path: impl Into<PathBuf>) -> impl FilterClone<Extract = One<File>, Error = Rejection> {
+ let base = Arc::new(path.into());
+ crate::get()
+ .and(path_from_tail(base))
+ .and(conditionals())
+ .and_then(file_reply)
+}
+
+fn path_from_tail(
+ base: Arc<PathBuf>,
+) -> impl FilterClone<Extract = One<ArcPath>, Error = Rejection> {
+ crate::path::tail().and_then(move |tail: crate::path::Tail| {
+ future::ready(sanitize_path(base.as_ref(), tail.as_str())).and_then(|mut buf| async {
+ let is_dir = tokio::fs::metadata(buf.clone())
+ .await
+ .map(|m| m.is_dir())
+ .unwrap_or(false);
+
+ if is_dir {
+ log::debug!("dir: appending index.html to directory path");
+ buf.push("index.html");
+ }
+ log::trace!("dir: {:?}", buf);
+ Ok(ArcPath(Arc::new(buf)))
+ })
+ })
+}
+
+fn sanitize_path(base: impl AsRef<Path>, tail: &str) -> Result<PathBuf, Rejection> {
+ let mut buf = PathBuf::from(base.as_ref());
+ let p = match decode(tail) {
+ Ok(p) => p,
+ Err(err) => {
+ log::debug!("dir: failed to decode route={:?}: {:?}", tail, err);
+ // FromUrlEncodingError doesn't implement StdError
+ return Err(reject::not_found());
+ }
+ };
+ log::trace!("dir? base={:?}, route={:?}", base.as_ref(), p);
+ for seg in p.split('/') {
+ if seg.starts_with("..") {
+ log::warn!("dir: rejecting segment starting with '..'");
+ return Err(reject::not_found());
+ } else if seg.contains('\\') {
+ log::warn!("dir: rejecting segment containing with backslash (\\)");
+ return Err(reject::not_found());
+ } else {
+ buf.push(seg);
+ }
+ }
+ Ok(buf)
+}
+
+#[derive(Debug)]
+struct Conditionals {
+ if_modified_since: Option<IfModifiedSince>,
+ if_unmodified_since: Option<IfUnmodifiedSince>,
+ if_range: Option<IfRange>,
+ range: Option<Range>,
+}
+
+enum Cond {
+ NoBody(Response),
+ WithBody(Option<Range>),
+}
+
+impl Conditionals {
+ fn check(self, last_modified: Option<LastModified>) -> Cond {
+ if let Some(since) = self.if_unmodified_since {
+ let precondition = last_modified
+ .map(|time| since.precondition_passes(time.into()))
+ .unwrap_or(false);
+
+ log::trace!(
+ "if-unmodified-since? {:?} vs {:?} = {}",
+ since,
+ last_modified,
+ precondition
+ );
+ if !precondition {
+ let mut res = Response::new(Body::empty());
+ *res.status_mut() = StatusCode::PRECONDITION_FAILED;
+ return Cond::NoBody(res);
+ }
+ }
+
+ if let Some(since) = self.if_modified_since {
+ log::trace!(
+ "if-modified-since? header = {:?}, file = {:?}",
+ since,
+ last_modified
+ );
+ let unmodified = last_modified
+ .map(|time| !since.is_modified(time.into()))
+ // no last_modified means its always modified
+ .unwrap_or(false);
+ if unmodified {
+ let mut res = Response::new(Body::empty());
+ *res.status_mut() = StatusCode::NOT_MODIFIED;
+ return Cond::NoBody(res);
+ }
+ }
+
+ if let Some(if_range) = self.if_range {
+ log::trace!("if-range? {:?} vs {:?}", if_range, last_modified);
+ let can_range = !if_range.is_modified(None, last_modified.as_ref());
+
+ if !can_range {
+ return Cond::WithBody(None);
+ }
+ }
+
+ Cond::WithBody(self.range)
+ }
+}
+
+fn conditionals() -> impl Filter<Extract = One<Conditionals>, Error = Infallible> + Copy {
+ crate::header::optional2()
+ .and(crate::header::optional2())
+ .and(crate::header::optional2())
+ .and(crate::header::optional2())
+ .map(
+ |if_modified_since, if_unmodified_since, if_range, range| Conditionals {
+ if_modified_since,
+ if_unmodified_since,
+ if_range,
+ range,
+ },
+ )
+}
+
+/// A file response.
+#[derive(Debug)]
+pub struct File {
+ resp: Response,
+}
+
+// Silly wrapper since Arc<PathBuf> doesn't implement AsRef<Path> ;_;
+#[derive(Clone, Debug)]
+struct ArcPath(Arc<PathBuf>);
+
+impl AsRef<Path> for ArcPath {
+ fn as_ref(&self) -> &Path {
+ (*self.0).as_ref()
+ }
+}
+
+impl Reply for File {
+ fn into_response(self) -> Response {
+ self.resp
+ }
+}
+
+fn file_reply(
+ path: ArcPath,
+ conditionals: Conditionals,
+) -> impl Future<Output = Result<File, Rejection>> + Send {
+ TkFile::open(path.clone()).then(move |res| match res {
+ Ok(f) => Either::Left(file_conditional(f, path, conditionals)),
+ Err(err) => {
+ let rej = match err.kind() {
+ io::ErrorKind::NotFound => {
+ log::debug!("file not found: {:?}", path.as_ref().display());
+ reject::not_found()
+ }
+ io::ErrorKind::PermissionDenied => {
+ log::warn!("file permission denied: {:?}", path.as_ref().display());
+ reject::known(FilePermissionError { _p: () })
+ }
+ _ => {
+ log::error!(
+ "file open error (path={:?}): {} ",
+ path.as_ref().display(),
+ err
+ );
+ reject::known(FileOpenError { _p: () })
+ }
+ };
+ Either::Right(future::err(rej))
+ }
+ })
+}
+
+async fn file_metadata(f: TkFile) -> Result<(TkFile, Metadata), Rejection> {
+ match f.metadata().await {
+ Ok(meta) => Ok((f, meta)),
+ Err(err) => {
+ log::debug!("file metadata error: {}", err);
+ Err(reject::not_found())
+ }
+ }
+}
+
+fn file_conditional(
+ f: TkFile,
+ path: ArcPath,
+ conditionals: Conditionals,
+) -> impl Future<Output = Result<File, Rejection>> + Send {
+ file_metadata(f).map_ok(move |(file, meta)| {
+ let mut len = meta.len();
+ let modified = meta.modified().ok().map(LastModified::from);
+
+ let resp = match conditionals.check(modified) {
+ Cond::NoBody(resp) => resp,
+ Cond::WithBody(range) => {
+ bytes_range(range, len)
+ .map(|(start, end)| {
+ let sub_len = end - start;
+ let buf_size = optimal_buf_size(&meta);
+ let stream = file_stream(file, buf_size, (start, end));
+ let body = Body::wrap_stream(stream);
+
+ let mut resp = Response::new(body);
+
+ if sub_len != len {
+ *resp.status_mut() = StatusCode::PARTIAL_CONTENT;
+ resp.headers_mut().typed_insert(
+ ContentRange::bytes(start..end, len).expect("valid ContentRange"),
+ );
+
+ len = sub_len;
+ }
+
+ let mime = mime_guess::from_path(path.as_ref()).first_or_octet_stream();
+
+ resp.headers_mut().typed_insert(ContentLength(len));
+ resp.headers_mut().typed_insert(ContentType::from(mime));
+ resp.headers_mut().typed_insert(AcceptRanges::bytes());
+
+ if let Some(last_modified) = modified {
+ resp.headers_mut().typed_insert(last_modified);
+ }
+
+ resp
+ })
+ .unwrap_or_else(|BadRange| {
+ // bad byte range
+ let mut resp = Response::new(Body::empty());
+ *resp.status_mut() = StatusCode::RANGE_NOT_SATISFIABLE;
+ resp.headers_mut()
+ .typed_insert(ContentRange::unsatisfied_bytes(len));
+ resp
+ })
+ }
+ };
+
+ File { resp }
+ })
+}
+
+struct BadRange;
+
+fn bytes_range(range: Option<Range>, max_len: u64) -> Result<(u64, u64), BadRange> {
+ use std::ops::Bound;
+
+ let range = if let Some(range) = range {
+ range
+ } else {
+ return Ok((0, max_len));
+ };
+
+ let ret = range
+ .iter()
+ .map(|(start, end)| {
+ let start = match start {
+ Bound::Unbounded => 0,
+ Bound::Included(s) => s,
+ Bound::Excluded(s) => s + 1,
+ };
+
+ let end = match end {
+ Bound::Unbounded => max_len,
+ Bound::Included(s) => s + 1,
+ Bound::Excluded(s) => s,
+ };
+
+ if start < end && end <= max_len {
+ Ok((start, end))
+ } else {
+ log::trace!("unsatisfiable byte range: {}-{}/{}", start, end, max_len);
+ Err(BadRange)
+ }
+ })
+ .next()
+ .unwrap_or(Ok((0, max_len)));
+ ret
+}
+
+fn file_stream(
+ mut file: TkFile,
+ buf_size: usize,
+ (start, end): (u64, u64),
+) -> impl Stream<Item = Result<Bytes, io::Error>> + Send {
+ use std::io::SeekFrom;
+
+ let seek = async move {
+ if start != 0 {
+ file.seek(SeekFrom::Start(start)).await?;
+ }
+ Ok(file)
+ };
+
+ seek.into_stream()
+ .map(move |result| {
+ let mut buf = BytesMut::new();
+ let mut len = end - start;
+ let mut f = match result {
+ Ok(f) => f,
+ Err(f) => return Either::Left(stream::once(future::err(f))),
+ };
+
+ Either::Right(stream::poll_fn(move |cx| {
+ if len == 0 {
+ return Poll::Ready(None);
+ }
+ reserve_at_least(&mut buf, buf_size);
+
+ let n = match ready!(Pin::new(&mut f).poll_read_buf(cx, &mut buf)) {
+ Ok(n) => n as u64,
+ Err(err) => {
+ log::debug!("file read error: {}", err);
+ return Poll::Ready(Some(Err(err)));
+ }
+ };
+
+ if n == 0 {
+ log::debug!("file read found EOF before expected length");
+ return Poll::Ready(None);
+ }
+
+ let mut chunk = buf.split().freeze();
+ if n > len {
+ chunk = chunk.split_to(len as usize);
+ len = 0;
+ } else {
+ len -= n;
+ }
+
+ Poll::Ready(Some(Ok(chunk)))
+ }))
+ })
+ .flatten()
+}
+
+fn reserve_at_least(buf: &mut BytesMut, cap: usize) {
+ if buf.capacity() - buf.len() < cap {
+ buf.reserve(cap);
+ }
+}
+
+const DEFAULT_READ_BUF_SIZE: usize = 8_192;
+
+fn optimal_buf_size(metadata: &Metadata) -> usize {
+ let block_size = get_block_size(metadata);
+
+ // If file length is smaller than block size, don't waste space
+ // reserving a bigger-than-needed buffer.
+ cmp::min(block_size as u64, metadata.len()) as usize
+}
+
+#[cfg(unix)]
+fn get_block_size(metadata: &Metadata) -> usize {
+ use std::os::unix::fs::MetadataExt;
+ //TODO: blksize() returns u64, should handle bad cast...
+ //(really, a block size bigger than 4gb?)
+
+ // Use device blocksize unless it's really small.
+ cmp::max(metadata.blksize() as usize, DEFAULT_READ_BUF_SIZE)
+}
+
+#[cfg(not(unix))]
+fn get_block_size(_metadata: &Metadata) -> usize {
+ DEFAULT_READ_BUF_SIZE
+}
+
+// ===== Rejections =====
+
+unit_error! {
+ pub(crate) FileOpenError: "file open error"
+}
+
+unit_error! {
+ pub(crate) FilePermissionError: "file perimission error"
+}
+
+#[cfg(test)]
+mod tests {
+ use super::sanitize_path;
+ use bytes::BytesMut;
+
+ #[test]
+ fn test_sanitize_path() {
+ let base = "/var/www";
+
+ fn p(s: &str) -> &::std::path::Path {
+ s.as_ref()
+ }
+
+ assert_eq!(
+ sanitize_path(base, "/foo.html").unwrap(),
+ p("/var/www/foo.html")
+ );
+
+ // bad paths
+ sanitize_path(base, "/../foo.html").expect_err("dot dot");
+
+ sanitize_path(base, "/C:\\/foo.html").expect_err("C:\\");
+ }
+
+ #[test]
+ fn test_reserve_at_least() {
+ let mut buf = BytesMut::new();
+ let cap = 8_192;
+
+ assert_eq!(buf.len(), 0);
+ assert_eq!(buf.capacity(), 0);
+
+ super::reserve_at_least(&mut buf, cap);
+ assert_eq!(buf.len(), 0);
+ assert_eq!(buf.capacity(), cap);
+ }
+}
diff --git a/third_party/rust/warp/src/filters/header.rs b/third_party/rust/warp/src/filters/header.rs
new file mode 100644
index 0000000000..5e79c25db8
--- /dev/null
+++ b/third_party/rust/warp/src/filters/header.rs
@@ -0,0 +1,203 @@
+//! Header Filters
+//!
+//! These filters are used to interact with the Request HTTP headers. Some
+//! of them, like `exact` and `exact_ignore_case`, are just predicates,
+//! they don't extract any values. The `header` filter allows parsing
+//! a type from any header.
+use std::convert::Infallible;
+use std::str::FromStr;
+
+use futures::future;
+use headers::{Header, HeaderMapExt};
+use http::HeaderMap;
+
+use crate::filter::{filter_fn, filter_fn_one, Filter, One};
+use crate::reject::{self, Rejection};
+
+/// Create a `Filter` that tries to parse the specified header.
+///
+/// This `Filter` will look for a header with supplied name, and try to
+/// parse to a `T`, otherwise rejects the request.
+///
+/// # Example
+///
+/// ```
+/// use std::net::SocketAddr;
+///
+/// // Parse `content-length: 100` as a `u64`
+/// let content_length = warp::header::<u64>("content-length");
+///
+/// // Parse `host: 127.0.0.1:8080` as a `SocketAddr
+/// let local_host = warp::header::<SocketAddr>("host");
+///
+/// // Parse `foo: bar` into a `String`
+/// let foo = warp::header::<String>("foo");
+/// ```
+pub fn header<T: FromStr + Send + 'static>(
+ name: &'static str,
+) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
+ filter_fn_one(move |route| {
+ log::trace!("header({:?})", name);
+ let route = route
+ .headers()
+ .get(name)
+ .ok_or_else(|| reject::missing_header(name))
+ .and_then(|value| value.to_str().map_err(|_| reject::invalid_header(name)))
+ .and_then(|s| T::from_str(s).map_err(|_| reject::invalid_header(name)));
+ future::ready(route)
+ })
+}
+
+pub(crate) fn header2<T: Header + Send + 'static>(
+) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
+ filter_fn_one(move |route| {
+ log::trace!("header2({:?})", T::name());
+ let route = route
+ .headers()
+ .typed_get()
+ .ok_or_else(|| reject::invalid_header(T::name().as_str()));
+ future::ready(route)
+ })
+}
+
+/// Create a `Filter` that tries to parse the specified header, if it exists.
+///
+/// If the header does not exist, it yields `None`. Otherwise, it will try to
+/// parse as a `T`, and if it fails, a invalid header rejection is return. If
+/// successful, the filter yields `Some(T)`.
+///
+/// # Example
+///
+/// ```
+/// // Grab the `authorization` header if it exists.
+/// let opt_auth = warp::header::optional::<String>("authorization");
+/// ```
+pub fn optional<T>(
+ name: &'static str,
+) -> impl Filter<Extract = One<Option<T>>, Error = Rejection> + Copy
+where
+ T: FromStr + Send + 'static,
+{
+ filter_fn_one(move |route| {
+ log::trace!("optional({:?})", name);
+ let result = route.headers().get(name).map(|value| {
+ value
+ .to_str()
+ .map_err(|_| reject::invalid_header(name))?
+ .parse::<T>()
+ .map_err(|_| reject::invalid_header(name))
+ });
+
+ match result {
+ Some(Ok(t)) => future::ok(Some(t)),
+ Some(Err(e)) => future::err(e),
+ None => future::ok(None),
+ }
+ })
+}
+
+pub(crate) fn optional2<T>() -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
+where
+ T: Header + Send + 'static,
+{
+ filter_fn_one(move |route| future::ready(Ok(route.headers().typed_get())))
+}
+
+/* TODO
+pub fn exact2<T>(header: T) -> impl FilterClone<Extract=(), Error=Rejection>
+where
+ T: Header + PartialEq + Clone + Send,
+{
+ filter_fn(move |route| {
+ log::trace!("exact2({:?})", T::NAME);
+ route.headers()
+ .typed_get::<T>()
+ .and_then(|val| if val == header {
+ Some(())
+ } else {
+ None
+ })
+ .ok_or_else(|| reject::bad_request())
+ })
+}
+*/
+
+/// Create a `Filter` that requires a header to match the value exactly.
+///
+/// This `Filter` will look for a header with supplied name and the exact
+/// value, otherwise rejects the request.
+///
+/// # Example
+///
+/// ```
+/// // Require `dnt: 1` header to be set.
+/// let must_dnt = warp::header::exact("dnt", "1");
+/// ```
+pub fn exact(
+ name: &'static str,
+ value: &'static str,
+) -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ filter_fn(move |route| {
+ log::trace!("exact?({:?}, {:?})", name, value);
+ let route = route
+ .headers()
+ .get(name)
+ .ok_or_else(|| reject::missing_header(name))
+ .and_then(|val| {
+ if val == value {
+ Ok(())
+ } else {
+ Err(reject::invalid_header(name))
+ }
+ });
+ future::ready(route)
+ })
+}
+
+/// Create a `Filter` that requires a header to match the value exactly.
+///
+/// This `Filter` will look for a header with supplied name and the exact
+/// value, ignoring ASCII case, otherwise rejects the request.
+///
+/// # Example
+///
+/// ```
+/// // Require `connection: keep-alive` header to be set.
+/// let keep_alive = warp::header::exact("connection", "keep-alive");
+/// ```
+pub fn exact_ignore_case(
+ name: &'static str,
+ value: &'static str,
+) -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ filter_fn(move |route| {
+ log::trace!("exact_ignore_case({:?}, {:?})", name, value);
+ let route = route
+ .headers()
+ .get(name)
+ .ok_or_else(|| reject::missing_header(name))
+ .and_then(|val| {
+ if val.as_bytes().eq_ignore_ascii_case(value.as_bytes()) {
+ Ok(())
+ } else {
+ Err(reject::invalid_header(name))
+ }
+ });
+ future::ready(route)
+ })
+}
+
+/// Create a `Filter` that returns a clone of the request's `HeaderMap`.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Filter, http::HeaderMap};
+///
+/// let headers = warp::header::headers_cloned()
+/// .map(|headers: HeaderMap| {
+/// format!("header count: {}", headers.len())
+/// });
+/// ```
+pub fn headers_cloned() -> impl Filter<Extract = One<HeaderMap>, Error = Infallible> + Copy {
+ filter_fn_one(|route| future::ok(route.headers().clone()))
+}
diff --git a/third_party/rust/warp/src/filters/log.rs b/third_party/rust/warp/src/filters/log.rs
new file mode 100644
index 0000000000..c3ff7c3b26
--- /dev/null
+++ b/third_party/rust/warp/src/filters/log.rs
@@ -0,0 +1,275 @@
+//! Logger Filters
+
+use std::fmt;
+use std::net::SocketAddr;
+use std::time::{Duration, Instant};
+
+use http::{self, header, StatusCode};
+
+use crate::filter::{Filter, WrapSealed};
+use crate::reject::IsReject;
+use crate::reply::Reply;
+use crate::route::Route;
+
+use self::internal::WithLog;
+
+/// Create a wrapping filter with the specified `name` as the `target`.
+///
+/// This uses the default access logging format, and log records produced
+/// will have their `target` set to `name`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // If using something like `pretty_env_logger`,
+/// // view logs by setting `RUST_LOG=example::api`.
+/// let log = warp::log("example::api");
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(log);
+/// ```
+pub fn log(name: &'static str) -> Log<impl Fn(Info) + Copy> {
+ let func = move |info: Info| {
+ // TODO?
+ // - response content length?
+ log::info!(
+ target: name,
+ "{} \"{} {} {:?}\" {} \"{}\" \"{}\" {:?}",
+ OptFmt(info.route.remote_addr()),
+ info.method(),
+ info.path(),
+ info.route.version(),
+ info.status().as_u16(),
+ OptFmt(info.referer()),
+ OptFmt(info.user_agent()),
+ info.elapsed(),
+ );
+ };
+ Log { func }
+}
+
+/// Create a wrapping filter that receives `warp::log::Info`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let log = warp::log::custom(|info| {
+/// // Use a log macro, or slog, or println, or whatever!
+/// eprintln!(
+/// "{} {} {}",
+/// info.method(),
+/// info.path(),
+/// info.status(),
+/// );
+/// });
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(log);
+/// ```
+pub fn custom<F>(func: F) -> Log<F>
+where
+ F: Fn(Info),
+{
+ Log { func }
+}
+
+/// Decorates a [`Filter`](crate::Filter) to log requests and responses.
+#[derive(Clone, Copy, Debug)]
+pub struct Log<F> {
+ func: F,
+}
+
+/// Information about the request/response that can be used to prepare log lines.
+#[allow(missing_debug_implementations)]
+pub struct Info<'a> {
+ route: &'a Route,
+ start: Instant,
+ status: StatusCode,
+}
+
+impl<FN, F> WrapSealed<F> for Log<FN>
+where
+ FN: Fn(Info) + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+{
+ type Wrapped = WithLog<FN, F>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ WithLog {
+ filter,
+ log: self.clone(),
+ }
+ }
+}
+
+impl<'a> Info<'a> {
+ /// View the remote `SocketAddr` of the request.
+ pub fn remote_addr(&self) -> Option<SocketAddr> {
+ self.route.remote_addr()
+ }
+
+ /// View the `http::Method` of the request.
+ pub fn method(&self) -> &http::Method {
+ self.route.method()
+ }
+
+ /// View the URI path of the request.
+ pub fn path(&self) -> &str {
+ self.route.full_path()
+ }
+
+ /// View the `http::Version` of the request.
+ pub fn version(&self) -> http::Version {
+ self.route.version()
+ }
+
+ /// View the `http::StatusCode` of the response.
+ pub fn status(&self) -> http::StatusCode {
+ self.status
+ }
+
+ /// View the referer of the request.
+ pub fn referer(&self) -> Option<&str> {
+ self.route
+ .headers()
+ .get(header::REFERER)
+ .and_then(|v| v.to_str().ok())
+ }
+
+ /// View the user agent of the request.
+ pub fn user_agent(&self) -> Option<&str> {
+ self.route
+ .headers()
+ .get(header::USER_AGENT)
+ .and_then(|v| v.to_str().ok())
+ }
+
+ /// View the `Duration` that elapsed for the request.
+ pub fn elapsed(&self) -> Duration {
+ tokio::time::Instant::now().into_std() - self.start
+ }
+
+ /// View the host of the request
+ pub fn host(&self) -> Option<&str> {
+ self.route
+ .headers()
+ .get(header::HOST)
+ .and_then(|v| v.to_str().ok())
+ }
+}
+
+struct OptFmt<T>(Option<T>);
+
+impl<T: fmt::Display> fmt::Display for OptFmt<T> {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ if let Some(ref t) = self.0 {
+ fmt::Display::fmt(t, f)
+ } else {
+ f.write_str("-")
+ }
+ }
+}
+
+mod internal {
+ use std::future::Future;
+ use std::pin::Pin;
+ use std::task::{Context, Poll};
+ use std::time::Instant;
+
+ use futures::{ready, TryFuture};
+ use pin_project::pin_project;
+
+ use super::{Info, Log};
+ use crate::filter::{Filter, FilterBase, Internal};
+ use crate::reject::IsReject;
+ use crate::reply::{Reply, Response};
+ use crate::route;
+
+ #[allow(missing_debug_implementations)]
+ pub struct Logged(pub(super) Response);
+
+ impl Reply for Logged {
+ #[inline]
+ fn into_response(self) -> Response {
+ self.0
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ #[derive(Clone, Copy)]
+ pub struct WithLog<FN, F> {
+ pub(super) filter: F,
+ pub(super) log: Log<FN>,
+ }
+
+ impl<FN, F> FilterBase for WithLog<FN, F>
+ where
+ FN: Fn(Info) + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+ {
+ type Extract = (Logged,);
+ type Error = F::Error;
+ type Future = WithLogFuture<FN, F::Future>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ let started = tokio::time::Instant::now().into_std();
+ WithLogFuture {
+ log: self.log.clone(),
+ future: self.filter.filter(Internal),
+ started,
+ }
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ #[pin_project]
+ pub struct WithLogFuture<FN, F> {
+ log: Log<FN>,
+ #[pin]
+ future: F,
+ started: Instant,
+ }
+
+ impl<FN, F> Future for WithLogFuture<FN, F>
+ where
+ FN: Fn(Info),
+ F: TryFuture,
+ F::Ok: Reply,
+ F::Error: IsReject,
+ {
+ type Output = Result<(Logged,), F::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> {
+ let pin = self.as_mut().project();
+ let (result, status) = match ready!(pin.future.try_poll(cx)) {
+ Ok(reply) => {
+ let resp = reply.into_response();
+ let status = resp.status();
+ (Poll::Ready(Ok((Logged(resp),))), status)
+ }
+ Err(reject) => {
+ let status = reject.status();
+ (Poll::Ready(Err(reject)), status)
+ }
+ };
+
+ route::with(|route| {
+ (self.log.func)(Info {
+ route,
+ start: self.started,
+ status,
+ });
+ });
+
+ result
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filters/method.rs b/third_party/rust/warp/src/filters/method.rs
new file mode 100644
index 0000000000..ce5b767119
--- /dev/null
+++ b/third_party/rust/warp/src/filters/method.rs
@@ -0,0 +1,150 @@
+//! HTTP Method filters.
+//!
+//! The filters deal with the HTTP Method part of a request. Several here will
+//! match the request `Method`, and if not matched, will reject the request
+//! with a `405 Method Not Allowed`.
+//!
+//! There is also [`warp::method()`](method), which never rejects
+//! a request, and just extracts the method to be used in your filter chains.
+use futures::future;
+use http::Method;
+
+use crate::filter::{filter_fn, filter_fn_one, Filter, One};
+use crate::reject::Rejection;
+use std::convert::Infallible;
+
+/// Create a `Filter` that requires the request method to be `GET`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let get_only = warp::get().map(warp::reply);
+/// ```
+pub fn get() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::GET)
+}
+
+/// Create a `Filter` that requires the request method to be `POST`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let post_only = warp::post().map(warp::reply);
+/// ```
+pub fn post() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::POST)
+}
+
+/// Create a `Filter` that requires the request method to be `PUT`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let put_only = warp::put().map(warp::reply);
+/// ```
+pub fn put() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::PUT)
+}
+
+/// Create a `Filter` that requires the request method to be `DELETE`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let delete_only = warp::delete().map(warp::reply);
+/// ```
+pub fn delete() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::DELETE)
+}
+
+/// Create a `Filter` that requires the request method to be `HEAD`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let head_only = warp::head().map(warp::reply);
+/// ```
+pub fn head() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::HEAD)
+}
+
+/// Create a `Filter` that requires the request method to be `OPTIONS`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let options_only = warp::options().map(warp::reply);
+/// ```
+pub fn options() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::OPTIONS)
+}
+
+/// Create a `Filter` that requires the request method to be `PATCH`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let patch_only = warp::patch().map(warp::reply);
+/// ```
+pub fn patch() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ method_is(|| &Method::PATCH)
+}
+
+/// Extract the `Method` from the request.
+///
+/// This never rejects a request.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::method()
+/// .map(|method| {
+/// format!("You sent a {} request!", method)
+/// });
+/// ```
+pub fn method() -> impl Filter<Extract = One<Method>, Error = Infallible> + Copy {
+ filter_fn_one(|route| future::ok::<_, Infallible>(route.method().clone()))
+}
+
+// NOTE: This takes a static function instead of `&'static Method` directly
+// so that the `impl Filter` can be zero-sized. Moving it around should be
+// cheaper than holding a single static pointer (which would make it 1 word).
+fn method_is<F>(func: F) -> impl Filter<Extract = (), Error = Rejection> + Copy
+where
+ F: Fn() -> &'static Method + Copy,
+{
+ filter_fn(move |route| {
+ let method = func();
+ log::trace!("method::{:?}?: {:?}", method, route.method());
+ if route.method() == method {
+ future::ok(())
+ } else {
+ future::err(crate::reject::method_not_allowed())
+ }
+ })
+}
+
+#[cfg(test)]
+mod tests {
+ #[test]
+ fn method_size_of() {
+ // See comment on `method_is` function.
+ assert_eq!(std::mem::size_of_val(&super::get()), 0,);
+ }
+}
diff --git a/third_party/rust/warp/src/filters/mod.rs b/third_party/rust/warp/src/filters/mod.rs
new file mode 100644
index 0000000000..9225601d3c
--- /dev/null
+++ b/third_party/rust/warp/src/filters/mod.rs
@@ -0,0 +1,25 @@
+//! Built-in Filters
+//!
+//! This module mostly serves as documentation to group together the list of
+//! built-in filters. Most of these are available at more convenient paths.
+
+pub mod addr;
+pub mod any;
+pub mod body;
+pub mod cookie;
+pub mod cors;
+pub mod ext;
+pub mod fs;
+pub mod header;
+pub mod log;
+pub mod method;
+#[cfg(feature = "multipart")]
+pub mod multipart;
+pub mod path;
+pub mod query;
+pub mod reply;
+pub mod sse;
+#[cfg(feature = "websocket")]
+pub mod ws;
+
+pub use crate::filter::BoxedFilter;
diff --git a/third_party/rust/warp/src/filters/multipart.rs b/third_party/rust/warp/src/filters/multipart.rs
new file mode 100644
index 0000000000..2fbc4247a5
--- /dev/null
+++ b/third_party/rust/warp/src/filters/multipart.rs
@@ -0,0 +1,190 @@
+//! Multipart body filters
+//!
+//! Filters that extract a multipart body for a route.
+
+use std::fmt;
+use std::future::Future;
+use std::io::{Cursor, Read};
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use bytes::{Buf, Bytes};
+use futures::{future, Stream};
+use headers::ContentType;
+use mime::Mime;
+use multipart::server::Multipart;
+
+use crate::filter::{Filter, FilterBase, Internal};
+use crate::reject::{self, Rejection};
+
+// If not otherwise configured, default to 2MB.
+const DEFAULT_FORM_DATA_MAX_LENGTH: u64 = 1024 * 1024 * 2;
+
+/// A `Filter` to extract a `multipart/form-data` body from a request.
+///
+/// Create with the `warp::multipart::form()` function.
+#[derive(Debug, Clone)]
+pub struct FormOptions {
+ max_length: u64,
+}
+
+/// A `Stream` of multipart/form-data `Part`s.
+///
+/// Extracted with a `warp::multipart::form` filter.
+pub struct FormData {
+ inner: Multipart<Cursor<::bytes::Bytes>>,
+}
+
+/// A single "part" of a multipart/form-data body.
+///
+/// Yielded from the `FormData` stream.
+pub struct Part {
+ name: String,
+ filename: Option<String>,
+ content_type: Option<String>,
+ data: Option<Vec<u8>>,
+}
+
+/// Create a `Filter` to extact a `multipart/form-data` body from a request.
+///
+/// The extracted `FormData` type is a `Stream` of `Part`s, and each `Part`
+/// in turn is a `Stream` of bytes.
+pub fn form() -> FormOptions {
+ FormOptions {
+ max_length: DEFAULT_FORM_DATA_MAX_LENGTH,
+ }
+}
+
+// ===== impl Form =====
+
+impl FormOptions {
+ /// Set the maximum byte length allowed for this body.
+ ///
+ /// Defaults to 2MB.
+ pub fn max_length(mut self, max: u64) -> Self {
+ self.max_length = max;
+ self
+ }
+}
+
+type FormFut = Pin<Box<dyn Future<Output = Result<(FormData,), Rejection>> + Send>>;
+
+impl FilterBase for FormOptions {
+ type Extract = (FormData,);
+ type Error = Rejection;
+ type Future = FormFut;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ let boundary = super::header::header2::<ContentType>().and_then(|ct| {
+ let mime = Mime::from(ct);
+ let mime = mime
+ .get_param("boundary")
+ .map(|v| v.to_string())
+ .ok_or_else(|| reject::invalid_header("content-type"));
+ future::ready(mime)
+ });
+
+ let filt = super::body::content_length_limit(self.max_length)
+ .and(boundary)
+ .and(super::body::bytes())
+ .map(|boundary, body| FormData {
+ inner: Multipart::with_body(Cursor::new(body), boundary),
+ });
+
+ let fut = filt.filter(Internal);
+
+ Box::pin(fut)
+ }
+}
+
+// ===== impl FormData =====
+
+impl fmt::Debug for FormData {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("FormData").finish()
+ }
+}
+
+impl Stream for FormData {
+ type Item = Result<Part, crate::Error>;
+
+ fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
+ match (*self).inner.read_entry() {
+ Ok(Some(mut field)) => {
+ let mut data = Vec::new();
+ field
+ .data
+ .read_to_end(&mut data)
+ .map_err(crate::Error::new)?;
+ Poll::Ready(Some(Ok(Part {
+ name: field.headers.name.to_string(),
+ filename: field.headers.filename,
+ content_type: field.headers.content_type.map(|m| m.to_string()),
+ data: Some(data),
+ })))
+ }
+ Ok(None) => Poll::Ready(None),
+ Err(e) => Poll::Ready(Some(Err(crate::Error::new(e)))),
+ }
+ }
+}
+
+// ===== impl Part =====
+
+impl Part {
+ /// Get the name of this part.
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ /// Get the filename of this part, if present.
+ pub fn filename(&self) -> Option<&str> {
+ self.filename.as_ref().map(|s| &**s)
+ }
+
+ /// Get the content-type of this part, if present.
+ pub fn content_type(&self) -> Option<&str> {
+ self.content_type.as_ref().map(|s| &**s)
+ }
+
+ /// Asynchronously get some of the data for this `Part`.
+ pub async fn data(&mut self) -> Option<Result<impl Buf, crate::Error>> {
+ self.take_data()
+ }
+
+ /// Convert this `Part` into a `Stream` of `Buf`s.
+ pub fn stream(self) -> impl Stream<Item = Result<impl Buf, crate::Error>> {
+ PartStream(self)
+ }
+
+ fn take_data(&mut self) -> Option<Result<Bytes, crate::Error>> {
+ self.data.take().map(|vec| Ok(vec.into()))
+ }
+}
+
+impl fmt::Debug for Part {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let mut builder = f.debug_struct("Part");
+ builder.field("name", &self.name);
+
+ if let Some(ref filename) = self.filename {
+ builder.field("filename", filename);
+ }
+
+ if let Some(ref mime) = self.content_type {
+ builder.field("content_type", mime);
+ }
+
+ builder.finish()
+ }
+}
+
+struct PartStream(Part);
+
+impl Stream for PartStream {
+ type Item = Result<Bytes, crate::Error>;
+
+ fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Option<Self::Item>> {
+ Poll::Ready(self.0.take_data())
+ }
+}
diff --git a/third_party/rust/warp/src/filters/path.rs b/third_party/rust/warp/src/filters/path.rs
new file mode 100644
index 0000000000..bfaa33d82f
--- /dev/null
+++ b/third_party/rust/warp/src/filters/path.rs
@@ -0,0 +1,649 @@
+//! Path Filters
+//!
+//! The filters here work on the "path" of requests.
+//!
+//! - [`path`](./fn.path.html) matches a specific segment, like `/foo`.
+//! - [`param`](./fn.param.html) tries to parse a segment into a type, like `/:u16`.
+//! - [`end`](./fn.end.html) matches when the path end is found.
+//! - [`path!`](../../macro.path.html) eases combining multiple `path` and `param` filters.
+//!
+//! # Routing
+//!
+//! Routing in warp is simple yet powerful.
+//!
+//! First up, matching a single segment:
+//!
+//! ```
+//! use warp::Filter;
+//!
+//! // GET /hi
+//! let hi = warp::path("hi").map(|| {
+//! "Hello, World!"
+//! });
+//! ```
+//!
+//! How about multiple segments? It's easiest with the `path!` macro:
+//!
+//! ```
+//! # use warp::Filter;
+//! // GET /hello/from/warp
+//! let hello_from_warp = warp::path!("hello" / "from" / "warp").map(|| {
+//! "Hello from warp!"
+//! });
+//! ```
+//!
+//! Neat! But how do I handle **parameters** in paths?
+//!
+//! ```
+//! # use warp::Filter;
+//! // GET /sum/:u32/:u32
+//! let sum = warp::path!("sum" / u32 / u32).map(|a, b| {
+//! format!("{} + {} = {}", a, b, a + b)
+//! });
+//! ```
+//!
+//! In fact, any type that implements `FromStr` can be used, in any order:
+//!
+//! ```
+//! # use warp::Filter;
+//! // GET /:u16/times/:u16
+//! let times = warp::path!(u16 / "times" / u16).map(|a, b| {
+//! format!("{} times {} = {}", a, b, a * b)
+//! });
+//! ```
+//!
+//! Oh shoot, those math routes should be **mounted** at a different path,
+//! is that possible? Yep!
+//!
+//! ```
+//! # use warp::Filter;
+//! # let sum = warp::any().map(warp::reply);
+//! # let times = sum.clone();
+//! // GET /math/sum/:u32/:u32
+//! // GET /math/:u16/times/:u16
+//! let math = warp::path("math");
+//! let math_sum = math.and(sum);
+//! let math_times = math.and(times);
+//! ```
+//!
+//! What! `and`? What's that do?
+//!
+//! It combines the filters in a sort of "this and then that" order. In fact,
+//! it's exactly what the `path!` macro has been doing internally.
+//!
+//! ```
+//! # use warp::Filter;
+//! // GET /bye/:string
+//! let bye = warp::path("bye")
+//! .and(warp::path::param())
+//! .map(|name: String| {
+//! format!("Good bye, {}!", name)
+//! });
+//! ```
+//!
+//! Ah, so, can filters do things besides `and`?
+//!
+//! Why, yes they can! They can also `or`! As you might expect, `or` creates a
+//! "this or else that" chain of filters. If the first doesn't succeed, then
+//! it tries the other.
+//!
+//! So, those `math` routes could have been **mounted** all as one, with `or`.
+//!
+//!
+//! ```
+//! # use warp::Filter;
+//! # let sum = warp::path("sum");
+//! # let times = warp::path("times");
+//! // GET /math/sum/:u32/:u32
+//! // GET /math/:u16/times/:u16
+//! let math = warp::path("math")
+//! .and(sum.or(times));
+//! ```
+//!
+//! It turns out, using `or` is how you combine everything together into a
+//! single API.
+//!
+//! ```
+//! # use warp::Filter;
+//! # let hi = warp::path("hi");
+//! # let hello_from_warp = hi.clone();
+//! # let bye = hi.clone();
+//! # let math = hi.clone();
+//! // GET /hi
+//! // GET /hello/from/warp
+//! // GET /bye/:string
+//! // GET /math/sum/:u32/:u32
+//! // GET /math/:u16/times/:u16
+//! let routes = hi
+//! .or(hello_from_warp)
+//! .or(bye)
+//! .or(math);
+//! ```
+//!
+//! Note that you will generally want path filters to come **before** other filters
+//! like `body` or `headers`. If a different type of filter comes first, a request
+//! with an invalid body for route `/right-path-wrong-body` may try matching against `/wrong-path`
+//! and return the error from `/wrong-path` instead of the correct body-related error.
+
+use std::convert::Infallible;
+use std::fmt;
+use std::str::FromStr;
+
+use futures::future;
+use http::uri::PathAndQuery;
+
+use self::internal::Opaque;
+use crate::filter::{filter_fn, one, Filter, FilterBase, Internal, One, Tuple};
+use crate::reject::{self, Rejection};
+use crate::route::{self, Route};
+
+/// Create an exact match path segment `Filter`.
+///
+/// This will try to match exactly to the current request path segment.
+///
+/// # Note
+///
+/// - [`end()`](./fn.end.html) should be used to match the end of a path to avoid having
+/// filters for shorter paths like `/math` unintentionally match a longer
+/// path such as `/math/sum`
+/// - Path-related filters should generally come **before** other types of filters, such
+/// as those checking headers or body types. Including those other filters before
+/// the path checks may result in strange errors being returned because a given request
+/// does not match the parameters for a completely separate route.
+///
+/// # Panics
+///
+/// Exact path filters cannot be empty, or contain slashes.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Matches '/hello'
+/// let hello = warp::path("hello")
+/// .map(|| "Hello, World!");
+/// ```
+pub fn path<P>(p: P) -> Exact<Opaque<P>>
+where
+ P: AsRef<str>,
+{
+ let s = p.as_ref();
+ assert!(!s.is_empty(), "exact path segments should not be empty");
+ assert!(
+ !s.contains('/'),
+ "exact path segments should not contain a slash: {:?}",
+ s
+ );
+
+ Exact(Opaque(p))
+ /*
+ segment(move |seg| {
+ log::trace!("{:?}?: {:?}", p, seg);
+ if seg == p {
+ Ok(())
+ } else {
+ Err(reject::not_found())
+ }
+ })
+ */
+}
+
+/// A `Filter` matching an exact path segment.
+///
+/// Constructed from `path()` or `path!()`.
+#[allow(missing_debug_implementations)]
+#[derive(Clone, Copy)]
+pub struct Exact<P>(P);
+
+impl<P> FilterBase for Exact<P>
+where
+ P: AsRef<str>,
+{
+ type Extract = ();
+ type Error = Rejection;
+ type Future = future::Ready<Result<Self::Extract, Self::Error>>;
+
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ route::with(|route| {
+ let p = self.0.as_ref();
+ future::ready(with_segment(route, |seg| {
+ log::trace!("{:?}?: {:?}", p, seg);
+
+ if seg == p {
+ Ok(())
+ } else {
+ Err(reject::not_found())
+ }
+ }))
+ })
+ }
+}
+
+/// Matches the end of a route.
+///
+/// Note that _not_ including `end()` may result in shorter paths like
+/// `/math` unintentionally matching `/math/sum`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Matches '/'
+/// let hello = warp::path::end()
+/// .map(|| "Hello, World!");
+/// ```
+pub fn end() -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ filter_fn(move |route| {
+ if route.path().is_empty() {
+ future::ok(())
+ } else {
+ future::err(reject::not_found())
+ }
+ })
+}
+
+/// Extract a parameter from a path segment.
+///
+/// This will try to parse a value from the current request path
+/// segment, and if successful, the value is returned as the `Filter`'s
+/// "extracted" value.
+///
+/// If the value could not be parsed, rejects with a `404 Not Found`.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::path::param()
+/// .map(|id: u32| {
+/// format!("You asked for /{}", id)
+/// });
+/// ```
+pub fn param<T: FromStr + Send + 'static>(
+) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
+ filter_segment(|seg| {
+ log::trace!("param?: {:?}", seg);
+ if seg.is_empty() {
+ return Err(reject::not_found());
+ }
+ T::from_str(seg).map(one).map_err(|_| reject::not_found())
+ })
+}
+
+/// Extract the unmatched tail of the path.
+///
+/// This will return a `Tail`, which allows access to the rest of the path
+/// that previous filters have not already matched.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::path("foo")
+/// .and(warp::path::tail())
+/// .map(|tail| {
+/// // GET /foo/bar/baz would return "bar/baz".
+/// format!("The tail after foo is {:?}", tail)
+/// });
+/// ```
+pub fn tail() -> impl Filter<Extract = One<Tail>, Error = Infallible> + Copy {
+ filter_fn(move |route| {
+ let path = path_and_query(&route);
+ let idx = route.matched_path_index();
+
+ // Giving the user the full tail means we assume the full path
+ // has been matched now.
+ let end = path.path().len() - idx;
+ route.set_unmatched_path(end);
+
+ future::ok(one(Tail {
+ path,
+ start_index: idx,
+ }))
+ })
+}
+
+/// Represents that tail part of a request path, returned by the `tail()` filter.
+pub struct Tail {
+ path: PathAndQuery,
+ start_index: usize,
+}
+
+impl Tail {
+ /// Get the `&str` representation of the remaining path.
+ pub fn as_str(&self) -> &str {
+ &self.path.path()[self.start_index..]
+ }
+}
+
+impl fmt::Debug for Tail {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(self.as_str(), f)
+ }
+}
+
+/// Peek at the unmatched tail of the path, without affecting the matched path.
+///
+/// This will return a `Peek`, which allows access to the rest of the path
+/// that previous filters have not already matched. This differs from `tail`
+/// in that `peek` will **not** set the entire path as matched.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::path("foo")
+/// .and(warp::path::peek())
+/// .map(|peek| {
+/// // GET /foo/bar/baz would return "bar/baz".
+/// format!("The path after foo is {:?}", peek)
+/// });
+/// ```
+pub fn peek() -> impl Filter<Extract = One<Peek>, Error = Infallible> + Copy {
+ filter_fn(move |route| {
+ let path = path_and_query(&route);
+ let idx = route.matched_path_index();
+
+ future::ok(one(Peek {
+ path,
+ start_index: idx,
+ }))
+ })
+}
+
+/// Represents that tail part of a request path, returned by the `tail()` filter.
+pub struct Peek {
+ path: PathAndQuery,
+ start_index: usize,
+}
+
+impl Peek {
+ /// Get the `&str` representation of the remaining path.
+ pub fn as_str(&self) -> &str {
+ &self.path.path()[self.start_index..]
+ }
+
+ /// Get an iterator over the segments of the peeked path.
+ pub fn segments(&self) -> impl Iterator<Item = &str> {
+ self.as_str().split('/').filter(|seg| !seg.is_empty())
+ }
+}
+
+impl fmt::Debug for Peek {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(self.as_str(), f)
+ }
+}
+
+/// Returns the full request path, irrespective of other filters.
+///
+/// This will return a `FullPath`, which can be stringified to return the
+/// full path of the request.
+///
+/// This is more useful in generic pre/post-processing filters, and should
+/// probably not be used for request matching/routing.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Filter, path::FullPath};
+/// use std::{collections::HashMap, sync::{Arc, Mutex}};
+///
+/// let counts = Arc::new(Mutex::new(HashMap::new()));
+/// let access_counter = warp::path::full()
+/// .map(move |path: FullPath| {
+/// let mut counts = counts.lock().unwrap();
+///
+/// *counts.entry(path.as_str().to_string())
+/// .and_modify(|c| *c += 1)
+/// .or_insert(0)
+/// });
+///
+/// let route = warp::path("foo")
+/// .and(warp::path("bar"))
+/// .and(access_counter)
+/// .map(|count| {
+/// format!("This is the {}th visit to this URL!", count)
+/// });
+/// ```
+pub fn full() -> impl Filter<Extract = One<FullPath>, Error = Infallible> + Copy {
+ filter_fn(move |route| future::ok(one(FullPath(path_and_query(&route)))))
+}
+
+/// Represents the full request path, returned by the `full()` filter.
+pub struct FullPath(PathAndQuery);
+
+impl FullPath {
+ /// Get the `&str` representation of the request path.
+ pub fn as_str(&self) -> &str {
+ &self.0.path()
+ }
+}
+
+impl fmt::Debug for FullPath {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(self.as_str(), f)
+ }
+}
+
+fn filter_segment<F, U>(func: F) -> impl Filter<Extract = U, Error = Rejection> + Copy
+where
+ F: Fn(&str) -> Result<U, Rejection> + Copy,
+ U: Tuple + Send + 'static,
+{
+ filter_fn(move |route| future::ready(with_segment(route, func)))
+}
+
+fn with_segment<F, U>(route: &mut Route, func: F) -> Result<U, Rejection>
+where
+ F: Fn(&str) -> Result<U, Rejection>,
+{
+ let seg = segment(route);
+ let ret = func(seg);
+ if ret.is_ok() {
+ let idx = seg.len();
+ route.set_unmatched_path(idx);
+ }
+ ret
+}
+
+fn segment(route: &Route) -> &str {
+ route
+ .path()
+ .splitn(2, '/')
+ .next()
+ .expect("split always has at least 1")
+}
+
+fn path_and_query(route: &Route) -> PathAndQuery {
+ route
+ .uri()
+ .path_and_query()
+ .expect("server URIs should always have path_and_query")
+ .clone()
+}
+
+/// Convenient way to chain multiple path filters together.
+///
+/// Any number of either type identifiers or string expressions can be passed,
+/// each separated by a forward slash (`/`). Strings will be used to match
+/// path segments exactly, and type identifiers are used just like
+/// [`param`](filters::path::param) filters.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Match `/sum/:a/:b`
+/// let route = warp::path!("sum" / u32 / u32)
+/// .map(|a, b| {
+/// format!("{} + {} = {}", a, b, a + b)
+/// });
+/// ```
+///
+/// The equivalent filter chain without using the `path!` macro looks this:
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::path("sum")
+/// .and(warp::path::param::<u32>())
+/// .and(warp::path::param::<u32>())
+/// .and(warp::path::end())
+/// .map(|a, b| {
+/// format!("{} + {} = {}", a, b, a + b)
+/// });
+/// ```
+///
+/// # Path Prefixes
+///
+/// The `path!` macro automatically assumes the path should include an `end()`
+/// filter. To build up a path filter *prefix*, such that the `end()` isn't
+/// included, use the `/ ..` syntax.
+///
+///
+/// ```
+/// use warp::Filter;
+///
+/// let prefix = warp::path!("math" / "sum" / ..);
+///
+/// let sum = warp::path!(u32 / u32)
+/// .map(|a, b| {
+/// format!("{} + {} = {}", a, b, a + b)
+/// });
+///
+/// let help = warp::path::end()
+/// .map(|| "This API returns the sum of two u32's");
+///
+/// let api = prefix.and(sum.or(help));
+/// ```
+#[macro_export]
+macro_rules! path {
+ ($($pieces:tt)*) => ({
+ $crate::__internal_path!(@start $($pieces)*)
+ });
+}
+
+#[doc(hidden)]
+#[macro_export]
+// not public API
+macro_rules! __internal_path {
+ (@start ..) => ({
+ compile_error!("'..' cannot be the only segment")
+ });
+ (@start $first:tt $(/ $tail:tt)*) => ({
+ $crate::__internal_path!(@munch $crate::any(); [$first] [$(/ $tail)*])
+ });
+
+ (@munch $sum:expr; [$cur:tt] [/ $next:tt $(/ $tail:tt)*]) => ({
+ $crate::__internal_path!(@munch $crate::Filter::and($sum, $crate::__internal_path!(@segment $cur)); [$next] [$(/ $tail)*])
+ });
+ (@munch $sum:expr; [$cur:tt] []) => ({
+ $crate::__internal_path!(@last $sum; $cur)
+ });
+
+ (@last $sum:expr; ..) => (
+ $sum
+ );
+ (@last $sum:expr; $end:tt) => (
+ $crate::Filter::and(
+ $crate::Filter::and($sum, $crate::__internal_path!(@segment $end)),
+ $crate::path::end()
+ )
+ );
+
+ (@segment ..) => (
+ compile_error!("'..' must be the last segment")
+ );
+ (@segment $param:ty) => (
+ $crate::path::param::<$param>()
+ );
+ // Constructs a unique ZST so the &'static str pointer doesn't need to
+ // be carried around.
+ (@segment $s:literal) => ({
+ #[derive(Clone, Copy)]
+ struct __StaticPath;
+ impl ::std::convert::AsRef<str> for __StaticPath {
+ fn as_ref(&self) -> &str {
+ static S: &str = $s;
+ S
+ }
+ }
+ $crate::path(__StaticPath)
+ });
+}
+
+// path! compile fail tests
+
+/// ```compile_fail
+/// warp::path!("foo" / .. / "bar");
+/// ```
+///
+/// ```compile_fail
+/// warp::path!(.. / "bar");
+/// ```
+///
+/// ```compile_fail
+/// warp::path!("foo" ..);
+/// ```
+///
+/// ```compile_fail
+/// warp::path!("foo" / .. /);
+/// ```
+///
+/// ```compile_fail
+/// warp::path!(..);
+/// ```
+fn _path_macro_compile_fail() {}
+
+mod internal {
+ // Used to prevent users from naming this type.
+ //
+ // For instance, `Exact<Opaque<String>>` means a user cannot depend
+ // on it being `Exact<String>`.
+ #[allow(missing_debug_implementations)]
+ #[derive(Clone, Copy)]
+ pub struct Opaque<T>(pub(super) T);
+
+ impl<T: AsRef<str>> AsRef<str> for Opaque<T> {
+ #[inline]
+ fn as_ref(&self) -> &str {
+ self.0.as_ref()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_path_exact_size() {
+ use std::mem::{size_of, size_of_val};
+
+ assert_eq!(
+ size_of_val(&path("hello")),
+ size_of::<&str>(),
+ "exact(&str) is size of &str"
+ );
+
+ assert_eq!(
+ size_of_val(&path(String::from("world"))),
+ size_of::<String>(),
+ "exact(String) is size of String"
+ );
+
+ assert_eq!(
+ size_of_val(&path!("zst")),
+ size_of::<()>(),
+ "path!(&str) is ZST"
+ );
+ }
+}
diff --git a/third_party/rust/warp/src/filters/query.rs b/third_party/rust/warp/src/filters/query.rs
new file mode 100644
index 0000000000..be72243267
--- /dev/null
+++ b/third_party/rust/warp/src/filters/query.rs
@@ -0,0 +1,39 @@
+//! Query Filters
+
+use futures::future;
+use serde::de::DeserializeOwned;
+use serde_urlencoded;
+
+use crate::filter::{filter_fn_one, Filter, One};
+use crate::reject::{self, Rejection};
+
+/// Creates a `Filter` that decodes query parameters to the type `T`.
+///
+/// If cannot decode into a `T`, the request is rejected with a `400 Bad Request`.
+pub fn query<T: DeserializeOwned + Send + 'static>(
+) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy {
+ filter_fn_one(|route| {
+ let query_string = route.query().unwrap_or_else(|| {
+ log::debug!("route was called without a query string, defaulting to empty");
+ ""
+ });
+
+ let query_encoded = serde_urlencoded::from_str(query_string).map_err(|e| {
+ log::debug!("failed to decode query string '{}': {:?}", query_string, e);
+ reject::invalid_query()
+ });
+ future::ready(query_encoded)
+ })
+}
+
+/// Creates a `Filter` that returns the raw query string as type String.
+pub fn raw() -> impl Filter<Extract = One<String>, Error = Rejection> + Copy {
+ filter_fn_one(|route| {
+ let route = route
+ .query()
+ .map(|q| q.to_owned())
+ .map(Ok)
+ .unwrap_or_else(|| Err(reject::invalid_query()));
+ future::ready(route)
+ })
+}
diff --git a/third_party/rust/warp/src/filters/reply.rs b/third_party/rust/warp/src/filters/reply.rs
new file mode 100644
index 0000000000..c4c37bbd80
--- /dev/null
+++ b/third_party/rust/warp/src/filters/reply.rs
@@ -0,0 +1,257 @@
+//! Reply Filters
+//!
+//! These "filters" behave a little differently than the rest. Instead of
+//! being used directly on requests, these filters "wrap" other filters.
+//!
+//!
+//! ## Wrapping a `Filter` (`with`)
+//!
+//! ```
+//! use warp::Filter;
+//!
+//! let with_server = warp::reply::with::header("server", "warp");
+//!
+//! let route = warp::any()
+//! .map(warp::reply)
+//! .with(with_server);
+//! ```
+//!
+//! Wrapping allows adding in conditional logic *before* the request enters
+//! the inner filter (though the `with::header` wrapper does not).
+
+use std::convert::TryFrom;
+use std::sync::Arc;
+
+use http::header::{HeaderMap, HeaderName, HeaderValue};
+
+use self::sealed::{WithDefaultHeader_, WithHeader_, WithHeaders_};
+use crate::filter::{Filter, Map, WrapSealed};
+use crate::reply::Reply;
+
+/// Wrap a [`Filter`](crate::Filter) that adds a header to the reply.
+///
+/// # Note
+///
+/// This **only** adds a header if the underlying filter is successful, and
+/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
+/// header is not added.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Always set `foo: bar` header.
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(warp::reply::with::header("foo", "bar"));
+/// ```
+pub fn header<K, V>(name: K, value: V) -> WithHeader
+where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+{
+ let (name, value) = assert_name_and_value(name, value);
+ WithHeader { name, value }
+}
+
+/// Wrap a [`Filter`](crate::Filter) that adds multiple headers to the reply.
+///
+/// # Note
+///
+/// This **only** adds a header if the underlying filter is successful, and
+/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
+/// header is not added.
+///
+/// # Example
+///
+/// ```
+/// use warp::http::header::{HeaderMap, HeaderValue};
+/// use warp::Filter;
+///
+/// let mut headers = HeaderMap::new();
+/// headers.insert("server", HeaderValue::from_static("wee/0"));
+/// headers.insert("foo", HeaderValue::from_static("bar"));
+///
+/// // Always set `server: wee/0` and `foo: bar` headers.
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(warp::reply::with::headers(headers));
+/// ```
+pub fn headers(headers: HeaderMap) -> WithHeaders {
+ WithHeaders {
+ headers: Arc::new(headers),
+ }
+}
+
+// pub fn headers?
+
+/// Wrap a [`Filter`](crate::Filter) that adds a header to the reply, if they
+/// aren't already set.
+///
+/// # Note
+///
+/// This **only** adds a header if the underlying filter is successful, and
+/// returns a [`Reply`](Reply). If the underlying filter was rejected, the
+/// header is not added.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// // Set `server: warp` if not already set.
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(warp::reply::with::default_header("server", "warp"));
+/// ```
+pub fn default_header<K, V>(name: K, value: V) -> WithDefaultHeader
+where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+{
+ let (name, value) = assert_name_and_value(name, value);
+ WithDefaultHeader { name, value }
+}
+
+/// Wrap a `Filter` to always set a header.
+#[derive(Clone, Debug)]
+pub struct WithHeader {
+ name: HeaderName,
+ value: HeaderValue,
+}
+
+impl<F, R> WrapSealed<F> for WithHeader
+where
+ F: Filter<Extract = (R,)>,
+ R: Reply,
+{
+ type Wrapped = Map<F, WithHeader_>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ let with = WithHeader_ { with: self.clone() };
+ filter.map(with)
+ }
+}
+
+/// Wrap a `Filter` to always set multiple headers.
+#[derive(Clone, Debug)]
+pub struct WithHeaders {
+ headers: Arc<HeaderMap>,
+}
+
+impl<F, R> WrapSealed<F> for WithHeaders
+where
+ F: Filter<Extract = (R,)>,
+ R: Reply,
+{
+ type Wrapped = Map<F, WithHeaders_>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ let with = WithHeaders_ { with: self.clone() };
+ filter.map(with)
+ }
+}
+
+/// Wrap a `Filter` to set a header if it is not already set.
+#[derive(Clone, Debug)]
+pub struct WithDefaultHeader {
+ name: HeaderName,
+ value: HeaderValue,
+}
+
+impl<F, R> WrapSealed<F> for WithDefaultHeader
+where
+ F: Filter<Extract = (R,)>,
+ R: Reply,
+{
+ type Wrapped = Map<F, WithDefaultHeader_>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ let with = WithDefaultHeader_ { with: self.clone() };
+ filter.map(with)
+ }
+}
+
+fn assert_name_and_value<K, V>(name: K, value: V) -> (HeaderName, HeaderValue)
+where
+ HeaderName: TryFrom<K>,
+ <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
+ HeaderValue: TryFrom<V>,
+ <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
+{
+ let name = <HeaderName as TryFrom<K>>::try_from(name)
+ .map_err(Into::into)
+ .unwrap_or_else(|_| panic!("invalid header name"));
+
+ let value = <HeaderValue as TryFrom<V>>::try_from(value)
+ .map_err(Into::into)
+ .unwrap_or_else(|_| panic!("invalid header value"));
+
+ (name, value)
+}
+
+mod sealed {
+ use super::{WithDefaultHeader, WithHeader, WithHeaders};
+ use crate::generic::{Func, One};
+ use crate::reply::{Reply, Reply_};
+
+ #[derive(Clone)]
+ #[allow(missing_debug_implementations)]
+ pub struct WithHeader_ {
+ pub(super) with: WithHeader,
+ }
+
+ impl<R: Reply> Func<One<R>> for WithHeader_ {
+ type Output = Reply_;
+
+ fn call(&self, args: One<R>) -> Self::Output {
+ let mut resp = args.0.into_response();
+ // Use "insert" to replace any set header...
+ resp.headers_mut()
+ .insert(&self.with.name, self.with.value.clone());
+ Reply_(resp)
+ }
+ }
+
+ #[derive(Clone)]
+ #[allow(missing_debug_implementations)]
+ pub struct WithHeaders_ {
+ pub(super) with: WithHeaders,
+ }
+
+ impl<R: Reply> Func<One<R>> for WithHeaders_ {
+ type Output = Reply_;
+
+ fn call(&self, args: One<R>) -> Self::Output {
+ let mut resp = args.0.into_response();
+ for (name, value) in &*self.with.headers {
+ resp.headers_mut().insert(name, value.clone());
+ }
+ Reply_(resp)
+ }
+ }
+
+ #[derive(Clone)]
+ #[allow(missing_debug_implementations)]
+ pub struct WithDefaultHeader_ {
+ pub(super) with: WithDefaultHeader,
+ }
+
+ impl<R: Reply> Func<One<R>> for WithDefaultHeader_ {
+ type Output = Reply_;
+
+ fn call(&self, args: One<R>) -> Self::Output {
+ let mut resp = args.0.into_response();
+ resp.headers_mut()
+ .entry(&self.with.name)
+ .or_insert_with(|| self.with.value.clone());
+
+ Reply_(resp)
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filters/sse.rs b/third_party/rust/warp/src/filters/sse.rs
new file mode 100644
index 0000000000..6235339fb1
--- /dev/null
+++ b/third_party/rust/warp/src/filters/sse.rs
@@ -0,0 +1,715 @@
+//! Server-Sent Events (SSE)
+//!
+//! # Example
+//!
+//! ```
+//!
+//! use std::time::Duration;
+//! use std::convert::Infallible;
+//! use warp::{Filter, sse::ServerSentEvent};
+//! use futures::{stream::iter, Stream};
+//!
+//! fn sse_events() -> impl Stream<Item = Result<impl ServerSentEvent, Infallible>> {
+//! iter(vec![
+//! Ok(warp::sse::data("unnamed event").into_a()),
+//! Ok((
+//! warp::sse::event("chat"),
+//! warp::sse::data("chat message"),
+//! ).into_a().into_b()),
+//! Ok((
+//! warp::sse::id(13),
+//! warp::sse::event("chat"),
+//! warp::sse::data("other chat message\nwith next line"),
+//! warp::sse::retry(Duration::from_millis(5000)),
+//! ).into_b().into_b()),
+//! ])
+//! }
+//!
+//! let app = warp::path("push-notifications")
+//! .and(warp::get())
+//! .map(|| {
+//! warp::sse::reply(warp::sse::keep_alive().stream(sse_events()))
+//! });
+//! ```
+//!
+//! Each field already is event which can be sent to client.
+//! The events with multiple fields can be created by combining fields using tuples.
+//!
+//! See also the [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) API,
+//! which specifies the expected behavior of Server Sent Events.
+//!
+
+use std::borrow::Cow;
+use std::error::Error as StdError;
+use std::fmt::{self, Display, Formatter, Write};
+use std::future::Future;
+use std::pin::Pin;
+use std::str::FromStr;
+use std::task::{Context, Poll};
+use std::time::Duration;
+
+use futures::{future, Stream, TryStream, TryStreamExt};
+use http::header::{HeaderValue, CACHE_CONTROL, CONTENT_TYPE};
+use hyper::Body;
+use pin_project::pin_project;
+use serde::Serialize;
+use serde_json;
+use tokio::time::{self, Delay};
+
+use self::sealed::{
+ BoxedServerSentEvent, EitherServerSentEvent, SseError, SseField, SseFormat, SseWrapper,
+};
+use super::header;
+use crate::filter::One;
+use crate::reply::Response;
+use crate::{Filter, Rejection, Reply};
+
+/// Server-sent event message
+pub trait ServerSentEvent: SseFormat + Sized + Send + Sync + 'static {
+ /// Convert to either A
+ fn into_a<B>(self) -> EitherServerSentEvent<Self, B> {
+ EitherServerSentEvent::A(self)
+ }
+
+ /// Convert to either B
+ fn into_b<A>(self) -> EitherServerSentEvent<A, Self> {
+ EitherServerSentEvent::B(self)
+ }
+
+ /// Convert to boxed
+ fn boxed(self) -> BoxedServerSentEvent {
+ BoxedServerSentEvent(Box::new(self))
+ }
+}
+
+impl<T: SseFormat + Send + Sync + 'static> ServerSentEvent for T {}
+
+#[allow(missing_debug_implementations)]
+struct SseComment<T>(T);
+
+/// Comment field (":<comment-text>")
+pub fn comment<T>(comment: T) -> impl ServerSentEvent
+where
+ T: Display + Send + Sync + 'static,
+{
+ SseComment(comment)
+}
+
+impl<T: Display> SseFormat for SseComment<T> {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Comment = k {
+ k.fmt(f)?;
+ self.0.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseEvent<T>(T);
+
+/// Event name field ("event:<event-name>")
+pub fn event<T>(event: T) -> impl ServerSentEvent
+where
+ T: Display + Send + Sync + 'static,
+{
+ SseEvent(event)
+}
+
+impl<T: Display> SseFormat for SseEvent<T> {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Event = k {
+ k.fmt(f)?;
+ self.0.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseId<T>(T);
+
+/// Identifier field ("id:<identifier>")
+pub fn id<T>(id: T) -> impl ServerSentEvent
+where
+ T: Display + Send + Sync + 'static,
+{
+ SseId(id)
+}
+
+impl<T: Display> SseFormat for SseId<T> {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Id = k {
+ k.fmt(f)?;
+ self.0.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseRetry(Duration);
+
+/// Retry timeout field ("retry:<timeout>")
+pub fn retry(time: Duration) -> impl ServerSentEvent {
+ SseRetry(time)
+}
+
+impl SseFormat for SseRetry {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Retry = k {
+ k.fmt(f)?;
+
+ let secs = self.0.as_secs();
+ let millis = self.0.subsec_millis();
+
+ if secs > 0 {
+ // format seconds
+ secs.fmt(f)?;
+
+ // pad milliseconds
+ if millis < 10 {
+ f.write_str("00")?;
+ } else if millis < 100 {
+ f.write_char('0')?;
+ }
+ }
+
+ // format milliseconds
+ millis.fmt(f)?;
+
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseData<T>(T);
+
+/// Data field(s) ("data:<content>")
+///
+/// The multiline content will be transferred
+/// using sequential data fields, one per line.
+pub fn data<T>(data: T) -> impl ServerSentEvent
+where
+ T: Display + Send + Sync + 'static,
+{
+ SseData(data)
+}
+
+impl<T: Display> SseFormat for SseData<T> {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Data = k {
+ for line in self.0.to_string().split('\n') {
+ k.fmt(f)?;
+ line.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ }
+ Ok(())
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseJson<T>(T);
+
+/// Data field with JSON content ("data:<json-content>")
+pub fn json<T>(data: T) -> impl ServerSentEvent
+where
+ T: Serialize + Send + Sync + 'static,
+{
+ SseJson(data)
+}
+
+impl<T: Serialize> SseFormat for SseJson<T> {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ if let SseField::Data = k {
+ k.fmt(f)?;
+ serde_json::to_string(&self.0)
+ .map_err(|error| {
+ log::error!("sse::json error {}", error);
+ fmt::Error
+ })
+ .and_then(|data| data.fmt(f))?;
+ f.write_char('\n')?;
+ }
+ Ok(())
+ }
+}
+
+macro_rules! tuple_fmt {
+ (($($t:ident),+) => ($($i:tt),+)) => {
+ impl<$($t),+> SseFormat for ($($t),+)
+ where
+ $($t: SseFormat,)+
+ {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ $(self.$i.fmt_field(f, k)?;)+
+ Ok(())
+ }
+ }
+ };
+}
+
+tuple_fmt!((A, B) => (0, 1));
+tuple_fmt!((A, B, C) => (0, 1, 2));
+tuple_fmt!((A, B, C, D) => (0, 1, 2, 3));
+tuple_fmt!((A, B, C, D, E) => (0, 1, 2, 3, 4));
+tuple_fmt!((A, B, C, D, E, F) => (0, 1, 2, 3, 4, 5));
+tuple_fmt!((A, B, C, D, E, F, G) => (0, 1, 2, 3, 4, 5, 6));
+tuple_fmt!((A, B, C, D, E, F, G, H) => (0, 1, 2, 3, 4, 5, 6, 7));
+
+/// Gets the optional last event id from request.
+/// Typically this identifier represented as number or string.
+///
+/// ```
+/// let app = warp::sse::last_event_id::<u32>();
+///
+/// // The identifier is present
+/// async {
+/// assert_eq!(
+/// warp::test::request()
+/// .header("Last-Event-ID", "12")
+/// .filter(&app)
+/// .await
+/// .unwrap(),
+/// Some(12)
+/// );
+///
+/// // The identifier is missing
+/// assert_eq!(
+/// warp::test::request()
+/// .filter(&app)
+/// .await
+/// .unwrap(),
+/// None
+/// );
+///
+/// // The identifier is not a valid
+/// assert!(
+/// warp::test::request()
+/// .header("Last-Event-ID", "abc")
+/// .filter(&app)
+/// .await
+/// .is_err(),
+/// );
+///};
+/// ```
+pub fn last_event_id<T>() -> impl Filter<Extract = One<Option<T>>, Error = Rejection> + Copy
+where
+ T: FromStr + Send + Sync + 'static,
+{
+ header::optional("last-event-id")
+}
+
+/// Server-sent events reply
+///
+/// This function converts stream of server events into a `Reply` with:
+///
+/// - Status of `200 OK`
+/// - Header `content-type: text/event-stream`
+/// - Header `cache-control: no-cache`.
+///
+/// # Example
+///
+/// ```
+///
+/// use std::time::Duration;
+/// use futures::Stream;
+/// use futures::stream::iter;
+/// use std::convert::Infallible;
+/// use warp::{Filter, sse::ServerSentEvent};
+/// use serde_derive::Serialize;
+///
+/// #[derive(Serialize)]
+/// struct Msg {
+/// from: u32,
+/// text: String,
+/// }
+///
+/// fn event_stream() -> impl Stream<Item = Result<impl ServerSentEvent, Infallible>> {
+/// iter(vec![
+/// // Unnamed event with data only
+/// Ok(warp::sse::data("payload").boxed()),
+/// // Named event with ID and retry timeout
+/// Ok((
+/// warp::sse::data("other message\nwith next line"),
+/// warp::sse::event("chat"),
+/// warp::sse::id(1),
+/// warp::sse::retry(Duration::from_millis(15000))
+/// ).boxed()),
+/// // Event with JSON data
+/// Ok((
+/// warp::sse::id(2),
+/// warp::sse::json(Msg {
+/// from: 2,
+/// text: "hello".into(),
+/// }),
+/// ).boxed()),
+/// ])
+/// }
+///
+/// async {
+/// let app = warp::path("sse").and(warp::get()).map(|| {
+/// warp::sse::reply(event_stream())
+/// });
+///
+/// let res = warp::test::request()
+/// .method("GET")
+/// .header("Connection", "Keep-Alive")
+/// .path("/sse")
+/// .reply(&app)
+/// .await
+/// .into_body();
+///
+/// assert_eq!(
+/// res,
+/// r#"data:payload
+///
+/// event:chat
+/// data:other message
+/// data:with next line
+/// id:1
+/// retry:15000
+///
+/// data:{"from":2,"text":"hello"}
+/// id:2
+///
+/// "#
+/// );
+/// };
+/// ```
+pub fn reply<S>(event_stream: S) -> impl Reply
+where
+ S: TryStream + Send + Sync + 'static,
+ S::Ok: ServerSentEvent,
+ S::Error: StdError + Send + Sync + 'static,
+{
+ SseReply { event_stream }
+}
+
+#[allow(missing_debug_implementations)]
+struct SseReply<S> {
+ event_stream: S,
+}
+
+impl<S> Reply for SseReply<S>
+where
+ S: TryStream + Send + Sync + 'static,
+ S::Ok: ServerSentEvent,
+ S::Error: StdError + Send + Sync + 'static,
+{
+ #[inline]
+ fn into_response(self) -> Response {
+ let body_stream = self
+ .event_stream
+ .map_err(|error| {
+ // FIXME: error logging
+ log::error!("sse stream error: {}", error);
+ SseError
+ })
+ .into_stream()
+ .and_then(|event| future::ready(SseWrapper::format(&event)));
+
+ let mut res = Response::new(Body::wrap_stream(body_stream));
+ // Set appropriate content type
+ res.headers_mut()
+ .insert(CONTENT_TYPE, HeaderValue::from_static("text/event-stream"));
+ // Disable response body caching
+ res.headers_mut()
+ .insert(CACHE_CONTROL, HeaderValue::from_static("no-cache"));
+ res
+ }
+}
+
+/// Configure the interval between keep-alive messages, the content
+/// of each message, and the associated stream.
+#[derive(Debug)]
+pub struct KeepAlive {
+ comment_text: Cow<'static, str>,
+ max_interval: Duration,
+}
+
+impl KeepAlive {
+ /// Customize the interval between keep-alive messages.
+ ///
+ /// Default is 15 seconds.
+ pub fn interval(mut self, time: Duration) -> Self {
+ self.max_interval = time;
+ self
+ }
+
+ /// Customize the text of the keep-alive message.
+ ///
+ /// Default is an empty comment.
+ pub fn text(mut self, text: impl Into<Cow<'static, str>>) -> Self {
+ self.comment_text = text.into();
+ self
+ }
+
+ /// Wrap an event stream with keep-alive functionality.
+ ///
+ /// See [`keep_alive`](keep_alive) for more.
+ pub fn stream<S>(
+ self,
+ event_stream: S,
+ ) -> impl TryStream<
+ Ok = impl ServerSentEvent + Send + 'static,
+ Error = impl StdError + Send + Sync + 'static,
+ > + Send
+ + 'static
+ where
+ S: TryStream + Send + 'static,
+ S::Ok: ServerSentEvent + Send,
+ S::Error: StdError + Send + Sync + 'static,
+ {
+ let alive_timer = time::delay_for(self.max_interval);
+ SseKeepAlive {
+ event_stream,
+ comment_text: self.comment_text,
+ max_interval: self.max_interval,
+ alive_timer,
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+struct SseKeepAlive<S> {
+ #[pin]
+ event_stream: S,
+ comment_text: Cow<'static, str>,
+ max_interval: Duration,
+ alive_timer: Delay,
+}
+
+#[doc(hidden)]
+#[deprecated(note = "use warp::see:keep_alive() instead")]
+pub fn keep<S>(
+ event_stream: S,
+ keep_interval: impl Into<Option<Duration>>,
+) -> impl TryStream<
+ Ok = impl ServerSentEvent + Send + 'static,
+ Error = impl StdError + Send + Sync + 'static,
+> + Send
+ + 'static
+where
+ S: TryStream + Send + 'static,
+ S::Ok: ServerSentEvent + Send,
+ S::Error: StdError + Send + Sync + 'static,
+{
+ let max_interval = keep_interval
+ .into()
+ .unwrap_or_else(|| Duration::from_secs(15));
+ let alive_timer = time::delay_for(max_interval);
+ SseKeepAlive {
+ event_stream,
+ comment_text: Cow::Borrowed(""),
+ max_interval,
+ alive_timer,
+ }
+}
+
+/// Keeps event source connection alive when no events sent over a some time.
+///
+/// Some proxy servers may drop HTTP connection after a some timeout of inactivity.
+/// This function helps to prevent such behavior by sending comment events every
+/// `keep_interval` of inactivity.
+///
+/// By default the comment is `:` (an empty comment) and the time interval between
+/// events is 15 seconds. Both may be customized using the builder pattern
+/// as shown below.
+///
+/// ```
+/// use std::time::Duration;
+/// use std::convert::Infallible;
+/// use futures::StreamExt;
+/// use tokio::time::interval;
+/// use warp::{Filter, Stream, sse::ServerSentEvent};
+///
+/// // create server-sent event
+/// fn sse_counter(counter: u64) -> Result<impl ServerSentEvent, Infallible> {
+/// Ok(warp::sse::data(counter))
+/// }
+///
+/// fn main() {
+/// let routes = warp::path("ticks")
+/// .and(warp::get())
+/// .map(|| {
+/// let mut counter: u64 = 0;
+/// let event_stream = interval(Duration::from_secs(15)).map(move |_| {
+/// counter += 1;
+/// sse_counter(counter)
+/// });
+/// // reply using server-sent events
+/// let stream = warp::sse::keep_alive()
+/// .interval(Duration::from_secs(5))
+/// .text("thump".to_string())
+/// .stream(event_stream);
+/// warp::sse::reply(stream)
+/// });
+/// }
+/// ```
+///
+/// See [notes](https://www.w3.org/TR/2009/WD-eventsource-20090421/#notes).
+pub fn keep_alive() -> KeepAlive {
+ KeepAlive {
+ comment_text: Cow::Borrowed(""),
+ max_interval: Duration::from_secs(15),
+ }
+}
+
+impl<S> Stream for SseKeepAlive<S>
+where
+ S: TryStream + Send + 'static,
+ S::Ok: ServerSentEvent,
+ S::Error: StdError + Send + Sync + 'static,
+{
+ type Item = Result<EitherServerSentEvent<S::Ok, SseComment<Cow<'static, str>>>, SseError>;
+
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ let mut pin = self.project();
+ match pin.event_stream.try_poll_next(cx) {
+ Poll::Pending => match Pin::new(&mut pin.alive_timer).poll(cx) {
+ Poll::Pending => Poll::Pending,
+ Poll::Ready(_) => {
+ // restart timer
+ pin.alive_timer
+ .reset(tokio::time::Instant::now() + *pin.max_interval);
+ let comment_str = pin.comment_text.clone();
+ Poll::Ready(Some(Ok(EitherServerSentEvent::B(SseComment(comment_str)))))
+ }
+ },
+ Poll::Ready(Some(Ok(event))) => {
+ // restart timer
+ pin.alive_timer
+ .reset(tokio::time::Instant::now() + *pin.max_interval);
+ Poll::Ready(Some(Ok(EitherServerSentEvent::A(event))))
+ }
+ Poll::Ready(None) => Poll::Ready(None),
+ Poll::Ready(Some(Err(error))) => {
+ log::error!("sse::keep error: {}", error);
+ Poll::Ready(Some(Err(SseError)))
+ }
+ }
+ }
+}
+
+mod sealed {
+ use super::*;
+
+ /// SSE error type
+ #[derive(Debug)]
+ pub struct SseError;
+
+ impl Display for SseError {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "sse error")
+ }
+ }
+
+ impl StdError for SseError {}
+
+ impl Display for SseField {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ use self::SseField::*;
+ f.write_str(match self {
+ Event => "event:",
+ Id => "id:",
+ Data => "data:",
+ Retry => "retry:",
+ Comment => ":",
+ })
+ }
+ }
+
+ /// SSE field kind
+ #[allow(missing_debug_implementations)]
+ pub enum SseField {
+ /// Event name field
+ Event,
+ /// Event id field
+ Id,
+ /// Event data field
+ Data,
+ /// Retry timeout field
+ Retry,
+ /// Comment field
+ Comment,
+ }
+
+ /// SSE formatter trait
+ pub trait SseFormat {
+ /// format message field
+ fn fmt_field(&self, _f: &mut Formatter, _key: &SseField) -> fmt::Result {
+ Ok(())
+ }
+ }
+
+ /// SSE wrapper to help formatting messages
+ #[allow(missing_debug_implementations)]
+ pub struct SseWrapper<'a, T: 'a>(&'a T);
+
+ impl<'a, T> SseWrapper<'a, T>
+ where
+ T: SseFormat + 'a,
+ {
+ pub fn format(event: &'a T) -> Result<String, SseError> {
+ let mut buf = String::new();
+ buf.write_fmt(format_args!("{}", SseWrapper(event)))
+ .map_err(|_| SseError)?;
+ buf.shrink_to_fit();
+ Ok(buf)
+ }
+ }
+
+ impl<'a, T> Display for SseWrapper<'a, T>
+ where
+ T: SseFormat,
+ {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ self.0.fmt_field(f, &SseField::Comment)?;
+ // The event name usually transferred before the other fields.
+ self.0.fmt_field(f, &SseField::Event)?;
+ // It is important that the data will be transferred before
+ // the identifier to prevent possible losing events when
+ // resuming connection.
+ self.0.fmt_field(f, &SseField::Data)?;
+ self.0.fmt_field(f, &SseField::Id)?;
+ self.0.fmt_field(f, &SseField::Retry)?;
+ f.write_char('\n')
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ pub struct BoxedServerSentEvent(pub(super) Box<dyn SseFormat + Send + Sync>);
+
+ impl SseFormat for BoxedServerSentEvent {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ self.0.fmt_field(f, k)
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ pub enum EitherServerSentEvent<A, B> {
+ A(A),
+ B(B),
+ }
+
+ impl<A, B> SseFormat for EitherServerSentEvent<A, B>
+ where
+ A: SseFormat,
+ B: SseFormat,
+ {
+ fn fmt_field(&self, f: &mut Formatter, k: &SseField) -> fmt::Result {
+ match self {
+ EitherServerSentEvent::A(a) => a.fmt_field(f, k),
+ EitherServerSentEvent::B(b) => b.fmt_field(f, k),
+ }
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filters/ws.rs b/third_party/rust/warp/src/filters/ws.rs
new file mode 100644
index 0000000000..2dd8c8d9d0
--- /dev/null
+++ b/third_party/rust/warp/src/filters/ws.rs
@@ -0,0 +1,365 @@
+//! Websockets Filters
+
+use std::borrow::Cow;
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use super::{body, header};
+use crate::filter::{Filter, One};
+use crate::reject::Rejection;
+use crate::reply::{Reply, Response};
+use futures::{future, ready, FutureExt, Sink, Stream, TryFutureExt};
+use headers::{Connection, HeaderMapExt, SecWebsocketAccept, SecWebsocketKey, Upgrade};
+use http;
+use tokio_tungstenite::{
+ tungstenite::protocol::{self, WebSocketConfig},
+ WebSocketStream,
+};
+
+/// Creates a Websocket Filter.
+///
+/// The yielded `Ws` is used to finish the websocket upgrade.
+///
+/// # Note
+///
+/// This filter combines multiple filters internally, so you don't need them:
+///
+/// - Method must be `GET`
+/// - Header `connection` must be `upgrade`
+/// - Header `upgrade` must be `websocket`
+/// - Header `sec-websocket-version` must be `13`
+/// - Header `sec-websocket-key` must be set.
+///
+/// If the filters are met, yields a `Ws`. Calling `Ws::on_upgrade` will
+/// return a reply with:
+///
+/// - Status of `101 Switching Protocols`
+/// - Header `connection: upgrade`
+/// - Header `upgrade: websocket`
+/// - Header `sec-websocket-accept` with the hash value of the received key.
+pub fn ws() -> impl Filter<Extract = One<Ws>, Error = Rejection> + Copy {
+ let connection_has_upgrade = header::header2()
+ .and_then(|conn: ::headers::Connection| {
+ if conn.contains("upgrade") {
+ future::ok(())
+ } else {
+ future::err(crate::reject::known(MissingConnectionUpgrade))
+ }
+ })
+ .untuple_one();
+
+ crate::get()
+ .and(connection_has_upgrade)
+ .and(header::exact_ignore_case("upgrade", "websocket"))
+ .and(header::exact("sec-websocket-version", "13"))
+ //.and(header::exact2(Upgrade::websocket()))
+ //.and(header::exact2(SecWebsocketVersion::V13))
+ .and(header::header2::<SecWebsocketKey>())
+ .and(body::body())
+ .map(move |key: SecWebsocketKey, body: ::hyper::Body| Ws {
+ body,
+ config: None,
+ key,
+ })
+}
+
+/// Extracted by the [`ws`](ws) filter, and used to finish an upgrade.
+pub struct Ws {
+ body: ::hyper::Body,
+ config: Option<WebSocketConfig>,
+ key: SecWebsocketKey,
+}
+
+impl Ws {
+ /// Finish the upgrade, passing a function to handle the `WebSocket`.
+ ///
+ /// The passed function must return a `Future`.
+ pub fn on_upgrade<F, U>(self, func: F) -> impl Reply
+ where
+ F: FnOnce(WebSocket) -> U + Send + 'static,
+ U: Future<Output = ()> + Send + 'static,
+ {
+ WsReply {
+ ws: self,
+ on_upgrade: func,
+ }
+ }
+
+ // config
+
+ /// Set the size of the internal message send queue.
+ pub fn max_send_queue(mut self, max: usize) -> Self {
+ self.config
+ .get_or_insert_with(WebSocketConfig::default)
+ .max_send_queue = Some(max);
+ self
+ }
+
+ /// Set the maximum message size (defaults to 64 megabytes)
+ pub fn max_message_size(mut self, max: usize) -> Self {
+ self.config
+ .get_or_insert_with(WebSocketConfig::default)
+ .max_message_size = Some(max);
+ self
+ }
+}
+
+impl fmt::Debug for Ws {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("Ws").finish()
+ }
+}
+
+#[allow(missing_debug_implementations)]
+struct WsReply<F> {
+ ws: Ws,
+ on_upgrade: F,
+}
+
+impl<F, U> Reply for WsReply<F>
+where
+ F: FnOnce(WebSocket) -> U + Send + 'static,
+ U: Future<Output = ()> + Send + 'static,
+{
+ fn into_response(self) -> Response {
+ let on_upgrade = self.on_upgrade;
+ let config = self.ws.config;
+ let fut = self
+ .ws
+ .body
+ .on_upgrade()
+ .and_then(move |upgraded| {
+ log::trace!("websocket upgrade complete");
+ WebSocket::from_raw_socket(upgraded, protocol::Role::Server, config).map(Ok)
+ })
+ .and_then(move |socket| on_upgrade(socket).map(Ok))
+ .map(|result| {
+ if let Err(err) = result {
+ log::debug!("ws upgrade error: {}", err);
+ }
+ });
+ ::tokio::task::spawn(fut);
+
+ let mut res = http::Response::default();
+
+ *res.status_mut() = http::StatusCode::SWITCHING_PROTOCOLS;
+
+ res.headers_mut().typed_insert(Connection::upgrade());
+ res.headers_mut().typed_insert(Upgrade::websocket());
+ res.headers_mut()
+ .typed_insert(SecWebsocketAccept::from(self.ws.key));
+
+ res
+ }
+}
+
+/// A websocket `Stream` and `Sink`, provided to `ws` filters.
+pub struct WebSocket {
+ inner: WebSocketStream<hyper::upgrade::Upgraded>,
+}
+
+impl WebSocket {
+ pub(crate) async fn from_raw_socket(
+ upgraded: hyper::upgrade::Upgraded,
+ role: protocol::Role,
+ config: Option<protocol::WebSocketConfig>,
+ ) -> Self {
+ WebSocketStream::from_raw_socket(upgraded, role, config)
+ .map(|inner| WebSocket { inner })
+ .await
+ }
+
+ /// Gracefully close this websocket.
+ pub async fn close(mut self) -> Result<(), crate::Error> {
+ future::poll_fn(|cx| Pin::new(&mut self).poll_close(cx)).await
+ }
+}
+
+impl Stream for WebSocket {
+ type Item = Result<Message, crate::Error>;
+
+ fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
+ match ready!(Pin::new(&mut self.inner).poll_next(cx)) {
+ Some(Ok(item)) => Poll::Ready(Some(Ok(Message { inner: item }))),
+ Some(Err(e)) => {
+ log::debug!("websocket poll error: {}", e);
+ Poll::Ready(Some(Err(crate::Error::new(e))))
+ }
+ None => {
+ log::trace!("websocket closed");
+ Poll::Ready(None)
+ }
+ }
+ }
+}
+
+impl Sink<Message> for WebSocket {
+ type Error = crate::Error;
+
+ fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
+ match ready!(Pin::new(&mut self.inner).poll_ready(cx)) {
+ Ok(()) => Poll::Ready(Ok(())),
+ Err(e) => Poll::Ready(Err(crate::Error::new(e))),
+ }
+ }
+
+ fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
+ match Pin::new(&mut self.inner).start_send(item.inner) {
+ Ok(()) => Ok(()),
+ Err(e) => {
+ log::debug!("websocket start_send error: {}", e);
+ Err(crate::Error::new(e))
+ }
+ }
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
+ match ready!(Pin::new(&mut self.inner).poll_flush(cx)) {
+ Ok(()) => Poll::Ready(Ok(())),
+ Err(e) => Poll::Ready(Err(crate::Error::new(e))),
+ }
+ }
+
+ fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Result<(), Self::Error>> {
+ match ready!(Pin::new(&mut self.inner).poll_close(cx)) {
+ Ok(()) => Poll::Ready(Ok(())),
+ Err(err) => {
+ log::debug!("websocket close error: {}", err);
+ Poll::Ready(Err(crate::Error::new(err)))
+ }
+ }
+ }
+}
+
+impl fmt::Debug for WebSocket {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("WebSocket").finish()
+ }
+}
+
+/// A WebSocket message.
+///
+/// Only repesents Text and Binary messages.
+///
+/// This will likely become a `non-exhaustive` enum in the future, once that
+/// language feature has stabilized.
+#[derive(Eq, PartialEq, Clone)]
+pub struct Message {
+ inner: protocol::Message,
+}
+
+impl Message {
+ /// Construct a new Text `Message`.
+ pub fn text<S: Into<String>>(s: S) -> Message {
+ Message {
+ inner: protocol::Message::text(s),
+ }
+ }
+
+ /// Construct a new Binary `Message`.
+ pub fn binary<V: Into<Vec<u8>>>(v: V) -> Message {
+ Message {
+ inner: protocol::Message::binary(v),
+ }
+ }
+
+ /// Construct a new Ping `Message`.
+ pub fn ping<V: Into<Vec<u8>>>(v: V) -> Message {
+ Message {
+ inner: protocol::Message::Ping(v.into()),
+ }
+ }
+
+ /// Construct the default Close `Message`.
+ pub fn close() -> Message {
+ Message {
+ inner: protocol::Message::Close(None),
+ }
+ }
+
+ /// Construct a Close `Message` with a code and reason.
+ pub fn close_with(code: impl Into<u16>, reason: impl Into<Cow<'static, str>>) -> Message {
+ Message {
+ inner: protocol::Message::Close(Some(protocol::frame::CloseFrame {
+ code: protocol::frame::coding::CloseCode::from(code.into()),
+ reason: reason.into(),
+ })),
+ }
+ }
+
+ /// Returns true if this message is a Text message.
+ pub fn is_text(&self) -> bool {
+ self.inner.is_text()
+ }
+
+ /// Returns true if this message is a Binary message.
+ pub fn is_binary(&self) -> bool {
+ self.inner.is_binary()
+ }
+
+ /// Returns true if this message a is a Close message.
+ pub fn is_close(&self) -> bool {
+ self.inner.is_close()
+ }
+
+ /// Returns true if this message is a Ping message.
+ pub fn is_ping(&self) -> bool {
+ self.inner.is_ping()
+ }
+
+ /// Returns true if this message is a Pong message.
+ pub fn is_pong(&self) -> bool {
+ self.inner.is_pong()
+ }
+
+ /// Try to get a reference to the string text, if this is a Text message.
+ pub fn to_str(&self) -> Result<&str, ()> {
+ match self.inner {
+ protocol::Message::Text(ref s) => Ok(s),
+ _ => Err(()),
+ }
+ }
+
+ /// Return the bytes of this message, if the message can contain data.
+ pub fn as_bytes(&self) -> &[u8] {
+ match self.inner {
+ protocol::Message::Text(ref s) => s.as_bytes(),
+ protocol::Message::Binary(ref v) => v,
+ protocol::Message::Ping(ref v) => v,
+ protocol::Message::Pong(ref v) => v,
+ protocol::Message::Close(_) => &[],
+ }
+ }
+
+ /// Destructure this message into binary data.
+ pub fn into_bytes(self) -> Vec<u8> {
+ self.inner.into_data()
+ }
+}
+
+impl fmt::Debug for Message {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ fmt::Debug::fmt(&self.inner, f)
+ }
+}
+
+impl Into<Vec<u8>> for Message {
+ fn into(self) -> Vec<u8> {
+ self.into_bytes()
+ }
+}
+
+// ===== Rejections =====
+
+#[derive(Debug)]
+pub(crate) struct MissingConnectionUpgrade;
+
+impl ::std::fmt::Display for MissingConnectionUpgrade {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Connection header did not include 'upgrade'")
+ }
+}
+
+impl ::std::error::Error for MissingConnectionUpgrade {}
diff --git a/third_party/rust/warp/src/generic.rs b/third_party/rust/warp/src/generic.rs
new file mode 100644
index 0000000000..1626a81409
--- /dev/null
+++ b/third_party/rust/warp/src/generic.rs
@@ -0,0 +1,237 @@
+#[derive(Debug)]
+pub struct Product<H, T: HList>(pub(crate) H, pub(crate) T);
+
+pub type One<T> = (T,);
+
+#[inline]
+pub(crate) fn one<T>(val: T) -> One<T> {
+ (val,)
+}
+
+#[derive(Debug)]
+pub enum Either<T, U> {
+ A(T),
+ B(U),
+}
+
+// Converts Product (and ()) into tuples.
+pub trait HList: Sized {
+ type Tuple: Tuple<HList = Self>;
+
+ fn flatten(self) -> Self::Tuple;
+}
+
+// Typeclass that tuples can be converted into a Product (or unit ()).
+pub trait Tuple: Sized {
+ type HList: HList<Tuple = Self>;
+
+ fn hlist(self) -> Self::HList;
+}
+
+// Combines Product together.
+pub trait Combine<T: HList> {
+ type Output: HList;
+
+ fn combine(self, other: T) -> Self::Output;
+}
+
+pub trait Func<Args> {
+ type Output;
+
+ fn call(&self, args: Args) -> Self::Output;
+}
+
+// ===== impl Combine =====
+
+impl<T: HList> Combine<T> for () {
+ type Output = T;
+ #[inline]
+ fn combine(self, other: T) -> Self::Output {
+ other
+ }
+}
+
+impl<H, T: HList, U: HList> Combine<U> for Product<H, T>
+where
+ T: Combine<U>,
+ Product<H, <T as Combine<U>>::Output>: HList,
+{
+ type Output = Product<H, <T as Combine<U>>::Output>;
+
+ #[inline]
+ fn combine(self, other: U) -> Self::Output {
+ Product(self.0, self.1.combine(other))
+ }
+}
+
+impl HList for () {
+ type Tuple = ();
+ #[inline]
+ fn flatten(self) -> Self::Tuple {}
+}
+
+impl Tuple for () {
+ type HList = ();
+
+ #[inline]
+ fn hlist(self) -> Self::HList {}
+}
+
+impl<F, R> Func<()> for F
+where
+ F: Fn() -> R,
+{
+ type Output = R;
+
+ #[inline]
+ fn call(&self, _args: ()) -> Self::Output {
+ (*self)()
+ }
+}
+
+impl<F, R> Func<crate::Rejection> for F
+where
+ F: Fn(crate::Rejection) -> R,
+{
+ type Output = R;
+
+ #[inline]
+ fn call(&self, arg: crate::Rejection) -> Self::Output {
+ (*self)(arg)
+ }
+}
+
+macro_rules! product {
+ ($H:expr) => { Product($H, ()) };
+ ($H:expr, $($T:expr),*) => { Product($H, product!($($T),*)) };
+}
+
+macro_rules! Product {
+ ($H:ty) => { Product<$H, ()> };
+ ($H:ty, $($T:ty),*) => { Product<$H, Product!($($T),*)> };
+}
+
+macro_rules! product_pat {
+ ($H:pat) => { Product($H, ()) };
+ ($H:pat, $($T:pat),*) => { Product($H, product_pat!($($T),*)) };
+}
+
+macro_rules! generics {
+ ($type:ident) => {
+ impl<$type> HList for Product!($type) {
+ type Tuple = ($type,);
+
+ #[inline]
+ fn flatten(self) -> Self::Tuple {
+ (self.0,)
+ }
+ }
+
+ impl<$type> Tuple for ($type,) {
+ type HList = Product!($type);
+ #[inline]
+ fn hlist(self) -> Self::HList {
+ product!(self.0)
+ }
+ }
+
+ impl<F, R, $type> Func<Product!($type)> for F
+ where
+ F: Fn($type) -> R,
+ {
+ type Output = R;
+
+ #[inline]
+ fn call(&self, args: Product!($type)) -> Self::Output {
+ (*self)(args.0)
+ }
+
+ }
+
+ impl<F, R, $type> Func<($type,)> for F
+ where
+ F: Fn($type) -> R,
+ {
+ type Output = R;
+
+ #[inline]
+ fn call(&self, args: ($type,)) -> Self::Output {
+ (*self)(args.0)
+ }
+ }
+
+ };
+
+ ($type1:ident, $( $type:ident ),*) => {
+ generics!($( $type ),*);
+
+ impl<$type1, $( $type ),*> HList for Product!($type1, $($type),*) {
+ type Tuple = ($type1, $( $type ),*);
+
+ #[inline]
+ fn flatten(self) -> Self::Tuple {
+ #[allow(non_snake_case)]
+ let product_pat!($type1, $( $type ),*) = self;
+ ($type1, $( $type ),*)
+ }
+ }
+
+ impl<$type1, $( $type ),*> Tuple for ($type1, $($type),*) {
+ type HList = Product!($type1, $( $type ),*);
+
+ #[inline]
+ fn hlist(self) -> Self::HList {
+ #[allow(non_snake_case)]
+ let ($type1, $( $type ),*) = self;
+ product!($type1, $( $type ),*)
+ }
+ }
+
+ impl<F, R, $type1, $( $type ),*> Func<Product!($type1, $($type),*)> for F
+ where
+ F: Fn($type1, $( $type ),*) -> R,
+ {
+ type Output = R;
+
+ #[inline]
+ fn call(&self, args: Product!($type1, $($type),*)) -> Self::Output {
+ #[allow(non_snake_case)]
+ let product_pat!($type1, $( $type ),*) = args;
+ (*self)($type1, $( $type ),*)
+ }
+ }
+
+ impl<F, R, $type1, $( $type ),*> Func<($type1, $($type),*)> for F
+ where
+ F: Fn($type1, $( $type ),*) -> R,
+ {
+ type Output = R;
+
+ #[inline]
+ fn call(&self, args: ($type1, $($type),*)) -> Self::Output {
+ #[allow(non_snake_case)]
+ let ($type1, $( $type ),*) = args;
+ (*self)($type1, $( $type ),*)
+ }
+ }
+ };
+}
+
+generics! {
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16
+}
diff --git a/third_party/rust/warp/src/lib.rs b/third_party/rust/warp/src/lib.rs
new file mode 100644
index 0000000000..241ce84432
--- /dev/null
+++ b/third_party/rust/warp/src/lib.rs
@@ -0,0 +1,170 @@
+#![doc(html_root_url = "https://docs.rs/warp/0.2.2")]
+#![deny(missing_docs)]
+#![deny(missing_debug_implementations)]
+#![cfg_attr(test, deny(warnings))]
+
+//! # warp
+//!
+//! warp is a super-easy, composable, web server framework for warp speeds.
+//!
+//! Thanks to its [`Filter`][Filter] system, warp provides these out of the box:
+//!
+//! - Path routing and parameter extraction
+//! - Header requirements and extraction
+//! - Query string deserialization
+//! - JSON and Form bodies
+//! - Multipart form data
+//! - Static Files and Directories
+//! - Websockets
+//! - Access logging
+//! - Etc
+//!
+//! Since it builds on top of [hyper](https://hyper.rs), you automatically get:
+//!
+//! - HTTP/1
+//! - HTTP/2
+//! - Asynchronous
+//! - One of the fastest HTTP implementations
+//! - Tested and **correct**
+//!
+//! ## Filters
+//!
+//! The main concept in warp is the [`Filter`][Filter], which allows composition
+//! to describe various endpoints in your web service. Besides this powerful
+//! trait, warp comes with several built in [filters](filters/index.html), which
+//! can be combined for your specific needs.
+//!
+//! As a small example, consider an endpoint that has path and header requirements:
+//!
+//! ```
+//! use warp::Filter;
+//!
+//! let hi = warp::path("hello")
+//! .and(warp::path::param())
+//! .and(warp::header("user-agent"))
+//! .map(|param: String, agent: String| {
+//! format!("Hello {}, whose agent is {}", param, agent)
+//! });
+//! ```
+//!
+//! This example composes several [`Filter`s][Filter] together using `and`:
+//!
+//! - A path prefix of "hello"
+//! - A path parameter of a `String`
+//! - The `user-agent` header parsed as a `String`
+//!
+//! These specific filters will [`reject`][reject] requests that don't match
+//! their requirements.
+//!
+//! This ends up matching requests like:
+//!
+//! ```notrust
+//! GET /hello/sean HTTP/1.1
+//! Host: hyper.rs
+//! User-Agent: reqwest/v0.8.6
+//!
+//! ```
+//! And it returns a response similar to this:
+//!
+//! ```notrust
+//! HTTP/1.1 200 OK
+//! Content-Length: 41
+//! Date: ...
+//!
+//! Hello sean, whose agent is reqwest/v0.8.6
+//! ```
+//!
+//! Take a look at the full list of [`filters`](filters/index.html) to see what
+//! you can build.
+//!
+//! ## Testing
+//!
+//! Testing your web services easily is extremely important, and warp provides
+//! a [`test`](test) module to help send mocked requests through your service.
+//!
+//! [Filter]: trait.Filter.html
+//! [reject]: reject/index.html
+
+#[macro_use]
+mod error;
+mod filter;
+pub mod filters;
+mod generic;
+pub mod redirect;
+pub mod reject;
+pub mod reply;
+mod route;
+mod server;
+mod service;
+pub mod test;
+#[cfg(feature = "tls")]
+mod tls;
+mod transport;
+
+pub use self::error::Error;
+pub use self::filter::Filter;
+// This otherwise shows a big dump of re-exports in the doc homepage,
+// with zero context, so just hide it from the docs. Doc examples
+// on each can show that a convenient import exists.
+#[cfg(feature = "multipart")]
+#[doc(hidden)]
+pub use self::filters::multipart;
+#[cfg(feature = "websocket")]
+#[doc(hidden)]
+pub use self::filters::ws;
+#[doc(hidden)]
+pub use self::filters::{
+ addr,
+ // any() function
+ any::any,
+ body,
+ cookie,
+ // cookie() function
+ cookie::cookie,
+ cors,
+ // cors() function
+ cors::cors,
+ ext,
+ fs,
+ header,
+ // header() function
+ header::header,
+ log,
+ // log() function
+ log::log,
+ method::{delete, get, head, method, options, patch, post, put},
+ path,
+ // path() function and macro
+ path::path,
+ query,
+ // query() function
+ query::query,
+ sse,
+};
+// ws() function
+#[cfg(feature = "websocket")]
+#[doc(hidden)]
+pub use self::filters::ws::ws;
+#[doc(hidden)]
+pub use self::redirect::redirect;
+#[doc(hidden)]
+#[allow(deprecated)]
+pub use self::reject::{reject, Rejection};
+#[doc(hidden)]
+pub use self::reply::{reply, Reply};
+#[cfg(feature = "tls")]
+pub use self::server::TlsServer;
+pub use self::server::{serve, Server};
+pub use self::service::service;
+#[doc(hidden)]
+pub use http;
+#[doc(hidden)]
+pub use hyper;
+
+#[doc(hidden)]
+pub use bytes::Buf;
+#[doc(hidden)]
+pub use futures::{Future, Sink, Stream};
+#[doc(hidden)]
+
+pub(crate) type Request = http::Request<hyper::Body>;
diff --git a/third_party/rust/warp/src/redirect.rs b/third_party/rust/warp/src/redirect.rs
new file mode 100644
index 0000000000..1459cef7dc
--- /dev/null
+++ b/third_party/rust/warp/src/redirect.rs
@@ -0,0 +1,70 @@
+//! Redirect requests to a new location.
+//!
+//! The types in this module are helpers that implement [`Reply`](Reply), and easy
+//! to use in order to setup redirects.
+
+use http::{header, StatusCode};
+
+use self::sealed::AsLocation;
+use crate::reply::{self, Reply};
+
+/// A simple `301` redirect to a different location.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+/// .map(|| {
+/// warp::redirect(Uri::from_static("/v2"))
+/// });
+/// ```
+pub fn redirect(uri: impl AsLocation) -> impl Reply {
+ reply::with_header(
+ StatusCode::MOVED_PERMANENTLY,
+ header::LOCATION,
+ uri.header_value(),
+ )
+}
+
+/// A simple `307` temporary redirect to a different location.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+/// .map(|| {
+/// warp::redirect::temporary(Uri::from_static("/v2"))
+/// });
+/// ```
+pub fn temporary(uri: impl AsLocation) -> impl Reply {
+ reply::with_header(
+ StatusCode::TEMPORARY_REDIRECT,
+ header::LOCATION,
+ uri.header_value(),
+ )
+}
+
+mod sealed {
+ use bytes::Bytes;
+ use http::{header::HeaderValue, Uri};
+
+ // These sealed traits are to allow adding possibly new impls so other
+ // arguments could be accepted, like maybe just `warp::redirect("/v2")`.
+ pub trait AsLocation: Sealed {}
+ pub trait Sealed {
+ fn header_value(self) -> HeaderValue;
+ }
+
+ impl AsLocation for Uri {}
+
+ impl Sealed for Uri {
+ fn header_value(self) -> HeaderValue {
+ let bytes = Bytes::from(self.to_string());
+ HeaderValue::from_maybe_shared(bytes).expect("Uri is a valid HeaderValue")
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/reject.rs b/third_party/rust/warp/src/reject.rs
new file mode 100644
index 0000000000..8fd28badda
--- /dev/null
+++ b/third_party/rust/warp/src/reject.rs
@@ -0,0 +1,810 @@
+//! Rejections
+//!
+//! Part of the power of the [`Filter`](../trait.Filter.html) system is being able to
+//! reject a request from a filter chain. This allows for filters to be
+//! combined with `or`, so that if one side of the chain finds that a request
+//! doesn't fulfill its requirements, the other side can try to process
+//! the request.
+//!
+//! Many of the built-in [`filters`](../filters) will automatically reject
+//! the request with an appropriate rejection. However, you can also build
+//! new custom [`Filter`](../trait.Filter.html)s and still want other routes to be
+//! matchable in the case a predicate doesn't hold.
+//!
+//! # Example
+//!
+//! ```
+//! use warp::Filter;
+//!
+//! // Filter on `/:id`, but reject with 404 if the `id` is `0`.
+//! let route = warp::path::param()
+//! .and_then(|id: u32| async move {
+//! if id == 0 {
+//! Err(warp::reject::not_found())
+//! } else {
+//! Ok("something since id is valid")
+//! }
+//! });
+//! ```
+
+use std::any::Any;
+use std::convert::Infallible;
+use std::error::Error as StdError;
+use std::fmt;
+
+use http::{
+ self,
+ header::{HeaderValue, CONTENT_TYPE},
+ StatusCode,
+};
+use hyper::Body;
+
+pub(crate) use self::sealed::{CombineRejection, IsReject};
+
+/// Rejects a request with `404 Not Found`.
+#[inline]
+pub fn reject() -> Rejection {
+ not_found()
+}
+
+/// Rejects a request with `404 Not Found`.
+#[inline]
+pub fn not_found() -> Rejection {
+ Rejection {
+ reason: Reason::NotFound,
+ }
+}
+
+// 400 Bad Request
+#[inline]
+pub(crate) fn invalid_query() -> Rejection {
+ known(InvalidQuery { _p: () })
+}
+
+// 400 Bad Request
+#[inline]
+pub(crate) fn missing_header(name: &'static str) -> Rejection {
+ known(MissingHeader { name })
+}
+
+// 400 Bad Request
+#[inline]
+pub(crate) fn invalid_header(name: &'static str) -> Rejection {
+ known(InvalidHeader { name })
+}
+
+// 400 Bad Request
+#[inline]
+pub(crate) fn missing_cookie(name: &'static str) -> Rejection {
+ known(MissingCookie { name })
+}
+
+// 405 Method Not Allowed
+#[inline]
+pub(crate) fn method_not_allowed() -> Rejection {
+ known(MethodNotAllowed { _p: () })
+}
+
+// 411 Length Required
+#[inline]
+pub(crate) fn length_required() -> Rejection {
+ known(LengthRequired { _p: () })
+}
+
+// 413 Payload Too Large
+#[inline]
+pub(crate) fn payload_too_large() -> Rejection {
+ known(PayloadTooLarge { _p: () })
+}
+
+// 415 Unsupported Media Type
+//
+// Used by the body filters if the request payload content-type doesn't match
+// what can be deserialized.
+#[inline]
+pub(crate) fn unsupported_media_type() -> Rejection {
+ known(UnsupportedMediaType { _p: () })
+}
+
+/// Rejects a request with a custom cause.
+///
+/// A [`recover`][] filter should convert this `Rejection` into a `Reply`,
+/// or else this will be returned as a `500 Internal Server Error`.
+///
+/// [`recover`]: ../trait.Filter.html#method.recover
+pub fn custom<T: Reject>(err: T) -> Rejection {
+ Rejection::custom(Box::new(err))
+}
+
+/// Protect against re-rejecting a rejection.
+///
+/// ```compile_fail
+/// fn with(r: warp::Rejection) {
+/// let _wat = warp::reject::custom(r);
+/// }
+/// ```
+fn __reject_custom_compilefail() {}
+
+/// A marker trait to ensure proper types are used for custom rejections.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Filter, reject::Reject};
+///
+/// #[derive(Debug)]
+/// struct RateLimited;
+///
+/// impl Reject for RateLimited {}
+///
+/// let route = warp::any().and_then(|| async {
+/// Err::<(), _>(warp::reject::custom(RateLimited))
+/// });
+/// ```
+// Require `Sized` for now to prevent passing a `Box<dyn Reject>`, since we
+// would be double-boxing it, and the downcasting wouldn't work as expected.
+pub trait Reject: fmt::Debug + Sized + Send + Sync + 'static {}
+
+trait Cause: fmt::Debug + Send + Sync + 'static {
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<T> Cause for T
+where
+ T: fmt::Debug + Send + Sync + 'static,
+{
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+impl dyn Cause {
+ fn downcast_ref<T: Any>(&self) -> Option<&T> {
+ self.as_any().downcast_ref::<T>()
+ }
+}
+
+pub(crate) fn known<T: Into<Known>>(err: T) -> Rejection {
+ Rejection::known(err.into())
+}
+
+/// Rejection of a request by a [`Filter`](crate::Filter).
+///
+/// See the [`reject`](module@crate::reject) documentation for more.
+pub struct Rejection {
+ reason: Reason,
+}
+
+enum Reason {
+ NotFound,
+ Other(Box<Rejections>),
+}
+
+enum Rejections {
+ Known(Known),
+ Custom(Box<dyn Cause>),
+ Combined(Box<Rejections>, Box<Rejections>),
+}
+
+macro_rules! enum_known {
+ ($($(#[$attr:meta])* $var:ident($ty:path),)+) => (
+ pub(crate) enum Known {
+ $(
+ $(#[$attr])*
+ $var($ty),
+ )+
+ }
+
+ impl Known {
+ fn inner_as_any(&self) -> &dyn Any {
+ match *self {
+ $(
+ $(#[$attr])*
+ Known::$var(ref t) => t,
+ )+
+ }
+ }
+ }
+
+ impl fmt::Debug for Known {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ $(
+ $(#[$attr])*
+ Known::$var(ref t) => t.fmt(f),
+ )+
+ }
+ }
+ }
+
+ impl fmt::Display for Known {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ $(
+ $(#[$attr])*
+ Known::$var(ref t) => t.fmt(f),
+ )+
+ }
+ }
+ }
+
+ $(
+ #[doc(hidden)]
+ $(#[$attr])*
+ impl From<$ty> for Known {
+ fn from(ty: $ty) -> Known {
+ Known::$var(ty)
+ }
+ }
+ )+
+ );
+}
+
+enum_known! {
+ MethodNotAllowed(MethodNotAllowed),
+ InvalidHeader(InvalidHeader),
+ MissingHeader(MissingHeader),
+ MissingCookie(MissingCookie),
+ InvalidQuery(InvalidQuery),
+ LengthRequired(LengthRequired),
+ PayloadTooLarge(PayloadTooLarge),
+ UnsupportedMediaType(UnsupportedMediaType),
+ FileOpenError(crate::fs::FileOpenError),
+ FilePermissionError(crate::fs::FilePermissionError),
+ BodyReadError(crate::body::BodyReadError),
+ BodyDeserializeError(crate::body::BodyDeserializeError),
+ CorsForbidden(crate::cors::CorsForbidden),
+ #[cfg(feature = "websocket")]
+ MissingConnectionUpgrade(crate::ws::MissingConnectionUpgrade),
+ MissingExtension(crate::ext::MissingExtension),
+ BodyConsumedMultipleTimes(crate::body::BodyConsumedMultipleTimes),
+}
+
+impl Rejection {
+ fn known(known: Known) -> Self {
+ Rejection {
+ reason: Reason::Other(Box::new(Rejections::Known(known))),
+ }
+ }
+
+ fn custom(other: Box<dyn Cause>) -> Self {
+ Rejection {
+ reason: Reason::Other(Box::new(Rejections::Custom(other))),
+ }
+ }
+
+ /// Searches this `Rejection` for a specific cause.
+ ///
+ /// A `Rejection` will accumulate causes over a `Filter` chain. This method
+ /// can search through them and return the first cause of this type.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// #[derive(Debug)]
+ /// struct Nope;
+ ///
+ /// impl warp::reject::Reject for Nope {}
+ ///
+ /// let reject = warp::reject::custom(Nope);
+ ///
+ /// if let Some(nope) = reject.find::<Nope>() {
+ /// println!("found it: {:?}", nope);
+ /// }
+ /// ```
+ pub fn find<T: 'static>(&self) -> Option<&T> {
+ if let Reason::Other(ref rejections) = self.reason {
+ return rejections.find();
+ }
+ None
+ }
+
+ /// Returns true if this Rejection was made via `warp::reject::not_found`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let rejection = warp::reject();
+ ///
+ /// assert!(rejection.is_not_found());
+ /// ```
+ pub fn is_not_found(&self) -> bool {
+ if let Reason::NotFound = self.reason {
+ true
+ } else {
+ false
+ }
+ }
+}
+
+impl From<Infallible> for Rejection {
+ #[inline]
+ fn from(infallible: Infallible) -> Rejection {
+ match infallible {}
+ }
+}
+
+impl IsReject for Infallible {
+ fn status(&self) -> StatusCode {
+ match *self {}
+ }
+
+ fn into_response(&self) -> crate::reply::Response {
+ match *self {}
+ }
+}
+
+impl IsReject for Rejection {
+ fn status(&self) -> StatusCode {
+ match self.reason {
+ Reason::NotFound => StatusCode::NOT_FOUND,
+ Reason::Other(ref other) => other.status(),
+ }
+ }
+
+ fn into_response(&self) -> crate::reply::Response {
+ match self.reason {
+ Reason::NotFound => {
+ let mut res = http::Response::default();
+ *res.status_mut() = StatusCode::NOT_FOUND;
+ res
+ }
+ Reason::Other(ref other) => other.into_response(),
+ }
+ }
+}
+
+impl fmt::Debug for Rejection {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_tuple("Rejection").field(&self.reason).finish()
+ }
+}
+
+impl fmt::Debug for Reason {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match *self {
+ Reason::NotFound => f.write_str("NotFound"),
+ Reason::Other(ref other) => match **other {
+ Rejections::Known(ref e) => fmt::Debug::fmt(e, f),
+ Rejections::Custom(ref e) => fmt::Debug::fmt(e, f),
+ Rejections::Combined(ref a, ref b) => {
+ let mut list = f.debug_list();
+ a.debug_list(&mut list);
+ b.debug_list(&mut list);
+ list.finish()
+ }
+ },
+ }
+ }
+}
+
+// ===== Rejections =====
+
+impl Rejections {
+ fn status(&self) -> StatusCode {
+ match *self {
+ Rejections::Known(ref k) => match *k {
+ Known::MethodNotAllowed(_) => StatusCode::METHOD_NOT_ALLOWED,
+ Known::InvalidHeader(_)
+ | Known::MissingHeader(_)
+ | Known::MissingCookie(_)
+ | Known::InvalidQuery(_)
+ | Known::BodyReadError(_)
+ | Known::BodyDeserializeError(_) => StatusCode::BAD_REQUEST,
+ #[cfg(feature = "websocket")]
+ Known::MissingConnectionUpgrade(_) => StatusCode::BAD_REQUEST,
+ Known::LengthRequired(_) => StatusCode::LENGTH_REQUIRED,
+ Known::PayloadTooLarge(_) => StatusCode::PAYLOAD_TOO_LARGE,
+ Known::UnsupportedMediaType(_) => StatusCode::UNSUPPORTED_MEDIA_TYPE,
+ Known::FilePermissionError(_) | Known::CorsForbidden(_) => StatusCode::FORBIDDEN,
+ Known::FileOpenError(_)
+ | Known::MissingExtension(_)
+ | Known::BodyConsumedMultipleTimes(_) => StatusCode::INTERNAL_SERVER_ERROR,
+ },
+ Rejections::Custom(..) => StatusCode::INTERNAL_SERVER_ERROR,
+ Rejections::Combined(ref a, ref b) => preferred(a, b).status(),
+ }
+ }
+
+ fn into_response(&self) -> crate::reply::Response {
+ match *self {
+ Rejections::Known(ref e) => {
+ let mut res = http::Response::new(Body::from(e.to_string()));
+ *res.status_mut() = self.status();
+ res.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("text/plain; charset=utf-8"),
+ );
+ res
+ }
+ Rejections::Custom(ref e) => {
+ log::error!(
+ "unhandled custom rejection, returning 500 response: {:?}",
+ e
+ );
+ let body = format!("Unhandled rejection: {:?}", e);
+ let mut res = http::Response::new(Body::from(body));
+ *res.status_mut() = self.status();
+ res.headers_mut().insert(
+ CONTENT_TYPE,
+ HeaderValue::from_static("text/plain; charset=utf-8"),
+ );
+ res
+ }
+ Rejections::Combined(ref a, ref b) => preferred(a, b).into_response(),
+ }
+ }
+
+ fn find<T: 'static>(&self) -> Option<&T> {
+ match *self {
+ Rejections::Known(ref e) => e.inner_as_any().downcast_ref(),
+ Rejections::Custom(ref e) => e.downcast_ref(),
+ Rejections::Combined(ref a, ref b) => a.find().or_else(|| b.find()),
+ }
+ }
+
+ fn debug_list(&self, f: &mut fmt::DebugList<'_, '_>) {
+ match *self {
+ Rejections::Known(ref e) => {
+ f.entry(e);
+ }
+ Rejections::Custom(ref e) => {
+ f.entry(e);
+ }
+ Rejections::Combined(ref a, ref b) => {
+ a.debug_list(f);
+ b.debug_list(f);
+ }
+ }
+ }
+}
+
+fn preferred<'a>(a: &'a Rejections, b: &'a Rejections) -> &'a Rejections {
+ // Compare status codes, with this priority:
+ // - NOT_FOUND is lowest
+ // - METHOD_NOT_ALLOWED is second
+ // - if one status code is greater than the other
+ // - otherwise, prefer A...
+ match (a.status(), b.status()) {
+ (_, StatusCode::NOT_FOUND) => a,
+ (StatusCode::NOT_FOUND, _) => b,
+ (_, StatusCode::METHOD_NOT_ALLOWED) => a,
+ (StatusCode::METHOD_NOT_ALLOWED, _) => b,
+ (sa, sb) if sa < sb => b,
+ _ => a,
+ }
+}
+
+unit_error! {
+ /// Invalid query
+ pub InvalidQuery: "Invalid query string"
+}
+
+unit_error! {
+ /// HTTP method not allowed
+ pub MethodNotAllowed: "HTTP method not allowed"
+}
+
+unit_error! {
+ /// A content-length header is required
+ pub LengthRequired: "A content-length header is required"
+}
+
+unit_error! {
+ /// The request payload is too large
+ pub PayloadTooLarge: "The request payload is too large"
+}
+
+unit_error! {
+ /// The request's content-type is not supported
+ pub UnsupportedMediaType: "The request's content-type is not supported"
+}
+
+/// Missing request header
+#[derive(Debug)]
+pub struct MissingHeader {
+ name: &'static str,
+}
+
+impl MissingHeader {
+ /// Retrieve the name of the header that was missing
+ pub fn name(&self) -> &str {
+ self.name
+ }
+}
+
+impl ::std::fmt::Display for MissingHeader {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Missing request header {:?}", self.name)
+ }
+}
+
+impl StdError for MissingHeader {}
+
+/// Invalid request header
+#[derive(Debug)]
+pub struct InvalidHeader {
+ name: &'static str,
+}
+
+impl InvalidHeader {
+ /// Retrieve the name of the header that was invalid
+ pub fn name(&self) -> &str {
+ self.name
+ }
+}
+
+impl ::std::fmt::Display for InvalidHeader {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Invalid request header {:?}", self.name)
+ }
+}
+
+impl StdError for InvalidHeader {}
+
+/// Missing cookie
+#[derive(Debug)]
+pub struct MissingCookie {
+ name: &'static str,
+}
+
+impl MissingCookie {
+ /// Retrieve the name of the cookie that was missing
+ pub fn name(&self) -> &str {
+ self.name
+ }
+}
+
+impl ::std::fmt::Display for MissingCookie {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ write!(f, "Missing request cookie {:?}", self.name)
+ }
+}
+
+impl StdError for MissingCookie {}
+
+mod sealed {
+ use super::{Reason, Rejection, Rejections};
+ use http::StatusCode;
+ use std::convert::Infallible;
+ use std::fmt;
+
+ // This sealed trait exists to allow Filters to return either `Rejection`
+ // or `!`. There are no other types that make sense, and so it is sealed.
+ pub trait IsReject: fmt::Debug + Send + Sync {
+ fn status(&self) -> StatusCode;
+ fn into_response(&self) -> crate::reply::Response;
+ }
+
+ fn _assert_object_safe() {
+ fn _assert(_: &dyn IsReject) {}
+ }
+
+ // This weird trait is to allow optimizations of propagating when a
+ // rejection can *never* happen (currently with the `Never` type,
+ // eventually to be replaced with `!`).
+ //
+ // Using this trait means the `Never` gets propagated to chained filters,
+ // allowing LLVM to eliminate more code paths. Without it, such as just
+ // requiring that `Rejection::from(Never)` were used in those filters,
+ // would mean that links later in the chain may assume a rejection *could*
+ // happen, and no longer eliminate those branches.
+ pub trait CombineRejection<E>: Send + Sized {
+ /// The type that should be returned when only 1 of the two
+ /// "rejections" occurs.
+ ///
+ /// # For example:
+ ///
+ /// `warp::any().and(warp::path("foo"))` has the following steps:
+ ///
+ /// 1. Since this is `and`, only **one** of the rejections will occur,
+ /// and as soon as it does, it will be returned.
+ /// 2. `warp::any()` rejects with `Never`. So, it will never return `Never`.
+ /// 3. `warp::path()` rejects with `Rejection`. It may return `Rejection`.
+ ///
+ /// Thus, if the above filter rejects, it will definitely be `Rejection`.
+ type One: IsReject + From<Self> + From<E> + Into<Rejection>;
+
+ /// The type that should be returned when both rejections occur,
+ /// and need to be combined.
+ type Combined: IsReject;
+
+ fn combine(self, other: E) -> Self::Combined;
+ }
+
+ impl CombineRejection<Rejection> for Rejection {
+ type One = Rejection;
+ type Combined = Rejection;
+
+ fn combine(self, other: Rejection) -> Self::Combined {
+ let reason = match (self.reason, other.reason) {
+ (Reason::Other(left), Reason::Other(right)) => {
+ Reason::Other(Box::new(Rejections::Combined(left, right)))
+ }
+ (Reason::Other(other), Reason::NotFound)
+ | (Reason::NotFound, Reason::Other(other)) => {
+ // ignore the NotFound
+ Reason::Other(other)
+ }
+ (Reason::NotFound, Reason::NotFound) => Reason::NotFound,
+ };
+
+ Rejection { reason }
+ }
+ }
+
+ impl CombineRejection<Infallible> for Rejection {
+ type One = Rejection;
+ type Combined = Infallible;
+
+ fn combine(self, other: Infallible) -> Self::Combined {
+ match other {}
+ }
+ }
+
+ impl CombineRejection<Rejection> for Infallible {
+ type One = Rejection;
+ type Combined = Infallible;
+
+ fn combine(self, _: Rejection) -> Self::Combined {
+ match self {}
+ }
+ }
+
+ impl CombineRejection<Infallible> for Infallible {
+ type One = Infallible;
+ type Combined = Infallible;
+
+ fn combine(self, _: Infallible) -> Self::Combined {
+ match self {}
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use http::StatusCode;
+
+ #[derive(Debug, PartialEq)]
+ struct Left;
+
+ #[derive(Debug, PartialEq)]
+ struct Right;
+
+ impl Reject for Left {}
+ impl Reject for Right {}
+
+ #[test]
+ fn rejection_status() {
+ assert_eq!(not_found().status(), StatusCode::NOT_FOUND);
+ assert_eq!(
+ method_not_allowed().status(),
+ StatusCode::METHOD_NOT_ALLOWED
+ );
+ assert_eq!(length_required().status(), StatusCode::LENGTH_REQUIRED);
+ assert_eq!(payload_too_large().status(), StatusCode::PAYLOAD_TOO_LARGE);
+ assert_eq!(
+ unsupported_media_type().status(),
+ StatusCode::UNSUPPORTED_MEDIA_TYPE
+ );
+ assert_eq!(custom(Left).status(), StatusCode::INTERNAL_SERVER_ERROR);
+ }
+
+ #[tokio::test]
+ async fn combine_rejection_causes_with_some_left_and_none_right() {
+ let left = custom(Left);
+ let right = not_found();
+ let reject = left.combine(right);
+ let resp = reject.into_response();
+
+ assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
+ assert_eq!(
+ response_body_string(resp).await,
+ "Unhandled rejection: Left"
+ )
+ }
+
+ #[tokio::test]
+ async fn combine_rejection_causes_with_none_left_and_some_right() {
+ let left = not_found();
+ let right = custom(Right);
+ let reject = left.combine(right);
+ let resp = reject.into_response();
+
+ assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
+ assert_eq!(
+ response_body_string(resp).await,
+ "Unhandled rejection: Right"
+ )
+ }
+
+ #[tokio::test]
+ async fn unhandled_customs() {
+ let reject = not_found().combine(custom(Right));
+
+ let resp = reject.into_response();
+ assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
+ assert_eq!(
+ response_body_string(resp).await,
+ "Unhandled rejection: Right"
+ );
+
+ // There's no real way to determine which is worse, since both are a 500,
+ // so pick the first one.
+ let reject = custom(Left).combine(custom(Right));
+
+ let resp = reject.into_response();
+ assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
+ assert_eq!(
+ response_body_string(resp).await,
+ "Unhandled rejection: Left"
+ );
+
+ // With many rejections, custom still is top priority.
+ let reject = not_found()
+ .combine(not_found())
+ .combine(not_found())
+ .combine(custom(Right))
+ .combine(not_found());
+
+ let resp = reject.into_response();
+ assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
+ assert_eq!(
+ response_body_string(resp).await,
+ "Unhandled rejection: Right"
+ );
+ }
+
+ async fn response_body_string(resp: crate::reply::Response) -> String {
+ let (_, body) = resp.into_parts();
+ let body_bytes = hyper::body::to_bytes(body).await.expect("failed concat");
+ String::from_utf8_lossy(&body_bytes).to_string()
+ }
+
+ #[test]
+ fn find_cause() {
+ let rej = custom(Left);
+
+ assert_eq!(rej.find::<Left>(), Some(&Left));
+
+ let rej = rej.combine(method_not_allowed());
+
+ assert_eq!(rej.find::<Left>(), Some(&Left));
+ assert!(rej.find::<MethodNotAllowed>().is_some(), "MethodNotAllowed");
+ }
+
+ #[test]
+ fn size_of_rejection() {
+ assert_eq!(
+ ::std::mem::size_of::<Rejection>(),
+ ::std::mem::size_of::<usize>(),
+ );
+ }
+
+ #[derive(Debug)]
+ struct X(u32);
+ impl Reject for X {}
+
+ fn combine_n<F, R>(n: u32, new_reject: F) -> Rejection
+ where
+ F: Fn(u32) -> R,
+ R: Reject,
+ {
+ let mut rej = not_found();
+
+ for i in 0..n {
+ rej = rej.combine(custom(new_reject(i)));
+ }
+
+ rej
+ }
+
+ #[test]
+ fn test_debug() {
+ let rej = combine_n(3, X);
+
+ let s = format!("{:?}", rej);
+ assert_eq!(s, "Rejection([X(0), X(1), X(2)])");
+ }
+}
diff --git a/third_party/rust/warp/src/reply.rs b/third_party/rust/warp/src/reply.rs
new file mode 100644
index 0000000000..657e6e8220
--- /dev/null
+++ b/third_party/rust/warp/src/reply.rs
@@ -0,0 +1,581 @@
+//! 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<impl Into<hyper::Body>`](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<Body>;
+
+/// 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<T>(val: &T) -> Json
+where
+ T: Serialize,
+{
+ Json {
+ inner: serde_json::to_vec(val).map_err(|err| {
+ log::error!("reply::json error: {}", err);
+ }),
+ }
+}
+
+/// A JSON formatted reply.
+#[allow(missing_debug_implementations)]
+pub struct Json {
+ inner: Result<Vec<u8>, ()>,
+}
+
+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>
+/// <head>
+/// <title>HTML with warp!</title>
+/// </head>
+/// <body>
+/// <h1>warp + HTML = :heart:</h1>
+/// </body>
+/// </html>
+/// "#;
+///
+/// let route = warp::any()
+/// .map(|| {
+/// warp::reply::html(body)
+/// });
+/// ```
+pub fn html<T>(body: T) -> impl Reply
+where
+ Body: From<T>,
+ T: Send,
+{
+ Html { body }
+}
+
+#[allow(missing_debug_implementations)]
+struct Html<T> {
+ body: T,
+}
+
+impl<T> Reply for Html<T>
+where
+ Body: From<T>,
+ 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<impl Into<hyper::Body>>`
+/// - `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<Reply>`.
+
+ A possible alternative is to have wrappers, like
+
+ - `WithStatus<R: Reply>(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<K, V>(self, name: K, value: V) -> Reply_
+ where
+ Self: Sized,
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ {
+ match <HeaderName as TryFrom<K>>::try_from(name) {
+ Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(value) => {
+ let mut res = self.into_response();
+ res.headers_mut().append(name, value);
+ Reply_(res)
+ },
+ Err(err) => {
+ log::error!("with_header value error: {}", err.into());
+ Reply_(::reject::server_error()
+ .into_response())
+ }
+ },
+ Err(err) => {
+ log::error!("with_header name error: {}", err.into());
+ Reply_(::reject::server_error()
+ .into_response())
+ }
+ }
+ }
+ */
+}
+
+impl<T: Reply + ?Sized> Reply for Box<T> {
+ 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<T: Reply>(reply: T, status: StatusCode) -> WithStatus<T> {
+ WithStatus { reply, status }
+}
+
+/// Wrap an `impl Reply` to change its `StatusCode`.
+///
+/// Returned by `warp::reply::with_status`.
+#[derive(Debug)]
+pub struct WithStatus<T> {
+ reply: T,
+ status: StatusCode,
+}
+
+impl<T: Reply> Reply for WithStatus<T> {
+ 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<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>,
+{
+ let header = match <HeaderName as TryFrom<K>>::try_from(name) {
+ Ok(name) => match <HeaderValue as TryFrom<V>>::try_from(value) {
+ Ok(value) => Some((name, value)),
+ Err(err) => {
+ log::error!("with_header value error: {}", err.into());
+ None
+ }
+ },
+ Err(err) => {
+ log::error!("with_header name error: {}", err.into());
+ 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<T> {
+ header: Option<(HeaderName, HeaderValue)>,
+ reply: T,
+}
+
+impl<T: Reply> Reply for WithHeader<T> {
+ 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<T: Send> Reply for ::http::Response<T>
+where
+ Body: From<T>,
+{
+ #[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<T> Reply for Result<T, ::http::Error>
+where
+ T: Reply + Send,
+{
+ #[inline]
+ fn into_response(self) -> Response {
+ match self {
+ Ok(t) => t.into_response(),
+ Err(e) => {
+ log::error!("reply error: {:?}", e);
+ StatusCode::INTERNAL_SERVER_ERROR.into_response()
+ }
+ }
+ }
+}
+
+fn text_plain<T: Into<Body>>(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<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 &'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<T, U> Reply for Either<T, U>
+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<T> Reply for One<T>
+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<dyn Reply>` working.
+ pub trait BoxedReply {
+ fn boxed_into_response(self: Box<Self>, internal: Internal) -> Response;
+ }
+
+ impl<T: Reply> BoxedReply for T {
+ fn boxed_into_response(self: Box<Self>, _: Internal) -> Response {
+ (*self).into_response()
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::collections::HashMap;
+
+ use super::*;
+
+ #[test]
+ fn json_serde_error() {
+ // a HashMap<Vec, _> 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<dyn Reply> = Box::new(reply());
+ let resp = r.into_response();
+ assert_eq!(resp.status(), 200);
+ }
+}
diff --git a/third_party/rust/warp/src/route.rs b/third_party/rust/warp/src/route.rs
new file mode 100644
index 0000000000..8eed7d79ff
--- /dev/null
+++ b/third_party/rust/warp/src/route.rs
@@ -0,0 +1,142 @@
+use scoped_tls::scoped_thread_local;
+use std::cell::RefCell;
+use std::mem;
+use std::net::SocketAddr;
+
+use http;
+use hyper::Body;
+
+use crate::Request;
+
+scoped_thread_local!(static ROUTE: RefCell<Route>);
+
+pub(crate) fn set<F, U>(r: &RefCell<Route>, func: F) -> U
+where
+ F: FnOnce() -> U,
+{
+ ROUTE.set(r, func)
+}
+
+pub(crate) fn is_set() -> bool {
+ ROUTE.is_set()
+}
+
+pub(crate) fn with<F, R>(func: F) -> R
+where
+ F: FnOnce(&mut Route) -> R,
+{
+ ROUTE.with(move |route| func(&mut *route.borrow_mut()))
+}
+
+#[derive(Debug)]
+pub(crate) struct Route {
+ body: BodyState,
+ remote_addr: Option<SocketAddr>,
+ req: Request,
+ segments_index: usize,
+}
+
+#[derive(Debug)]
+enum BodyState {
+ Ready,
+ Taken,
+}
+
+impl Route {
+ pub(crate) fn new(req: Request, remote_addr: Option<SocketAddr>) -> RefCell<Route> {
+ let segments_index = if req.uri().path().starts_with('/') {
+ // Skip the beginning slash.
+ 1
+ } else {
+ 0
+ };
+
+ RefCell::new(Route {
+ body: BodyState::Ready,
+ remote_addr,
+ req,
+ segments_index,
+ })
+ }
+
+ pub(crate) fn method(&self) -> &http::Method {
+ self.req.method()
+ }
+
+ pub(crate) fn headers(&self) -> &http::HeaderMap {
+ self.req.headers()
+ }
+
+ pub(crate) fn version(&self) -> http::Version {
+ self.req.version()
+ }
+
+ pub(crate) fn extensions(&self) -> &http::Extensions {
+ self.req.extensions()
+ }
+
+ /*
+ pub(crate) fn extensions_mut(&mut self) -> &mut http::Extensions {
+ self.req.extensions_mut()
+ }
+ */
+
+ pub(crate) fn uri(&self) -> &http::Uri {
+ self.req.uri()
+ }
+
+ pub(crate) fn path(&self) -> &str {
+ &self.req.uri().path()[self.segments_index..]
+ }
+
+ pub(crate) fn full_path(&self) -> &str {
+ self.req.uri().path()
+ }
+
+ pub(crate) fn set_unmatched_path(&mut self, index: usize) {
+ let index = self.segments_index + index;
+
+ let path = self.req.uri().path();
+
+ if path.len() == index {
+ self.segments_index = index;
+ } else {
+ debug_assert_eq!(path.as_bytes()[index], b'/');
+
+ self.segments_index = index + 1;
+ }
+ }
+
+ pub(crate) fn query(&self) -> Option<&str> {
+ self.req.uri().query()
+ }
+
+ pub(crate) fn matched_path_index(&self) -> usize {
+ self.segments_index
+ }
+
+ pub(crate) fn reset_matched_path_index(&mut self, index: usize) {
+ debug_assert!(
+ index <= self.segments_index,
+ "reset_match_path_index should not be bigger: current={}, arg={}",
+ self.segments_index,
+ index,
+ );
+ self.segments_index = index;
+ }
+
+ pub(crate) fn remote_addr(&self) -> Option<SocketAddr> {
+ self.remote_addr
+ }
+
+ pub(crate) fn take_body(&mut self) -> Option<Body> {
+ match self.body {
+ BodyState::Ready => {
+ let body = mem::replace(self.req.body_mut(), Body::empty());
+ self.body = BodyState::Taken;
+ Some(body)
+ }
+ BodyState::Taken => None,
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/server.rs b/third_party/rust/warp/src/server.rs
new file mode 100644
index 0000000000..3736fca969
--- /dev/null
+++ b/third_party/rust/warp/src/server.rs
@@ -0,0 +1,458 @@
+#[cfg(feature = "tls")]
+use crate::tls::TlsConfigBuilder;
+use std::convert::Infallible;
+use std::error::Error as StdError;
+use std::future::Future;
+use std::net::SocketAddr;
+#[cfg(feature = "tls")]
+use std::path::Path;
+
+use futures::{future, FutureExt, TryFuture, TryStream, TryStreamExt};
+use hyper::server::conn::AddrIncoming;
+use hyper::service::{make_service_fn, service_fn};
+use hyper::Server as HyperServer;
+use tokio::io::{AsyncRead, AsyncWrite};
+
+use crate::filter::Filter;
+use crate::reject::IsReject;
+use crate::reply::Reply;
+use crate::transport::Transport;
+
+/// Create a `Server` with the provided `Filter`.
+pub fn serve<F>(filter: F) -> Server<F>
+where
+ F: Filter + Clone + Send + Sync + 'static,
+ F::Extract: Reply,
+ F::Error: IsReject,
+{
+ Server {
+ pipeline: false,
+ filter,
+ }
+}
+
+/// A Warp Server ready to filter requests.
+#[derive(Debug)]
+pub struct Server<F> {
+ pipeline: bool,
+ filter: F,
+}
+
+/// A Warp Server ready to filter requests over TLS.
+///
+/// *This type requires the `"tls"` feature.*
+#[cfg(feature = "tls")]
+pub struct TlsServer<F> {
+ server: Server<F>,
+ tls: TlsConfigBuilder,
+}
+
+// Getting all various generic bounds to make this a re-usable method is
+// very complicated, so instead this is just a macro.
+macro_rules! into_service {
+ ($into:expr) => {{
+ let inner = crate::service($into);
+ make_service_fn(move |transport| {
+ let inner = inner.clone();
+ let remote_addr = Transport::remote_addr(transport);
+ future::ok::<_, Infallible>(service_fn(move |req| {
+ inner.call_with_addr(req, remote_addr)
+ }))
+ })
+ }};
+}
+
+macro_rules! addr_incoming {
+ ($addr:expr) => {{
+ let mut incoming = AddrIncoming::bind($addr)?;
+ incoming.set_nodelay(true);
+ let addr = incoming.local_addr();
+ (addr, incoming)
+ }};
+}
+
+macro_rules! bind_inner {
+ ($this:ident, $addr:expr) => {{
+ let service = into_service!($this.filter);
+ let (addr, incoming) = addr_incoming!($addr);
+ let srv = HyperServer::builder(incoming)
+ .http1_pipeline_flush($this.pipeline)
+ .serve(service);
+ Ok::<_, hyper::Error>((addr, srv))
+ }};
+
+ (tls: $this:ident, $addr:expr) => {{
+ let service = into_service!($this.server.filter);
+ let (addr, incoming) = addr_incoming!($addr);
+ let tls = $this.tls.build()?;
+ let srv = HyperServer::builder(crate::tls::TlsAcceptor::new(tls, incoming))
+ .http1_pipeline_flush($this.server.pipeline)
+ .serve(service);
+ Ok::<_, Box<dyn std::error::Error + Send + Sync>>((addr, srv))
+ }};
+}
+
+macro_rules! bind {
+ ($this:ident, $addr:expr) => {{
+ let addr = $addr.into();
+ (|addr| bind_inner!($this, addr))(&addr).unwrap_or_else(|e| {
+ panic!("error binding to {}: {}", addr, e);
+ })
+ }};
+
+ (tls: $this:ident, $addr:expr) => {{
+ let addr = $addr.into();
+ (|addr| bind_inner!(tls: $this, addr))(&addr).unwrap_or_else(|e| {
+ panic!("error binding to {}: {}", addr, e);
+ })
+ }};
+}
+
+macro_rules! try_bind {
+ ($this:ident, $addr:expr) => {{
+ (|addr| bind_inner!($this, addr))($addr)
+ }};
+
+ (tls: $this:ident, $addr:expr) => {{
+ (|addr| bind_inner!(tls: $this, addr))($addr)
+ }};
+}
+
+// ===== impl Server =====
+
+impl<F> Server<F>
+where
+ F: Filter + Clone + Send + Sync + 'static,
+ <F::Future as TryFuture>::Ok: Reply,
+ <F::Future as TryFuture>::Error: IsReject,
+{
+ /// Run this `Server` forever on the current thread.
+ pub async fn run(self, addr: impl Into<SocketAddr> + 'static) {
+ let (addr, fut) = self.bind_ephemeral(addr);
+
+ log::info!("listening on http://{}", addr);
+
+ fut.await;
+ }
+
+ /// Run this `Server` forever on the current thread with a specific stream
+ /// of incoming connections.
+ ///
+ /// This can be used for Unix Domain Sockets, or TLS, etc.
+ pub async fn run_incoming<I>(self, incoming: I)
+ where
+ I: TryStream + Send,
+ I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
+ I::Error: Into<Box<dyn StdError + Send + Sync>>,
+ {
+ self.run_incoming2(incoming.map_ok(crate::transport::LiftIo).into_stream())
+ .await;
+ }
+
+ async fn run_incoming2<I>(self, incoming: I)
+ where
+ I: TryStream + Send,
+ I::Ok: Transport + Send + 'static + Unpin,
+ I::Error: Into<Box<dyn StdError + Send + Sync>>,
+ {
+ let fut = self.serve_incoming2(incoming);
+
+ log::info!("listening with custom incoming");
+
+ fut.await;
+ }
+
+ /// Bind to a socket address, returning a `Future` that can be
+ /// executed on any runtime.
+ ///
+ /// # Panics
+ ///
+ /// Panics if we are unable to bind to the provided address.
+ pub fn bind(self, addr: impl Into<SocketAddr> + 'static) -> impl Future<Output = ()> + 'static {
+ let (_, fut) = self.bind_ephemeral(addr);
+ fut
+ }
+
+ /// Bind to a socket address, returning a `Future` that can be
+ /// executed on any runtime.
+ ///
+ /// In case we are unable to bind to the specified address, resolves to an
+ /// error and logs the reason.
+ pub async fn try_bind(self, addr: impl Into<SocketAddr> + 'static) {
+ let addr = addr.into();
+ let srv = match try_bind!(self, &addr) {
+ Ok((_, srv)) => srv,
+ Err(err) => {
+ log::error!("error binding to {}: {}", addr, err);
+ return;
+ }
+ };
+
+ srv.map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ })
+ .await;
+ }
+
+ /// Bind to a possibly ephemeral socket address.
+ ///
+ /// Returns the bound address and a `Future` that can be executed on
+ /// any runtime.
+ ///
+ /// # Panics
+ ///
+ /// Panics if we are unable to bind to the provided address.
+ pub fn bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(self, addr);
+ let srv = srv.map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ });
+
+ (addr, srv)
+ }
+
+ /// Tried to bind a possibly ephemeral socket address.
+ ///
+ /// Returns a `Result` which fails in case we are unable to bind with the
+ /// underlying error.
+ ///
+ /// Returns the bound address and a `Future` that can be executed on
+ /// any runtime.
+ pub fn try_bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ ) -> Result<(SocketAddr, impl Future<Output = ()> + 'static), crate::Error> {
+ let addr = addr.into();
+ let (addr, srv) = try_bind!(self, &addr).map_err(crate::Error::new)?;
+ let srv = srv.map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ });
+
+ Ok((addr, srv))
+ }
+
+ /// Create a server with graceful shutdown signal.
+ ///
+ /// When the signal completes, the server will start the graceful shutdown
+ /// process.
+ ///
+ /// Returns the bound address and a `Future` that can be executed on
+ /// any runtime.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use warp::Filter;
+ /// use futures::future::TryFutureExt;
+ /// use tokio::sync::oneshot;
+ ///
+ /// # fn main() {
+ /// let routes = warp::any()
+ /// .map(|| "Hello, World!");
+ ///
+ /// let (tx, rx) = oneshot::channel();
+ ///
+ /// let (addr, server) = warp::serve(routes)
+ /// .bind_with_graceful_shutdown(([127, 0, 0, 1], 3030), async {
+ /// rx.await.ok();
+ /// });
+ ///
+ /// // Spawn the server into a runtime
+ /// tokio::task::spawn(server);
+ ///
+ /// // Later, start the shutdown...
+ /// let _ = tx.send(());
+ /// # }
+ /// ```
+ pub fn bind_with_graceful_shutdown(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ signal: impl Future<Output = ()> + Send + 'static,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(self, addr);
+ let fut = srv.with_graceful_shutdown(signal).map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ });
+ (addr, fut)
+ }
+
+ /// Setup this `Server` with a specific stream of incoming connections.
+ ///
+ /// This can be used for Unix Domain Sockets, or TLS, etc.
+ ///
+ /// Returns a `Future` that can be executed on any runtime.
+ pub fn serve_incoming<I>(self, incoming: I) -> impl Future<Output = ()> + 'static
+ where
+ I: TryStream + Send + 'static,
+ I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
+ I::Error: Into<Box<dyn StdError + Send + Sync>>,
+ {
+ let incoming = incoming.map_ok(crate::transport::LiftIo);
+ self.serve_incoming2(incoming)
+ }
+
+ async fn serve_incoming2<I>(self, incoming: I)
+ where
+ I: TryStream + Send,
+ I::Ok: Transport + Send + 'static + Unpin,
+ I::Error: Into<Box<dyn StdError + Send + Sync>>,
+ {
+ let service = into_service!(self.filter);
+
+ let srv = HyperServer::builder(hyper::server::accept::from_stream(incoming.into_stream()))
+ .http1_pipeline_flush(self.pipeline)
+ .serve(service)
+ .await;
+
+ if let Err(err) = srv {
+ log::error!("server error: {}", err);
+ }
+ }
+
+ // Generally shouldn't be used, as it can slow down non-pipelined responses.
+ //
+ // It's only real use is to make silly pipeline benchmarks look better.
+ #[doc(hidden)]
+ pub fn unstable_pipeline(mut self) -> Self {
+ self.pipeline = true;
+ self
+ }
+
+ /// Configure a server to use TLS.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ #[cfg(feature = "tls")]
+ pub fn tls(self) -> TlsServer<F> {
+ TlsServer {
+ server: self,
+ tls: TlsConfigBuilder::new(),
+ }
+ }
+}
+
+// // ===== impl TlsServer =====
+
+#[cfg(feature = "tls")]
+impl<F> TlsServer<F>
+where
+ F: Filter + Clone + Send + Sync + 'static,
+ <F::Future as TryFuture>::Ok: Reply,
+ <F::Future as TryFuture>::Error: IsReject,
+{
+ // TLS config methods
+
+ /// Specify the file path to read the private key.
+ pub fn key_path(self, path: impl AsRef<Path>) -> Self {
+ self.with_tls(|tls| tls.key_path(path))
+ }
+
+ /// Specify the file path to read the certificate.
+ pub fn cert_path(self, path: impl AsRef<Path>) -> Self {
+ self.with_tls(|tls| tls.cert_path(path))
+ }
+
+ /// Specify the in-memory contents of the private key.
+ pub fn key(self, key: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.key(key.as_ref()))
+ }
+
+ /// Specify the in-memory contents of the certificate.
+ pub fn cert(self, cert: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.cert(cert.as_ref()))
+ }
+
+ fn with_tls<Func>(self, func: Func) -> Self
+ where
+ Func: FnOnce(TlsConfigBuilder) -> TlsConfigBuilder,
+ {
+ let TlsServer { server, tls } = self;
+ let tls = func(tls);
+ TlsServer { server, tls }
+ }
+
+ // Server run methods
+
+ /// Run this `TlsServer` forever on the current thread.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub async fn run(self, addr: impl Into<SocketAddr> + 'static) {
+ let (addr, fut) = self.bind_ephemeral(addr);
+
+ log::info!("listening on https://{}", addr);
+
+ fut.await;
+ }
+
+ /// Bind to a socket address, returning a `Future` that can be
+ /// executed on a runtime.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub async fn bind(self, addr: impl Into<SocketAddr> + 'static) {
+ let (_, fut) = self.bind_ephemeral(addr);
+ fut.await;
+ }
+
+ /// Bind to a possibly ephemeral socket address.
+ ///
+ /// Returns the bound address and a `Future` that can be executed on
+ /// any runtime.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(tls: self, addr);
+ let srv = srv.map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ });
+
+ (addr, srv)
+ }
+
+ /// Create a server with graceful shutdown signal.
+ ///
+ /// When the signal completes, the server will start the graceful shutdown
+ /// process.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn bind_with_graceful_shutdown(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ signal: impl Future<Output = ()> + Send + 'static,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(tls: self, addr);
+
+ let fut = srv.with_graceful_shutdown(signal).map(|result| {
+ if let Err(err) = result {
+ log::error!("server error: {}", err)
+ }
+ });
+ (addr, fut)
+ }
+}
+
+#[cfg(feature = "tls")]
+impl<F> ::std::fmt::Debug for TlsServer<F>
+where
+ F: ::std::fmt::Debug,
+{
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.debug_struct("TlsServer")
+ .field("server", &self.server)
+ .finish()
+ }
+}
diff --git a/third_party/rust/warp/src/service.rs b/third_party/rust/warp/src/service.rs
new file mode 100644
index 0000000000..4f93809c4e
--- /dev/null
+++ b/third_party/rust/warp/src/service.rs
@@ -0,0 +1,3 @@
+//! Convert `Filter`s into `Service`s
+
+pub use crate::filter::service::service;
diff --git a/third_party/rust/warp/src/test.rs b/third_party/rust/warp/src/test.rs
new file mode 100644
index 0000000000..bcb0023699
--- /dev/null
+++ b/third_party/rust/warp/src/test.rs
@@ -0,0 +1,684 @@
+//! Test utilities to test your filters.
+//!
+//! [`Filter`](../trait.Filter.html)s can be easily tested without starting up an HTTP
+//! server, by making use of the [`RequestBuilder`](./struct.RequestBuilder.html) in this
+//! module.
+//!
+//! # Testing Filters
+//!
+//! It's easy to test filters, especially if smaller filters are used to build
+//! up your full set. Consider these example filters:
+//!
+//! ```
+//! use warp::Filter;
+//!
+//! fn sum() -> impl Filter<Extract = (u32,), Error = warp::Rejection> + Copy {
+//! warp::path::param()
+//! .and(warp::path::param())
+//! .map(|x: u32, y: u32| {
+//! x + y
+//! })
+//! }
+//!
+//! fn math() -> impl Filter<Extract = (String,), Error = warp::Rejection> + Copy {
+//! warp::post()
+//! .and(sum())
+//! .map(|z: u32| {
+//! format!("Sum = {}", z)
+//! })
+//! }
+//! ```
+//!
+//! We can test some requests against the `sum` filter like this:
+//!
+//! ```
+//! # use warp::Filter;
+//! #[test]
+//! fn test_sum() {
+//! # let sum = || warp::any().map(|| 3);
+//! let filter = sum();
+//!
+//! // Execute `sum` and get the `Extract` back.
+//! let value = warp::test::request()
+//! .path("/1/2")
+//! .filter(&filter)
+//! .unwrap();
+//! assert_eq!(value, 3);
+//!
+//! // Or simply test if a request matches (doesn't reject).
+//! assert!(
+//! !warp::test::request()
+//! .path("/1/-5")
+//! .matches(&filter)
+//! );
+//! }
+//! ```
+//!
+//! If the filter returns something that implements `Reply`, and thus can be
+//! turned into a response sent back to the client, we can test what exact
+//! response is returned. The `math` filter uses the `sum` filter, but returns
+//! a `String` that can be turned into a response.
+//!
+//! ```
+//! # use warp::Filter;
+//! #[test]
+//! fn test_math() {
+//! # let math = || warp::any().map(warp::reply);
+//! let filter = math();
+//!
+//! let res = warp::test::request()
+//! .path("/1/2")
+//! .reply(&filter);
+//! assert_eq!(res.status(), 405, "GET is not allowed");
+//!
+//! let res = warp::test::request()
+//! .method("POST")
+//! .path("/1/2")
+//! .reply(&filter);
+//! assert_eq!(res.status(), 200);
+//! assert_eq!(res.body(), "Sum is 3");
+//! }
+//! ```
+use std::convert::TryFrom;
+use std::error::Error as StdError;
+use std::fmt;
+use std::future::Future;
+use std::net::SocketAddr;
+#[cfg(feature = "websocket")]
+use std::pin::Pin;
+#[cfg(feature = "websocket")]
+use std::task::{self, Poll};
+
+use bytes::Bytes;
+#[cfg(feature = "websocket")]
+use futures::StreamExt;
+use futures::{future, FutureExt, TryFutureExt};
+use http::{
+ header::{HeaderName, HeaderValue},
+ Response,
+};
+use serde::Serialize;
+use serde_json;
+#[cfg(feature = "websocket")]
+use tokio::sync::{mpsc, oneshot};
+
+use crate::filter::Filter;
+use crate::reject::IsReject;
+use crate::reply::Reply;
+use crate::route::{self, Route};
+use crate::Request;
+
+use self::inner::OneOrTuple;
+
+/// Starts a new test `RequestBuilder`.
+pub fn request() -> RequestBuilder {
+ RequestBuilder {
+ remote_addr: None,
+ req: Request::default(),
+ }
+}
+
+/// Starts a new test `WsBuilder`.
+#[cfg(feature = "websocket")]
+pub fn ws() -> WsBuilder {
+ WsBuilder { req: request() }
+}
+
+/// A request builder for testing filters.
+///
+/// See [module documentation](crate::test) for an overview.
+#[must_use = "RequestBuilder does nothing on its own"]
+#[derive(Debug)]
+pub struct RequestBuilder {
+ remote_addr: Option<SocketAddr>,
+ req: Request,
+}
+
+/// A Websocket builder for testing filters.
+///
+/// See [module documentation](crate::test) for an overview.
+#[cfg(feature = "websocket")]
+#[must_use = "WsBuilder does nothing on its own"]
+#[derive(Debug)]
+pub struct WsBuilder {
+ req: RequestBuilder,
+}
+
+/// A test client for Websocket filters.
+#[cfg(feature = "websocket")]
+pub struct WsClient {
+ tx: mpsc::UnboundedSender<crate::ws::Message>,
+ rx: mpsc::UnboundedReceiver<Result<crate::ws::Message, crate::error::Error>>,
+}
+
+/// An error from Websocket filter tests.
+#[derive(Debug)]
+pub struct WsError {
+ cause: Box<dyn StdError + Send + Sync>,
+}
+
+impl RequestBuilder {
+ /// Sets the method of this builder.
+ ///
+ /// The default if not set is `GET`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::request()
+ /// .method("POST");
+ /// ```
+ ///
+ /// # Panic
+ ///
+ /// This panics if the passed string is not able to be parsed as a valid
+ /// `Method`.
+ pub fn method(mut self, method: &str) -> Self {
+ *self.req.method_mut() = method.parse().expect("valid method");
+ self
+ }
+
+ /// Sets the request path of this builder.
+ ///
+ /// The default is not set is `/`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::request()
+ /// .path("/todos/33");
+ /// ```
+ ///
+ /// # Panic
+ ///
+ /// This panics if the passed string is not able to be parsed as a valid
+ /// `Uri`.
+ pub fn path(mut self, p: &str) -> Self {
+ let uri = p.parse().expect("test request path invalid");
+ *self.req.uri_mut() = uri;
+ self
+ }
+
+ /// Set a header for this request.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::request()
+ /// .header("accept", "application/json");
+ /// ```
+ ///
+ /// # Panic
+ ///
+ /// This panics if the passed strings are not able to be parsed as a valid
+ /// `HeaderName` and `HeaderValue`.
+ pub fn header<K, V>(mut self, key: K, value: V) -> Self
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ {
+ let name: HeaderName = TryFrom::try_from(key)
+ .map_err(|_| ())
+ .expect("invalid header name");
+ let value = TryFrom::try_from(value)
+ .map_err(|_| ())
+ .expect("invalid header value");
+ self.req.headers_mut().insert(name, value);
+ self
+ }
+
+ /// Add a type to the request's `http::Extensions`.
+ pub fn extension<T>(mut self, ext: T) -> Self
+ where
+ T: Send + Sync + 'static,
+ {
+ self.req.extensions_mut().insert(ext);
+ self
+ }
+
+ /// Set the bytes of this request body.
+ ///
+ /// Default is an empty body.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::request()
+ /// .body("foo=bar&baz=quux");
+ /// ```
+ pub fn body(mut self, body: impl AsRef<[u8]>) -> Self {
+ let body = body.as_ref().to_vec();
+ let len = body.len();
+ *self.req.body_mut() = body.into();
+ self.header("content-length", len.to_string())
+ }
+
+ /// Set the bytes of this request body by serializing a value into JSON.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::request()
+ /// .json(&true);
+ /// ```
+ pub fn json(mut self, val: &impl Serialize) -> Self {
+ let vec = serde_json::to_vec(val).expect("json() must serialize to JSON");
+ let len = vec.len();
+ *self.req.body_mut() = vec.into();
+ self.header("content-length", len.to_string())
+ .header("content-type", "application/json")
+ }
+
+ /// Tries to apply the `Filter` on this request.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// async {
+ /// let param = warp::path::param::<u32>();
+ ///
+ /// let ex = warp::test::request()
+ /// .path("/41")
+ /// .filter(&param)
+ /// .await
+ /// .unwrap();
+ ///
+ /// assert_eq!(ex, 41);
+ ///
+ /// assert!(
+ /// warp::test::request()
+ /// .path("/foo")
+ /// .filter(&param)
+ /// .await
+ /// .is_err()
+ /// );
+ ///};
+ /// ```
+ pub async fn filter<F>(self, f: &F) -> Result<<F::Extract as OneOrTuple>::Output, F::Error>
+ where
+ F: Filter,
+ F::Future: Send + 'static,
+ F::Extract: OneOrTuple + Send + 'static,
+ F::Error: Send + 'static,
+ {
+ self.apply_filter(f).await.map(|ex| ex.one_or_tuple())
+ }
+
+ /// Returns whether the `Filter` matches this request, or rejects it.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// async {
+ /// let get = warp::get();
+ /// let post = warp::post();
+ ///
+ /// assert!(
+ /// warp::test::request()
+ /// .method("GET")
+ /// .matches(&get)
+ /// .await
+ /// );
+ ///
+ /// assert!(
+ /// !warp::test::request()
+ /// .method("GET")
+ /// .matches(&post)
+ /// .await
+ /// );
+ ///};
+ /// ```
+ pub async fn matches<F>(self, f: &F) -> bool
+ where
+ F: Filter,
+ F::Future: Send + 'static,
+ F::Extract: Send + 'static,
+ F::Error: Send + 'static,
+ {
+ self.apply_filter(f).await.is_ok()
+ }
+
+ /// Returns `Response` provided by applying the `Filter`.
+ ///
+ /// This requires that the supplied `Filter` return a [`Reply`](Reply).
+ pub async fn reply<F>(self, f: &F) -> Response<Bytes>
+ where
+ F: Filter + 'static,
+ F::Extract: Reply + Send,
+ F::Error: IsReject + Send,
+ {
+ // TODO: de-duplicate this and apply_filter()
+ assert!(!route::is_set(), "nested test filter calls");
+
+ let route = Route::new(self.req, self.remote_addr);
+ let mut fut = Box::pin(
+ route::set(&route, move || f.filter(crate::filter::Internal)).then(|result| {
+ let res = match result {
+ Ok(rep) => rep.into_response(),
+ Err(rej) => {
+ log::debug!("rejected: {:?}", rej);
+ rej.into_response()
+ }
+ };
+ let (parts, body) = res.into_parts();
+ hyper::body::to_bytes(body)
+ .map_ok(|chunk| Response::from_parts(parts, chunk.into()))
+ }),
+ );
+
+ let fut = future::poll_fn(move |cx| route::set(&route, || fut.as_mut().poll(cx)));
+
+ fut.await.expect("reply shouldn't fail")
+ }
+
+ fn apply_filter<F>(self, f: &F) -> impl Future<Output = Result<F::Extract, F::Error>>
+ where
+ F: Filter,
+ F::Future: Send + 'static,
+ F::Extract: Send + 'static,
+ F::Error: Send + 'static,
+ {
+ assert!(!route::is_set(), "nested test filter calls");
+
+ let route = Route::new(self.req, self.remote_addr);
+ let mut fut = Box::pin(route::set(&route, move || {
+ f.filter(crate::filter::Internal)
+ }));
+ future::poll_fn(move |cx| route::set(&route, || fut.as_mut().poll(cx)))
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl WsBuilder {
+ /// Sets the request path of this builder.
+ ///
+ /// The default is not set is `/`.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::ws()
+ /// .path("/chat");
+ /// ```
+ ///
+ /// # Panic
+ ///
+ /// This panics if the passed string is not able to be parsed as a valid
+ /// `Uri`.
+ pub fn path(self, p: &str) -> Self {
+ WsBuilder {
+ req: self.req.path(p),
+ }
+ }
+
+ /// Set a header for this request.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// let req = warp::test::ws()
+ /// .header("foo", "bar");
+ /// ```
+ ///
+ /// # Panic
+ ///
+ /// This panics if the passed strings are not able to be parsed as a valid
+ /// `HeaderName` and `HeaderValue`.
+ pub fn header<K, V>(self, key: K, value: V) -> Self
+ where
+ HeaderName: TryFrom<K>,
+ HeaderValue: TryFrom<V>,
+ {
+ WsBuilder {
+ req: self.req.header(key, value),
+ }
+ }
+
+ /// Execute this Websocket request against te provided filter.
+ ///
+ /// If the handshake succeeds, returns a `WsClient`.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use futures::future;
+ /// use warp::Filter;
+ /// #[tokio::main]
+ /// # async fn main() {
+ ///
+ /// // Some route that accepts websockets (but drops them immediately).
+ /// let route = warp::ws()
+ /// .map(|ws: warp::ws::Ws| {
+ /// ws.on_upgrade(|_| future::ready(()))
+ /// });
+ ///
+ /// let client = warp::test::ws()
+ /// .handshake(route)
+ /// .await
+ /// .expect("handshake");
+ /// # }
+ /// ```
+ pub async fn handshake<F>(self, f: F) -> Result<WsClient, WsError>
+ where
+ F: Filter + Clone + Send + Sync + 'static,
+ F::Extract: Reply + Send,
+ F::Error: IsReject + Send,
+ {
+ let (upgraded_tx, upgraded_rx) = oneshot::channel();
+ let (wr_tx, wr_rx) = mpsc::unbounded_channel();
+ let (rd_tx, rd_rx) = mpsc::unbounded_channel();
+
+ tokio::spawn(async move {
+ use tokio_tungstenite::tungstenite::protocol;
+
+ let (addr, srv) = crate::serve(f).bind_ephemeral(([127, 0, 0, 1], 0));
+
+ let mut req = self
+ .req
+ .header("connection", "upgrade")
+ .header("upgrade", "websocket")
+ .header("sec-websocket-version", "13")
+ .header("sec-websocket-key", "dGhlIHNhbXBsZSBub25jZQ==")
+ .req;
+
+ let uri = format!("http://{}{}", addr, req.uri().path())
+ .parse()
+ .expect("addr + path is valid URI");
+
+ *req.uri_mut() = uri;
+
+ // let mut rt = current_thread::Runtime::new().unwrap();
+ tokio::spawn(srv);
+
+ let upgrade = ::hyper::Client::builder()
+ .build(AddrConnect(addr))
+ .request(req)
+ .and_then(|res| res.into_body().on_upgrade());
+
+ let upgraded = match upgrade.await {
+ Ok(up) => {
+ let _ = upgraded_tx.send(Ok(()));
+ up
+ }
+ Err(err) => {
+ let _ = upgraded_tx.send(Err(err));
+ return;
+ }
+ };
+ let ws = crate::ws::WebSocket::from_raw_socket(
+ upgraded,
+ protocol::Role::Client,
+ Default::default(),
+ )
+ .await;
+
+ let (tx, rx) = ws.split();
+ let write = wr_rx.map(Ok).forward(tx).map(|_| ());
+
+ let read = rx
+ .take_while(|result| match result {
+ Err(_) => future::ready(false),
+ Ok(m) => future::ready(!m.is_close()),
+ })
+ .for_each(move |item| {
+ rd_tx.send(item).expect("ws receive error");
+ future::ready(())
+ });
+
+ future::join(write, read).await;
+ });
+
+ match upgraded_rx.await {
+ Ok(Ok(())) => Ok(WsClient {
+ tx: wr_tx,
+ rx: rd_rx,
+ }),
+ Ok(Err(err)) => Err(WsError::new(err)),
+ Err(_canceled) => panic!("websocket handshake thread panicked"),
+ }
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl WsClient {
+ /// Send a "text" websocket message to the server.
+ pub async fn send_text(&mut self, text: impl Into<String>) {
+ self.send(crate::ws::Message::text(text)).await;
+ }
+
+ /// Send a websocket message to the server.
+ pub async fn send(&mut self, msg: crate::ws::Message) {
+ self.tx.send(msg).unwrap();
+ }
+
+ /// Receive a websocket message from the server.
+ pub async fn recv(&mut self) -> Result<crate::filters::ws::Message, WsError> {
+ self.rx
+ .next()
+ .await
+ .map(|unbounded_result| unbounded_result.map_err(WsError::new))
+ .unwrap_or_else(|| {
+ // websocket is closed
+ Err(WsError::new("closed"))
+ })
+ }
+
+ /// Assert the server has closed the connection.
+ pub async fn recv_closed(&mut self) -> Result<(), WsError> {
+ self.rx
+ .next()
+ .await
+ .map(|result| match result {
+ Ok(msg) => Err(WsError::new(format!("received message: {:?}", msg))),
+ Err(err) => Err(WsError::new(err)),
+ })
+ .unwrap_or_else(|| {
+ // closed successfully
+ Ok(())
+ })
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl fmt::Debug for WsClient {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ f.debug_struct("WsClient").finish()
+ }
+}
+
+// ===== impl WsError =====
+
+#[cfg(feature = "websocket")]
+impl WsError {
+ fn new<E: Into<Box<dyn StdError + Send + Sync>>>(cause: E) -> Self {
+ WsError {
+ cause: cause.into(),
+ }
+ }
+}
+
+impl fmt::Display for WsError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "websocket error: {}", self.cause)
+ }
+}
+
+impl StdError for WsError {
+ fn description(&self) -> &str {
+ "websocket error"
+ }
+}
+
+// ===== impl AddrConnect =====
+
+#[cfg(feature = "websocket")]
+#[derive(Clone)]
+struct AddrConnect(SocketAddr);
+
+#[cfg(feature = "websocket")]
+impl tower_service::Service<::http::Uri> for AddrConnect {
+ type Response = ::tokio::net::TcpStream;
+ type Error = ::std::io::Error;
+ type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
+
+ fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
+ Poll::Ready(Ok(()))
+ }
+
+ fn call(&mut self, _: ::http::Uri) -> Self::Future {
+ Box::pin(tokio::net::TcpStream::connect(self.0))
+ }
+}
+
+mod inner {
+ pub trait OneOrTuple {
+ type Output;
+
+ fn one_or_tuple(self) -> Self::Output;
+ }
+
+ impl OneOrTuple for () {
+ type Output = ();
+ fn one_or_tuple(self) -> Self::Output {}
+ }
+
+ macro_rules! one_or_tuple {
+ ($type1:ident) => {
+ impl<$type1> OneOrTuple for ($type1,) {
+ type Output = $type1;
+ fn one_or_tuple(self) -> Self::Output {
+ self.0
+ }
+ }
+ };
+ ($type1:ident, $( $type:ident ),*) => {
+ one_or_tuple!($( $type ),*);
+
+ impl<$type1, $($type),*> OneOrTuple for ($type1, $($type),*) {
+ type Output = Self;
+ fn one_or_tuple(self) -> Self::Output {
+ self
+ }
+ }
+ }
+ }
+
+ one_or_tuple! {
+ T1,
+ T2,
+ T3,
+ T4,
+ T5,
+ T6,
+ T7,
+ T8,
+ T9,
+ T10,
+ T11,
+ T12,
+ T13,
+ T14,
+ T15,
+ T16
+ }
+}
diff --git a/third_party/rust/warp/src/tls.rs b/third_party/rust/warp/src/tls.rs
new file mode 100644
index 0000000000..7a73be724c
--- /dev/null
+++ b/third_party/rust/warp/src/tls.rs
@@ -0,0 +1,314 @@
+use std::fs::File;
+use std::future::Future;
+use std::io::{self, BufReader, Cursor, Read};
+use std::net::SocketAddr;
+use std::path::{Path, PathBuf};
+use std::pin::Pin;
+use std::sync::Arc;
+use std::task::{Context, Poll};
+use tokio::io::{AsyncRead, AsyncWrite};
+
+use futures::ready;
+use hyper::server::accept::Accept;
+use hyper::server::conn::{AddrIncoming, AddrStream};
+
+use crate::transport::Transport;
+use tokio_rustls::rustls::{NoClientAuth, ServerConfig, TLSError};
+
+/// Represents errors that can occur building the TlsConfig
+#[derive(Debug)]
+pub(crate) enum TlsConfigError {
+ Io(io::Error),
+ /// An Error parsing the Certificate
+ CertParseError,
+ /// An Error parsing a Pkcs8 key
+ Pkcs8ParseError,
+ /// An Error parsing a Rsa key
+ RsaParseError,
+ /// An error from an empty key
+ EmptyKey,
+ /// An error from an invalid key
+ InvalidKey(TLSError),
+}
+
+impl std::fmt::Display for TlsConfigError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ TlsConfigError::Io(err) => err.fmt(f),
+ TlsConfigError::CertParseError => write!(f, "certificate parse error"),
+ TlsConfigError::Pkcs8ParseError => write!(f, "pkcs8 parse error"),
+ TlsConfigError::RsaParseError => write!(f, "rsa parse error"),
+ TlsConfigError::EmptyKey => write!(f, "key contains no private key"),
+ TlsConfigError::InvalidKey(err) => write!(f, "key contains an invalid key, {}", err),
+ }
+ }
+}
+
+impl std::error::Error for TlsConfigError {}
+
+/// Builder to set the configuration for the Tls server.
+pub(crate) struct TlsConfigBuilder {
+ cert: Box<dyn Read + Send + Sync>,
+ key: Box<dyn Read + Send + Sync>,
+}
+
+impl std::fmt::Debug for TlsConfigBuilder {
+ fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+ f.debug_struct("TlsConfigBuilder").finish()
+ }
+}
+
+impl TlsConfigBuilder {
+ /// Create a new TlsConfigBuilder
+ pub(crate) fn new() -> TlsConfigBuilder {
+ TlsConfigBuilder {
+ key: Box::new(io::empty()),
+ cert: Box::new(io::empty()),
+ }
+ }
+
+ /// sets the Tls key via File Path, returns `TlsConfigError::IoError` if the file cannot be open
+ pub(crate) fn key_path(mut self, path: impl AsRef<Path>) -> Self {
+ self.key = Box::new(LazyFile {
+ path: path.as_ref().into(),
+ file: None,
+ });
+ self
+ }
+
+ /// sets the Tls key via bytes slice
+ pub(crate) fn key(mut self, key: &[u8]) -> Self {
+ self.key = Box::new(Cursor::new(Vec::from(key)));
+ self
+ }
+
+ /// Specify the file path for the TLS certificate to use.
+ pub(crate) fn cert_path(mut self, path: impl AsRef<Path>) -> Self {
+ self.cert = Box::new(LazyFile {
+ path: path.as_ref().into(),
+ file: None,
+ });
+ self
+ }
+
+ /// sets the Tls certificate via bytes slice
+ pub(crate) fn cert(mut self, cert: &[u8]) -> Self {
+ self.cert = Box::new(Cursor::new(Vec::from(cert)));
+ self
+ }
+
+ pub(crate) fn build(mut self) -> Result<ServerConfig, TlsConfigError> {
+ let mut cert_rdr = BufReader::new(self.cert);
+ let cert = tokio_rustls::rustls::internal::pemfile::certs(&mut cert_rdr)
+ .map_err(|()| TlsConfigError::CertParseError)?;
+
+ let key = {
+ // convert it to Vec<u8> to allow reading it again if key is RSA
+ let mut key_vec = Vec::new();
+ self.key
+ .read_to_end(&mut key_vec)
+ .map_err(TlsConfigError::Io)?;
+
+ if key_vec.is_empty() {
+ return Err(TlsConfigError::EmptyKey);
+ }
+
+ let mut pkcs8 = tokio_rustls::rustls::internal::pemfile::pkcs8_private_keys(
+ &mut key_vec.as_slice(),
+ )
+ .map_err(|()| TlsConfigError::Pkcs8ParseError)?;
+
+ if !pkcs8.is_empty() {
+ pkcs8.remove(0)
+ } else {
+ let mut rsa = tokio_rustls::rustls::internal::pemfile::rsa_private_keys(
+ &mut key_vec.as_slice(),
+ )
+ .map_err(|()| TlsConfigError::RsaParseError)?;
+
+ if !rsa.is_empty() {
+ rsa.remove(0)
+ } else {
+ return Err(TlsConfigError::EmptyKey);
+ }
+ }
+ };
+
+ let mut config = ServerConfig::new(NoClientAuth::new());
+ config
+ .set_single_cert(cert, key)
+ .map_err(|err| TlsConfigError::InvalidKey(err))?;
+ config.set_protocols(&["h2".into(), "http/1.1".into()]);
+ Ok(config)
+ }
+}
+
+struct LazyFile {
+ path: PathBuf,
+ file: Option<File>,
+}
+
+impl LazyFile {
+ fn lazy_read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ if self.file.is_none() {
+ self.file = Some(File::open(&self.path)?);
+ }
+
+ self.file.as_mut().unwrap().read(buf)
+ }
+}
+
+impl Read for LazyFile {
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ self.lazy_read(buf).map_err(|err| {
+ let kind = err.kind();
+ io::Error::new(
+ kind,
+ format!("error reading file ({:?}): {}", self.path.display(), err),
+ )
+ })
+ }
+}
+
+impl Transport for TlsStream {
+ fn remote_addr(&self) -> Option<SocketAddr> {
+ match self.state {
+ State::Handshaking(_) => None,
+ State::Streaming(ref stream) => Some(stream.get_ref().0.remote_addr()),
+ }
+ }
+}
+
+enum State {
+ Handshaking(tokio_rustls::Accept<AddrStream>),
+ Streaming(tokio_rustls::server::TlsStream<AddrStream>),
+}
+
+// tokio_rustls::server::TlsStream doesn't expose constructor methods,
+// so we have to TlsAcceptor::accept and handshake to have access to it
+// TlsStream implements AsyncRead/AsyncWrite handshaking tokio_rustls::Accept first
+pub(crate) struct TlsStream {
+ state: State,
+}
+
+impl TlsStream {
+ fn new(stream: AddrStream, config: Arc<ServerConfig>) -> TlsStream {
+ let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream);
+ TlsStream {
+ state: State::Handshaking(accept),
+ }
+ }
+}
+
+impl AsyncRead for TlsStream {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context,
+ buf: &mut [u8],
+ ) -> Poll<io::Result<usize>> {
+ let pin = self.get_mut();
+ match pin.state {
+ State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
+ Ok(mut stream) => {
+ let result = Pin::new(&mut stream).poll_read(cx, buf);
+ pin.state = State::Streaming(stream);
+ result
+ }
+ Err(err) => Poll::Ready(Err(err)),
+ },
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_read(cx, buf),
+ }
+ }
+}
+
+impl AsyncWrite for TlsStream {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ let pin = self.get_mut();
+ match pin.state {
+ State::Handshaking(ref mut accept) => match ready!(Pin::new(accept).poll(cx)) {
+ Ok(mut stream) => {
+ let result = Pin::new(&mut stream).poll_write(cx, buf);
+ pin.state = State::Streaming(stream);
+ result
+ }
+ Err(err) => Poll::Ready(Err(err)),
+ },
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_write(cx, buf),
+ }
+ }
+
+ fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
+ match self.state {
+ State::Handshaking(_) => Poll::Ready(Ok(())),
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_flush(cx),
+ }
+ }
+
+ fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
+ match self.state {
+ State::Handshaking(_) => Poll::Ready(Ok(())),
+ State::Streaming(ref mut stream) => Pin::new(stream).poll_shutdown(cx),
+ }
+ }
+}
+
+pub(crate) struct TlsAcceptor {
+ config: Arc<ServerConfig>,
+ incoming: AddrIncoming,
+}
+
+impl TlsAcceptor {
+ pub(crate) fn new(config: ServerConfig, incoming: AddrIncoming) -> TlsAcceptor {
+ TlsAcceptor {
+ config: Arc::new(config),
+ incoming,
+ }
+ }
+}
+
+impl Accept for TlsAcceptor {
+ type Conn = TlsStream;
+ type Error = io::Error;
+
+ fn poll_accept(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ ) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
+ let pin = self.get_mut();
+ match ready!(Pin::new(&mut pin.incoming).poll_accept(cx)) {
+ Some(Ok(sock)) => Poll::Ready(Some(Ok(TlsStream::new(sock, pin.config.clone())))),
+ Some(Err(e)) => Poll::Ready(Some(Err(e))),
+ None => Poll::Ready(None),
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn file_cert_key() {
+ TlsConfigBuilder::new()
+ .key_path("examples/tls/key.rsa")
+ .cert_path("examples/tls/cert.pem")
+ .build()
+ .unwrap();
+ }
+
+ #[test]
+ fn bytes_cert_key() {
+ let key = include_str!("../examples/tls/key.rsa");
+ let cert = include_str!("../examples/tls/cert.pem");
+
+ TlsConfigBuilder::new()
+ .key(key.as_bytes())
+ .cert(cert.as_bytes())
+ .build()
+ .unwrap();
+ }
+}
diff --git a/third_party/rust/warp/src/transport.rs b/third_party/rust/warp/src/transport.rs
new file mode 100644
index 0000000000..42f0431dfd
--- /dev/null
+++ b/third_party/rust/warp/src/transport.rs
@@ -0,0 +1,53 @@
+use std::io;
+use std::net::SocketAddr;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use hyper::server::conn::AddrStream;
+use tokio::io::{AsyncRead, AsyncWrite};
+
+pub trait Transport: AsyncRead + AsyncWrite {
+ fn remote_addr(&self) -> Option<SocketAddr>;
+}
+
+impl Transport for AddrStream {
+ fn remote_addr(&self) -> Option<SocketAddr> {
+ Some(self.remote_addr())
+ }
+}
+
+pub(crate) struct LiftIo<T>(pub(crate) T);
+
+impl<T: AsyncRead + Unpin> AsyncRead for LiftIo<T> {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &mut [u8],
+ ) -> Poll<io::Result<usize>> {
+ Pin::new(&mut self.get_mut().0).poll_read(cx, buf)
+ }
+}
+
+impl<T: AsyncWrite + Unpin> AsyncWrite for LiftIo<T> {
+ fn poll_write(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &[u8],
+ ) -> Poll<io::Result<usize>> {
+ Pin::new(&mut self.get_mut().0).poll_write(cx, buf)
+ }
+
+ fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
+ Pin::new(&mut self.get_mut().0).poll_flush(cx)
+ }
+
+ fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
+ Pin::new(&mut self.get_mut().0).poll_shutdown(cx)
+ }
+}
+
+impl<T: AsyncRead + AsyncWrite + Unpin> Transport for LiftIo<T> {
+ fn remote_addr(&self) -> Option<SocketAddr> {
+ None
+ }
+}
diff --git a/third_party/rust/warp/tests/body.rs b/third_party/rust/warp/tests/body.rs
new file mode 100644
index 0000000000..8d4bce7d05
--- /dev/null
+++ b/third_party/rust/warp/tests/body.rs
@@ -0,0 +1,202 @@
+#![deny(warnings)]
+
+use bytes::Buf;
+use futures::TryStreamExt;
+use warp::Filter;
+
+#[tokio::test]
+async fn matches() {
+ let _ = pretty_env_logger::try_init();
+
+ let concat = warp::body::bytes();
+
+ let req = warp::test::request().path("/nothing-matches-me");
+
+ assert!(req.matches(&concat).await);
+
+ let p = warp::path("body");
+ let req = warp::test::request().path("/body");
+
+ let and = p.and(concat);
+
+ assert!(req.matches(&and).await);
+}
+
+#[tokio::test]
+async fn server_error_if_taking_body_multiple_times() {
+ let _ = pretty_env_logger::try_init();
+
+ let concat = warp::body::bytes();
+ let double = concat.and(concat).map(|_, _| warp::reply());
+
+ let res = warp::test::request().reply(&double).await;
+
+ assert_eq!(res.status(), 500);
+ assert_eq!(res.body(), "Request body consumed multiple times");
+}
+
+#[tokio::test]
+async fn content_length_limit() {
+ let _ = pretty_env_logger::try_init();
+
+ let limit = warp::body::content_length_limit(30).map(warp::reply);
+
+ let res = warp::test::request().reply(&limit).await;
+ assert_eq!(res.status(), 411, "missing content-length returns 411");
+
+ let res = warp::test::request()
+ .header("content-length", "999")
+ .reply(&limit)
+ .await;
+ assert_eq!(res.status(), 413, "over limit returns 413");
+
+ let res = warp::test::request()
+ .header("content-length", "2")
+ .reply(&limit)
+ .await;
+ assert_eq!(res.status(), 200, "under limit succeeds");
+}
+
+#[tokio::test]
+async fn json() {
+ let _ = pretty_env_logger::try_init();
+
+ let json = warp::body::json::<Vec<i32>>();
+
+ let req = warp::test::request().body("[1, 2, 3]");
+
+ let vec = req.filter(&json).await.unwrap();
+ assert_eq!(vec, &[1, 2, 3]);
+
+ let req = warp::test::request()
+ .header("content-type", "application/json")
+ .body("[3, 2, 1]");
+
+ let vec = req.filter(&json).await.unwrap();
+ assert_eq!(vec, &[3, 2, 1], "matches content-type");
+}
+
+#[tokio::test]
+async fn json_rejects_bad_content_type() {
+ let _ = pretty_env_logger::try_init();
+
+ let json = warp::body::json::<Vec<i32>>().map(|_| warp::reply());
+
+ let req = warp::test::request()
+ .header("content-type", "text/xml")
+ .body("[3, 2, 1]");
+
+ let res = req.reply(&json).await;
+ assert_eq!(
+ res.status(),
+ 415,
+ "bad content-type should be 415 Unsupported Media Type"
+ );
+}
+
+#[tokio::test]
+async fn json_invalid() {
+ let _ = pretty_env_logger::try_init();
+
+ let json = warp::body::json::<Vec<i32>>().map(|vec| warp::reply::json(&vec));
+
+ let res = warp::test::request().body("lol#wat").reply(&json).await;
+ assert_eq!(res.status(), 400);
+ let prefix = b"Request body deserialize error: ";
+ assert_eq!(&res.body()[..prefix.len()], prefix);
+}
+
+#[test]
+fn json_size_of() {
+ let json = warp::body::json::<Vec<i32>>();
+ assert_eq!(std::mem::size_of_val(&json), 0);
+}
+
+#[tokio::test]
+async fn form() {
+ let _ = pretty_env_logger::try_init();
+
+ let form = warp::body::form::<Vec<(String, String)>>();
+
+ let req = warp::test::request().body("foo=bar&baz=quux");
+
+ let vec = req.filter(&form).await.unwrap();
+ let expected = vec![
+ ("foo".to_owned(), "bar".to_owned()),
+ ("baz".to_owned(), "quux".to_owned()),
+ ];
+ assert_eq!(vec, expected);
+}
+
+#[tokio::test]
+async fn form_rejects_bad_content_type() {
+ let _ = pretty_env_logger::try_init();
+
+ let form = warp::body::form::<Vec<(String, String)>>().map(|_| warp::reply());
+
+ let req = warp::test::request()
+ .header("content-type", "application/x-www-form-urlencoded")
+ .body("foo=bar");
+
+ let res = req.reply(&form).await;
+ assert_eq!(res.status(), 200);
+
+ let req = warp::test::request()
+ .header("content-type", "text/xml")
+ .body("foo=bar");
+ let res = req.reply(&form).await;
+ assert_eq!(
+ res.status(),
+ 415,
+ "bad content-type should be 415 Unsupported Media Type"
+ );
+}
+
+#[tokio::test]
+async fn form_allows_charset() {
+ let _ = pretty_env_logger::try_init();
+
+ let form = warp::body::form::<Vec<(String, String)>>();
+
+ let req = warp::test::request()
+ .header(
+ "content-type",
+ "application/x-www-form-urlencoded; charset=utf-8",
+ )
+ .body("foo=bar");
+
+ let vec = req.filter(&form).await.unwrap();
+ let expected = vec![("foo".to_owned(), "bar".to_owned())];
+ assert_eq!(vec, expected);
+}
+
+#[tokio::test]
+async fn form_invalid() {
+ let _ = pretty_env_logger::try_init();
+
+ let form = warp::body::form::<Vec<i32>>().map(|vec| warp::reply::json(&vec));
+
+ let res = warp::test::request().body("nope").reply(&form).await;
+ assert_eq!(res.status(), 400);
+ let prefix = b"Request body deserialize error: ";
+ assert_eq!(&res.body()[..prefix.len()], prefix);
+}
+
+#[tokio::test]
+async fn stream() {
+ let _ = pretty_env_logger::try_init();
+
+ let stream = warp::body::stream();
+
+ let body = warp::test::request()
+ .body("foo=bar")
+ .filter(&stream)
+ .await
+ .expect("filter() stream");
+
+ let bufs: Result<Vec<_>, warp::Error> = body.try_collect().await;
+ let bufs = bufs.unwrap();
+
+ assert_eq!(bufs.len(), 1);
+ assert_eq!(bufs[0].bytes(), b"foo=bar");
+}
diff --git a/third_party/rust/warp/tests/cookie.rs b/third_party/rust/warp/tests/cookie.rs
new file mode 100644
index 0000000000..fadf3e3cea
--- /dev/null
+++ b/third_party/rust/warp/tests/cookie.rs
@@ -0,0 +1,50 @@
+#![deny(warnings)]
+
+#[tokio::test]
+async fn cookie() {
+ let foo = warp::cookie("foo");
+
+ let req = warp::test::request().header("cookie", "foo=bar");
+ assert_eq!(req.filter(&foo).await.unwrap(), "bar");
+
+ let req = warp::test::request().header("cookie", "abc=def; foo=baz");
+ assert_eq!(req.filter(&foo).await.unwrap(), "baz");
+
+ let req = warp::test::request().header("cookie", "abc=def");
+ assert!(!req.matches(&foo).await);
+
+ let req = warp::test::request().header("cookie", "foobar=quux");
+ assert!(!req.matches(&foo).await);
+}
+
+#[tokio::test]
+async fn optional() {
+ let foo = warp::cookie::optional("foo");
+
+ let req = warp::test::request().header("cookie", "foo=bar");
+ assert_eq!(req.filter(&foo).await.unwrap().unwrap(), "bar");
+
+ let req = warp::test::request().header("cookie", "abc=def; foo=baz");
+ assert_eq!(req.filter(&foo).await.unwrap().unwrap(), "baz");
+
+ let req = warp::test::request().header("cookie", "abc=def");
+ assert!(req.matches(&foo).await);
+
+ let req = warp::test::request().header("cookie", "foobar=quux");
+ assert!(req.matches(&foo).await);
+}
+
+#[tokio::test]
+async fn missing() {
+ let _ = pretty_env_logger::try_init();
+
+ let cookie = warp::cookie("foo");
+
+ let res = warp::test::request()
+ .header("cookie", "not=here")
+ .reply(&cookie)
+ .await;
+
+ assert_eq!(res.status(), 400);
+ assert_eq!(res.body(), "Missing request cookie \"foo\"");
+}
diff --git a/third_party/rust/warp/tests/cors.rs b/third_party/rust/warp/tests/cors.rs
new file mode 100644
index 0000000000..86ec8a386d
--- /dev/null
+++ b/third_party/rust/warp/tests/cors.rs
@@ -0,0 +1,184 @@
+#![deny(warnings)]
+use warp::{http::Method, Filter};
+
+#[tokio::test]
+async fn allow_methods() {
+ let cors = warp::cors().allow_methods(&[Method::GET, Method::POST, Method::DELETE]);
+
+ let route = warp::any().map(warp::reply).with(cors);
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "warp")
+ .header("access-control-request-method", "DELETE")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 200);
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "warp")
+ .header("access-control-request-method", "PUT")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 403);
+}
+
+#[tokio::test]
+async fn origin_not_allowed() {
+ let cors = warp::cors()
+ .allow_methods(&[Method::DELETE])
+ .allow_origin("https://hyper.rs");
+
+ let route = warp::any().map(warp::reply).with(cors);
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "https://warp.rs")
+ .header("access-control-request-method", "DELETE")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 403);
+
+ let res = warp::test::request()
+ .header("origin", "https://warp.rs")
+ .header("access-control-request-method", "DELETE")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 403);
+}
+
+#[tokio::test]
+async fn headers_not_exposed() {
+ let cors = warp::cors()
+ .allow_any_origin()
+ .allow_methods(&[Method::GET]);
+
+ let route = warp::any().map(warp::reply).with(cors);
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "https://warp.rs")
+ .header("access-control-request-method", "GET")
+ .reply(&route)
+ .await;
+
+ assert_eq!(
+ res.headers().contains_key("access-control-expose-headers"),
+ false
+ );
+
+ let res = warp::test::request()
+ .method("GET")
+ .header("origin", "https://warp.rs")
+ .reply(&route)
+ .await;
+
+ assert_eq!(
+ res.headers().contains_key("access-control-expose-headers"),
+ false
+ );
+}
+
+#[tokio::test]
+async fn headers_not_allowed() {
+ let cors = warp::cors()
+ .allow_methods(&[Method::DELETE])
+ .allow_headers(vec!["x-foo"]);
+
+ let route = warp::any().map(warp::reply).with(cors);
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "https://warp.rs")
+ .header("access-control-request-headers", "x-bar")
+ .header("access-control-request-method", "DELETE")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 403);
+}
+
+#[tokio::test]
+async fn success() {
+ let cors = warp::cors()
+ .allow_credentials(true)
+ .allow_headers(vec!["x-foo", "x-bar"])
+ .allow_methods(&[Method::POST, Method::DELETE])
+ .expose_header("x-header1")
+ .expose_headers(vec!["x-header2"])
+ .max_age(30);
+
+ let route = warp::any().map(warp::reply).with(cors);
+
+ // preflight
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "https://hyper.rs")
+ .header("access-control-request-headers", "x-bar,x-foo")
+ .header("access-control-request-method", "DELETE")
+ .reply(&route)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(
+ res.headers()["access-control-allow-origin"],
+ "https://hyper.rs"
+ );
+ assert_eq!(res.headers()["access-control-allow-credentials"], "true");
+ let allowed_headers = &res.headers()["access-control-allow-headers"];
+ assert!(allowed_headers == "x-bar, x-foo" || allowed_headers == "x-foo, x-bar");
+ let exposed_headers = &res.headers()["access-control-expose-headers"];
+ assert!(exposed_headers == "x-header1, x-header2" || exposed_headers == "x-header2, x-header1");
+ assert_eq!(res.headers()["access-control-max-age"], "30");
+ let methods = &res.headers()["access-control-allow-methods"];
+ assert!(
+ // HashSet randomly orders these...
+ methods == "DELETE, POST" || methods == "POST, DELETE",
+ "access-control-allow-methods: {:?}",
+ methods,
+ );
+
+ // cors request
+ let res = warp::test::request()
+ .method("DELETE")
+ .header("origin", "https://hyper.rs")
+ .header("x-foo", "hello")
+ .header("x-bar", "world")
+ .reply(&route)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(
+ res.headers()["access-control-allow-origin"],
+ "https://hyper.rs"
+ );
+ assert_eq!(res.headers()["access-control-allow-credentials"], "true");
+ assert_eq!(res.headers().get("access-control-max-age"), None);
+ assert_eq!(res.headers().get("access-control-allow-methods"), None);
+ let exposed_headers = &res.headers()["access-control-expose-headers"];
+ assert!(exposed_headers == "x-header1, x-header2" || exposed_headers == "x-header2, x-header1");
+}
+
+#[tokio::test]
+async fn with_log() {
+ let cors = warp::cors()
+ .allow_any_origin()
+ .allow_methods(&[Method::GET]);
+
+ let route = warp::any()
+ .map(warp::reply)
+ .with(cors)
+ .with(warp::log("cors test"));
+
+ let res = warp::test::request()
+ .method("OPTIONS")
+ .header("origin", "warp")
+ .header("access-control-request-method", "GET")
+ .reply(&route)
+ .await;
+
+ assert_eq!(res.status(), 200);
+}
diff --git a/third_party/rust/warp/tests/ext.rs b/third_party/rust/warp/tests/ext.rs
new file mode 100644
index 0000000000..0393d8893d
--- /dev/null
+++ b/third_party/rust/warp/tests/ext.rs
@@ -0,0 +1,28 @@
+#![deny(warnings)]
+use warp::Filter;
+
+#[derive(Clone, Debug, PartialEq)]
+struct Ext1(i32);
+
+#[tokio::test]
+async fn set_and_get() {
+ let ext = warp::ext::get::<Ext1>();
+
+ let extracted = warp::test::request()
+ .extension(Ext1(55))
+ .filter(&ext)
+ .await
+ .unwrap();
+
+ assert_eq!(extracted, Ext1(55));
+}
+
+#[tokio::test]
+async fn get_missing() {
+ let ext = warp::ext::get().map(|e: Ext1| e.0.to_string());
+
+ let res = warp::test::request().reply(&ext).await;
+
+ assert_eq!(res.status(), 500);
+ assert_eq!(res.body(), "Missing request extension");
+}
diff --git a/third_party/rust/warp/tests/filter.rs b/third_party/rust/warp/tests/filter.rs
new file mode 100644
index 0000000000..5f69418847
--- /dev/null
+++ b/third_party/rust/warp/tests/filter.rs
@@ -0,0 +1,158 @@
+#![deny(warnings)]
+use std::convert::Infallible;
+use warp::Filter;
+
+#[tokio::test]
+async fn flattens_tuples() {
+ let _ = pretty_env_logger::try_init();
+
+ let str1 = warp::any().map(|| "warp");
+ let true1 = warp::any().map(|| true);
+ let unit1 = warp::any();
+
+ // just 1 value
+ let ext = warp::test::request().filter(&str1).await.unwrap();
+ assert_eq!(ext, "warp");
+
+ // just 1 unit
+ let ext = warp::test::request().filter(&unit1).await.unwrap();
+ assert_eq!(ext, ());
+
+ // combine 2 values
+ let and = str1.and(true1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true));
+
+ // combine 2 reversed
+ let and = true1.and(str1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, (true, "warp"));
+
+ // combine 1 with unit
+ let and = str1.and(unit1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, "warp");
+
+ let and = unit1.and(str1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, "warp");
+
+ // combine 3 values
+ let and = str1.and(str1).and(true1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", "warp", true));
+
+ // combine 2 with unit
+ let and = str1.and(unit1).and(true1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true));
+
+ let and = unit1.and(str1).and(true1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true));
+
+ let and = str1.and(true1).and(unit1);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true));
+
+ // nested tuples
+ let str_true_unit = str1.and(true1).and(unit1);
+ let unit_str_true = unit1.and(str1).and(true1);
+
+ let and = str_true_unit.and(unit_str_true);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true, "warp", true));
+
+ let and = unit_str_true.and(unit1).and(str1).and(str_true_unit);
+ let ext = warp::test::request().filter(&and).await.unwrap();
+ assert_eq!(ext, ("warp", true, "warp", "warp", true));
+}
+
+#[tokio::test]
+async fn map() {
+ let _ = pretty_env_logger::try_init();
+
+ let ok = warp::any().map(warp::reply);
+
+ let req = warp::test::request();
+ let resp = req.reply(&ok).await;
+ assert_eq!(resp.status(), 200);
+}
+
+#[tokio::test]
+async fn or() {
+ let _ = pretty_env_logger::try_init();
+
+ // Or can be combined with an infallible filter
+ let a = warp::path::param::<u32>();
+ let b = warp::any().map(|| 41i32);
+ let f = a.or(b);
+
+ let _: Result<_, Infallible> = warp::test::request().filter(&f).await;
+}
+
+#[tokio::test]
+async fn or_else() {
+ let _ = pretty_env_logger::try_init();
+
+ let a = warp::path::param::<u32>();
+ let f = a.or_else(|_| async { Ok::<_, warp::Rejection>((44u32,)) });
+
+ assert_eq!(
+ warp::test::request().path("/33").filter(&f).await.unwrap(),
+ 33,
+ );
+ assert_eq!(warp::test::request().filter(&f).await.unwrap(), 44,);
+
+ // OrElse can be combined with an infallible filter
+ let a = warp::path::param::<u32>();
+ let f = a.or_else(|_| async { Ok::<_, Infallible>((44u32,)) });
+
+ let _: Result<_, Infallible> = warp::test::request().filter(&f).await;
+}
+
+#[tokio::test]
+async fn recover() {
+ let _ = pretty_env_logger::try_init();
+
+ let a = warp::path::param::<String>();
+ let f = a.recover(|err| async move { Err::<String, _>(err) });
+
+ // not rejected
+ let resp = warp::test::request().path("/hi").reply(&f).await;
+ assert_eq!(resp.status(), 200);
+ assert_eq!(resp.body(), "hi");
+
+ // rejected, recovered, re-rejected
+ let resp = warp::test::request().reply(&f).await;
+ assert_eq!(resp.status(), 404);
+
+ // Recover can be infallible
+ let f = a.recover(|_| async move { Ok::<_, Infallible>("shh") });
+
+ let _: Result<_, Infallible> = warp::test::request().filter(&f).await;
+}
+
+#[tokio::test]
+async fn unify() {
+ let _ = pretty_env_logger::try_init();
+
+ let a = warp::path::param::<u32>();
+ let b = warp::path::param::<u32>();
+ let f = a.or(b).unify();
+
+ let ex = warp::test::request().path("/1").filter(&f).await.unwrap();
+
+ assert_eq!(ex, 1);
+}
+
+#[should_panic]
+#[tokio::test]
+async fn nested() {
+ let f = warp::any().and_then(|| async {
+ let p = warp::path::param::<u32>();
+ warp::test::request().filter(&p).await
+ });
+
+ let _ = warp::test::request().filter(&f).await;
+}
diff --git a/third_party/rust/warp/tests/fs.rs b/third_party/rust/warp/tests/fs.rs
new file mode 100644
index 0000000000..738ec06d6d
--- /dev/null
+++ b/third_party/rust/warp/tests/fs.rs
@@ -0,0 +1,228 @@
+#![deny(warnings)]
+use std::fs;
+
+#[tokio::test]
+async fn file() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::file("README.md");
+
+ let req = warp::test::request();
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 200);
+
+ let contents = fs::read("README.md").expect("fs::read README.md");
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+ assert_eq!(res.headers()["accept-ranges"], "bytes");
+
+ let ct = &res.headers()["content-type"];
+ assert!(
+ ct == "text/x-markdown" || ct == "text/markdown",
+ "content-type is not markdown: {:?}",
+ ct,
+ );
+
+ assert_eq!(res.body(), &*contents);
+}
+
+#[tokio::test]
+async fn dir() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+
+ let req = warp::test::request().path("/todos.rs");
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 200);
+
+ let contents = fs::read("examples/todos.rs").expect("fs::read");
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+ assert_eq!(res.headers()["content-type"], "text/x-rust");
+ assert_eq!(res.headers()["accept-ranges"], "bytes");
+
+ assert_eq!(res.body(), &*contents);
+}
+
+#[tokio::test]
+async fn dir_encoded() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+
+ let req = warp::test::request().path("/todos%2ers");
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 200);
+
+ let contents = fs::read("examples/todos.rs").expect("fs::read");
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+
+ assert_eq!(res.body(), &*contents);
+}
+
+#[tokio::test]
+async fn dir_not_found() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+
+ let req = warp::test::request().path("/definitely-not-found");
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 404);
+}
+
+#[tokio::test]
+async fn dir_bad_path() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+
+ let req = warp::test::request().path("/../README.md");
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 404);
+}
+
+#[tokio::test]
+async fn dir_bad_encoded_path() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+
+ let req = warp::test::request().path("/%2E%2e/README.md");
+ let res = req.reply(&file).await;
+
+ assert_eq!(res.status(), 404);
+}
+
+#[tokio::test]
+async fn dir_fallback_index_on_dir() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::dir("examples");
+ let req = warp::test::request().path("/dir");
+ let res = req.reply(&file).await;
+ let contents = fs::read("examples/dir/index.html").expect("fs::read");
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+ assert_eq!(res.status(), 200);
+ let req = warp::test::request().path("/dir/");
+ let res = req.reply(&file).await;
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+ assert_eq!(res.status(), 200);
+}
+
+#[tokio::test]
+async fn not_modified() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::file("README.md");
+
+ let req = warp::test::request();
+ let body = fs::read("README.md").unwrap();
+ let res1 = req.reply(&file).await;
+ assert_eq!(res1.status(), 200);
+ assert_eq!(res1.headers()["content-length"], body.len().to_string());
+
+ // if-modified-since
+ let res = warp::test::request()
+ .header("if-modified-since", &res1.headers()["last-modified"])
+ .reply(&file)
+ .await;
+ assert_eq!(res.headers().get("content-length"), None);
+ assert_eq!(res.status(), 304);
+ assert_eq!(res.body(), "");
+
+ // clearly too old
+ let res = warp::test::request()
+ .header("if-modified-since", "Sun, 07 Nov 1994 01:00:00 GMT")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(res.body(), &body);
+ assert_eq!(res1.headers()["content-length"], body.len().to_string());
+}
+
+#[tokio::test]
+async fn precondition() {
+ let _ = pretty_env_logger::try_init();
+
+ let file = warp::fs::file("README.md");
+
+ let req = warp::test::request();
+ let res1 = req.reply(&file).await;
+ assert_eq!(res1.status(), 200);
+
+ // if-unmodified-since
+ let res = warp::test::request()
+ .header("if-unmodified-since", &res1.headers()["last-modified"])
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 200);
+
+ // clearly too old
+ let res = warp::test::request()
+ .header("if-unmodified-since", "Sun, 07 Nov 1994 01:00:00 GMT")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 412);
+ assert_eq!(res.body(), "");
+}
+
+#[tokio::test]
+async fn byte_ranges() {
+ let _ = pretty_env_logger::try_init();
+
+ let contents = fs::read("README.md").expect("fs::read README.md");
+ let file = warp::fs::file("README.md");
+
+ let res = warp::test::request()
+ .header("range", "bytes=100-200")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 206);
+ assert_eq!(
+ res.headers()["content-range"],
+ format!("bytes 100-200/{}", contents.len())
+ );
+ assert_eq!(res.headers()["content-length"], "101");
+ assert_eq!(res.body(), &contents[100..=200]);
+
+ // bad range
+ let res = warp::test::request()
+ .header("range", "bytes=100-10")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 416);
+ assert_eq!(
+ res.headers()["content-range"],
+ format!("bytes */{}", contents.len())
+ );
+ assert_eq!(res.headers().get("content-length"), None);
+ assert_eq!(res.body(), "");
+
+ // out of range
+ let res = warp::test::request()
+ .header("range", "bytes=100-100000")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 416);
+ assert_eq!(
+ res.headers()["content-range"],
+ format!("bytes */{}", contents.len())
+ );
+ assert_eq!(res.headers().get("content-length"), None);
+ assert_eq!(res.body(), "");
+
+ // if-range too old
+ let res = warp::test::request()
+ .header("range", "bytes=100-200")
+ .header("if-range", "Sun, 07 Nov 1994 01:00:00 GMT")
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 200);
+ assert_eq!(res.headers()["content-length"], contents.len().to_string());
+ assert_eq!(res.headers().get("content-range"), None);
+}
diff --git a/third_party/rust/warp/tests/header.rs b/third_party/rust/warp/tests/header.rs
new file mode 100644
index 0000000000..ea882b44a1
--- /dev/null
+++ b/third_party/rust/warp/tests/header.rs
@@ -0,0 +1,71 @@
+#![deny(warnings)]
+use warp::Filter;
+
+#[tokio::test]
+async fn exact() {
+ let _ = pretty_env_logger::try_init();
+
+ let host = warp::header::exact("host", "localhost");
+
+ let req = warp::test::request().header("host", "localhost");
+
+ assert!(req.matches(&host).await);
+
+ let req = warp::test::request();
+ assert!(!req.matches(&host).await, "header missing");
+
+ let req = warp::test::request().header("host", "hyper.rs");
+ assert!(!req.matches(&host).await, "header value different");
+}
+
+#[tokio::test]
+async fn exact_rejections() {
+ let _ = pretty_env_logger::try_init();
+
+ let host = warp::header::exact("host", "localhost").map(warp::reply);
+
+ let res = warp::test::request()
+ .header("host", "nope")
+ .reply(&host)
+ .await;
+
+ assert_eq!(res.status(), 400);
+ assert_eq!(res.body(), "Invalid request header \"host\"");
+
+ let res = warp::test::request()
+ .header("not-even-a-host", "localhost")
+ .reply(&host)
+ .await;
+
+ assert_eq!(res.status(), 400);
+ assert_eq!(res.body(), "Missing request header \"host\"");
+}
+
+#[tokio::test]
+async fn optional() {
+ let _ = pretty_env_logger::try_init();
+
+ let con_len = warp::header::optional::<u64>("content-length");
+
+ let val = warp::test::request()
+ .filter(&con_len)
+ .await
+ .expect("missing header matches");
+ assert_eq!(val, None);
+
+ let val = warp::test::request()
+ .header("content-length", "5")
+ .filter(&con_len)
+ .await
+ .expect("existing header matches");
+
+ assert_eq!(val, Some(5));
+
+ assert!(
+ !warp::test::request()
+ .header("content-length", "boom")
+ .matches(&con_len)
+ .await,
+ "invalid optional header still rejects",
+ );
+}
diff --git a/third_party/rust/warp/tests/method.rs b/third_party/rust/warp/tests/method.rs
new file mode 100644
index 0000000000..d03126cabd
--- /dev/null
+++ b/third_party/rust/warp/tests/method.rs
@@ -0,0 +1,52 @@
+#![deny(warnings)]
+use warp::Filter;
+
+#[tokio::test]
+async fn method() {
+ let _ = pretty_env_logger::try_init();
+ let get = warp::get().map(warp::reply);
+
+ let req = warp::test::request();
+ assert!(req.matches(&get).await);
+
+ let req = warp::test::request().method("POST");
+ assert!(!req.matches(&get).await);
+
+ let req = warp::test::request().method("POST");
+ let resp = req.reply(&get).await;
+ assert_eq!(resp.status(), 405);
+}
+
+#[tokio::test]
+async fn method_not_allowed_trumps_not_found() {
+ let _ = pretty_env_logger::try_init();
+ let get = warp::get().and(warp::path("hello").map(warp::reply));
+ let post = warp::post().and(warp::path("bye").map(warp::reply));
+
+ let routes = get.or(post);
+
+ let req = warp::test::request().method("GET").path("/bye");
+
+ let resp = req.reply(&routes).await;
+ // GET was allowed, but only for /hello, so POST returning 405 is fine.
+ assert_eq!(resp.status(), 405);
+}
+
+#[tokio::test]
+async fn bad_request_trumps_method_not_allowed() {
+ let _ = pretty_env_logger::try_init();
+ let get = warp::get()
+ .and(warp::path("hello"))
+ .and(warp::header::exact("foo", "bar"))
+ .map(warp::reply);
+ let post = warp::post().and(warp::path("bye")).map(warp::reply);
+
+ let routes = get.or(post);
+
+ let req = warp::test::request().method("GET").path("/hello");
+
+ let resp = req.reply(&routes).await;
+ // GET was allowed, but header rejects with 400, should not
+ // assume POST was the appropriate method.
+ assert_eq!(resp.status(), 400);
+}
diff --git a/third_party/rust/warp/tests/multipart.rs b/third_party/rust/warp/tests/multipart.rs
new file mode 100644
index 0000000000..7702e7361d
--- /dev/null
+++ b/third_party/rust/warp/tests/multipart.rs
@@ -0,0 +1,54 @@
+#![deny(warnings)]
+use bytes::BufMut;
+use futures::{TryFutureExt, TryStreamExt};
+use warp::{multipart, Filter};
+
+#[tokio::test]
+async fn form_fields() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = multipart::form().and_then(|form: multipart::FormData| {
+ async {
+ // Collect the fields into (name, value): (String, Vec<u8>)
+ let part: Result<Vec<(String, Vec<u8>)>, warp::Rejection> = form
+ .and_then(|part| {
+ let name = part.name().to_string();
+ let value = part.stream().try_fold(Vec::new(), |mut vec, data| {
+ vec.put(data);
+ async move { Ok(vec) }
+ });
+ value.map_ok(move |vec| (name, vec))
+ })
+ .try_collect()
+ .await
+ .map_err(|e| {
+ panic!("multipart error: {:?}", e);
+ });
+ part
+ }
+ });
+
+ let boundary = "--abcdef1234--";
+ let body = format!(
+ "\
+ --{0}\r\n\
+ content-disposition: form-data; name=\"foo\"\r\n\r\n\
+ bar\r\n\
+ --{0}--\r\n\
+ ",
+ boundary
+ );
+
+ let req = warp::test::request()
+ .method("POST")
+ .header("content-length", body.len())
+ .header(
+ "content-type",
+ format!("multipart/form-data; boundary={}", boundary),
+ )
+ .body(body);
+
+ let vec = req.filter(&route).await.unwrap();
+ assert_eq!(&vec[0].0, "foo");
+ assert_eq!(&vec[0].1, b"bar");
+}
diff --git a/third_party/rust/warp/tests/path.rs b/third_party/rust/warp/tests/path.rs
new file mode 100644
index 0000000000..5390e51efe
--- /dev/null
+++ b/third_party/rust/warp/tests/path.rs
@@ -0,0 +1,386 @@
+#![deny(warnings)]
+#[macro_use]
+extern crate warp;
+
+use futures::future;
+use warp::Filter;
+
+#[tokio::test]
+async fn path() {
+ let _ = pretty_env_logger::try_init();
+
+ let foo = warp::path("foo");
+ let bar = warp::path(String::from("bar"));
+ let foo_bar = foo.and(bar.clone());
+
+ // /foo
+ let foo_req = || warp::test::request().path("/foo");
+
+ assert!(foo_req().matches(&foo).await);
+ assert!(!foo_req().matches(&bar).await);
+ assert!(!foo_req().matches(&foo_bar).await);
+
+ // /foo/bar
+ let foo_bar_req = || warp::test::request().path("/foo/bar");
+
+ assert!(foo_bar_req().matches(&foo).await);
+ assert!(!foo_bar_req().matches(&bar).await);
+ assert!(foo_bar_req().matches(&foo_bar).await);
+}
+
+#[tokio::test]
+async fn param() {
+ let _ = pretty_env_logger::try_init();
+
+ let num = warp::path::param::<u32>();
+
+ let req = warp::test::request().path("/321");
+ assert_eq!(req.filter(&num).await.unwrap(), 321);
+
+ let s = warp::path::param::<String>();
+
+ let req = warp::test::request().path("/warp");
+ assert_eq!(req.filter(&s).await.unwrap(), "warp");
+
+ // u32 doesn't extract a non-int
+ let req = warp::test::request().path("/warp");
+ assert!(!req.matches(&num).await);
+
+ let combo = num.map(|n| n + 5).and(s);
+
+ let req = warp::test::request().path("/42/vroom");
+ assert_eq!(req.filter(&combo).await.unwrap(), (47, "vroom".to_string()));
+
+ // empty segments never match
+ let req = warp::test::request();
+ assert!(
+ !req.matches(&s).await,
+ "param should never match an empty segment"
+ );
+}
+
+#[tokio::test]
+async fn end() {
+ let _ = pretty_env_logger::try_init();
+
+ let foo = warp::path("foo");
+ let end = warp::path::end();
+ let foo_end = foo.and(end);
+
+ assert!(
+ warp::test::request().path("/").matches(&end).await,
+ "end() matches /"
+ );
+
+ assert!(
+ warp::test::request()
+ .path("http://localhost:1234")
+ .matches(&end)
+ .await,
+ "end() matches /"
+ );
+
+ assert!(
+ warp::test::request()
+ .path("http://localhost:1234?q=2")
+ .matches(&end)
+ .await,
+ "end() matches empty path"
+ );
+
+ assert!(
+ warp::test::request()
+ .path("localhost:1234")
+ .matches(&end)
+ .await,
+ "end() matches authority-form"
+ );
+
+ assert!(
+ !warp::test::request().path("/foo").matches(&end).await,
+ "end() doesn't match /foo"
+ );
+
+ assert!(
+ warp::test::request().path("/foo").matches(&foo_end).await,
+ "path().and(end()) matches /foo"
+ );
+
+ assert!(
+ warp::test::request().path("/foo/").matches(&foo_end).await,
+ "path().and(end()) matches /foo/"
+ );
+}
+
+#[tokio::test]
+async fn tail() {
+ let tail = warp::path::tail();
+
+ // matches full path
+ let ex = warp::test::request()
+ .path("/42/vroom")
+ .filter(&tail)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "42/vroom");
+
+ // matches index
+ let ex = warp::test::request().path("/").filter(&tail).await.unwrap();
+ assert_eq!(ex.as_str(), "");
+
+ // doesn't include query
+ let ex = warp::test::request()
+ .path("/foo/bar?baz=quux")
+ .filter(&tail)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "foo/bar");
+
+ // doesn't include previously matched prefix
+ let and = warp::path("foo").and(tail);
+ let ex = warp::test::request()
+ .path("/foo/bar")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "bar");
+
+ // sets unmatched path index to end
+ let m = tail.and(warp::path("foo"));
+ assert!(!warp::test::request().path("/foo/bar").matches(&m).await);
+
+ let m = tail.and(warp::path::end());
+ assert!(warp::test::request().path("/foo/bar").matches(&m).await);
+}
+
+#[tokio::test]
+async fn or() {
+ let _ = pretty_env_logger::try_init();
+
+ // /foo/bar OR /foo/baz
+ let foo = warp::path("foo");
+ let bar = warp::path("bar");
+ let baz = warp::path("baz");
+ let p = foo.and(bar.or(baz));
+
+ // /foo/bar
+ let req = warp::test::request().path("/foo/bar");
+
+ assert!(req.matches(&p).await);
+
+ // /foo/baz
+ let req = warp::test::request().path("/foo/baz");
+
+ assert!(req.matches(&p).await);
+
+ // deeper nested ORs
+ // /foo/bar/baz OR /foo/baz/bar OR /foo/bar/bar
+ let p = foo
+ .and(bar.and(baz).map(|| panic!("shouldn't match")))
+ .or(foo.and(baz.and(bar)).map(|| panic!("shouldn't match")))
+ .or(foo.and(bar.and(bar)));
+
+ // /foo/baz
+ let req = warp::test::request().path("/foo/baz/baz");
+ assert!(!req.matches(&p).await);
+
+ // /foo/bar/bar
+ let req = warp::test::request().path("/foo/bar/bar");
+ assert!(req.matches(&p).await);
+}
+
+#[tokio::test]
+async fn or_else() {
+ let _ = pretty_env_logger::try_init();
+
+ let foo = warp::path("foo");
+ let bar = warp::path("bar");
+
+ let p = foo.and(bar.or_else(|_| future::ok::<_, std::convert::Infallible>(())));
+
+ // /foo/bar
+ let req = warp::test::request().path("/foo/nope");
+
+ assert!(req.matches(&p).await);
+}
+
+#[tokio::test]
+async fn path_macro() {
+ let _ = pretty_env_logger::try_init();
+
+ let req = warp::test::request().path("/foo/bar");
+ let p = path!("foo" / "bar");
+ assert!(req.matches(&p).await);
+
+ let req = warp::test::request().path("/foo/bar");
+ let p = path!(String / "bar");
+ assert_eq!(req.filter(&p).await.unwrap(), "foo");
+
+ let req = warp::test::request().path("/foo/bar");
+ let p = path!("foo" / String);
+ assert_eq!(req.filter(&p).await.unwrap(), "bar");
+
+ // Requires path end
+
+ let req = warp::test::request().path("/foo/bar/baz");
+ let p = path!("foo" / "bar");
+ assert!(!req.matches(&p).await);
+
+ let req = warp::test::request().path("/foo/bar/baz");
+ let p = path!("foo" / "bar").and(warp::path("baz"));
+ assert!(!req.matches(&p).await);
+
+ // Prefix syntax
+
+ let req = warp::test::request().path("/foo/bar/baz");
+ let p = path!("foo" / "bar" / ..);
+ assert!(req.matches(&p).await);
+
+ let req = warp::test::request().path("/foo/bar/baz");
+ let p = path!("foo" / "bar" / ..).and(warp::path!("baz"));
+ assert!(req.matches(&p).await);
+}
+
+#[tokio::test]
+async fn full_path() {
+ let full_path = warp::path::full();
+
+ let foo = warp::path("foo");
+ let bar = warp::path("bar");
+ let param = warp::path::param::<u32>();
+
+ // matches full request path
+ let ex = warp::test::request()
+ .path("/42/vroom")
+ .filter(&full_path)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/42/vroom");
+
+ // matches index
+ let ex = warp::test::request()
+ .path("/")
+ .filter(&full_path)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/");
+
+ // does not include query
+ let ex = warp::test::request()
+ .path("/foo/bar?baz=quux")
+ .filter(&full_path)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/foo/bar");
+
+ // includes previously matched prefix
+ let and = foo.and(full_path);
+ let ex = warp::test::request()
+ .path("/foo/bar")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/foo/bar");
+
+ // includes following matches
+ let and = full_path.and(foo);
+ let ex = warp::test::request()
+ .path("/foo/bar")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/foo/bar");
+
+ // includes previously matched param
+ let and = foo.and(param).and(full_path);
+ let (_, ex) = warp::test::request()
+ .path("/foo/123")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/foo/123");
+
+ // does not modify matching
+ let m = full_path.and(foo).and(bar);
+ assert!(warp::test::request().path("/foo/bar").matches(&m).await);
+}
+
+#[tokio::test]
+async fn peek() {
+ let peek = warp::path::peek();
+
+ let foo = warp::path("foo");
+ let bar = warp::path("bar");
+ let param = warp::path::param::<u32>();
+
+ // matches full request path
+ let ex = warp::test::request()
+ .path("/42/vroom")
+ .filter(&peek)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "42/vroom");
+
+ // matches index
+ let ex = warp::test::request().path("/").filter(&peek).await.unwrap();
+ assert_eq!(ex.as_str(), "");
+
+ // does not include query
+ let ex = warp::test::request()
+ .path("/foo/bar?baz=quux")
+ .filter(&peek)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "foo/bar");
+
+ // does not include previously matched prefix
+ let and = foo.and(peek);
+ let ex = warp::test::request()
+ .path("/foo/bar")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "bar");
+
+ // includes following matches
+ let and = peek.and(foo);
+ let ex = warp::test::request()
+ .path("/foo/bar")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "foo/bar");
+
+ // does not include previously matched param
+ let and = foo.and(param).and(peek);
+ let (_, ex) = warp::test::request()
+ .path("/foo/123")
+ .filter(&and)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "");
+
+ // does not modify matching
+ let and = peek.and(foo).and(bar);
+ assert!(warp::test::request().path("/foo/bar").matches(&and).await);
+}
+
+#[tokio::test]
+async fn peek_segments() {
+ let peek = warp::path::peek();
+
+ // matches full request path
+ let ex = warp::test::request()
+ .path("/42/vroom")
+ .filter(&peek)
+ .await
+ .unwrap();
+
+ assert_eq!(ex.segments().collect::<Vec<_>>(), &["42", "vroom"]);
+
+ // matches index
+ let ex = warp::test::request().path("/").filter(&peek).await.unwrap();
+
+ let segs = ex.segments().collect::<Vec<_>>();
+ assert_eq!(segs, Vec::<&str>::new());
+}
diff --git a/third_party/rust/warp/tests/query.rs b/third_party/rust/warp/tests/query.rs
new file mode 100644
index 0000000000..c93885acb5
--- /dev/null
+++ b/third_party/rust/warp/tests/query.rs
@@ -0,0 +1,123 @@
+#![deny(warnings)]
+
+use serde_derive::Deserialize;
+use std::collections::HashMap;
+use warp::Filter;
+
+#[tokio::test]
+async fn query() {
+ let as_map = warp::query::<HashMap<String, String>>();
+
+ let req = warp::test::request().path("/?foo=bar&baz=quux");
+
+ let extracted = req.filter(&as_map).await.unwrap();
+ assert_eq!(extracted["foo"], "bar");
+ assert_eq!(extracted["baz"], "quux");
+}
+
+#[tokio::test]
+async fn query_struct() {
+ let as_struct = warp::query::<MyArgs>();
+
+ let req = warp::test::request().path("/?foo=bar&baz=quux");
+
+ let extracted = req.filter(&as_struct).await.unwrap();
+ assert_eq!(
+ extracted,
+ MyArgs {
+ foo: Some("bar".into()),
+ baz: Some("quux".into())
+ }
+ );
+}
+
+#[tokio::test]
+async fn empty_query_struct() {
+ let as_struct = warp::query::<MyArgs>();
+
+ let req = warp::test::request().path("/?");
+
+ let extracted = req.filter(&as_struct).await.unwrap();
+ assert_eq!(
+ extracted,
+ MyArgs {
+ foo: None,
+ baz: None
+ }
+ );
+}
+
+#[tokio::test]
+async fn missing_query_struct() {
+ let as_struct = warp::query::<MyArgs>();
+
+ let req = warp::test::request().path("/");
+
+ let extracted = req.filter(&as_struct).await.unwrap();
+ assert_eq!(
+ extracted,
+ MyArgs {
+ foo: None,
+ baz: None
+ }
+ );
+}
+
+#[derive(Deserialize, Debug, Eq, PartialEq)]
+struct MyArgs {
+ foo: Option<String>,
+ baz: Option<String>,
+}
+
+#[tokio::test]
+async fn required_query_struct() {
+ let as_struct = warp::query::<MyRequiredArgs>();
+
+ let req = warp::test::request().path("/?foo=bar&baz=quux");
+
+ let extracted = req.filter(&as_struct).await.unwrap();
+ assert_eq!(
+ extracted,
+ MyRequiredArgs {
+ foo: "bar".into(),
+ baz: "quux".into()
+ }
+ );
+}
+
+#[tokio::test]
+async fn missing_required_query_struct_partial() {
+ let as_struct = warp::query::<MyRequiredArgs>();
+
+ let req = warp::test::request().path("/?foo=something");
+
+ let extracted = req.filter(&as_struct).await;
+ assert!(extracted.is_err())
+}
+
+#[tokio::test]
+async fn missing_required_query_struct_no_query() {
+ let as_struct = warp::query::<MyRequiredArgs>().map(|_| warp::reply());
+
+ let req = warp::test::request().path("/");
+
+ let res = req.reply(&as_struct).await;
+ assert_eq!(res.status(), 400);
+ assert_eq!(res.body(), "Invalid query string");
+}
+
+#[derive(Deserialize, Debug, Eq, PartialEq)]
+struct MyRequiredArgs {
+ foo: String,
+ baz: String,
+}
+
+#[tokio::test]
+async fn raw_query() {
+ let as_raw = warp::query::raw();
+
+ let req = warp::test::request().path("/?foo=bar&baz=quux");
+
+ let extracted = req.filter(&as_raw).await.unwrap();
+ assert_eq!(extracted, "foo=bar&baz=quux".to_owned());
+}
diff --git a/third_party/rust/warp/tests/redirect.rs b/third_party/rust/warp/tests/redirect.rs
new file mode 100644
index 0000000000..5754a9cdf6
--- /dev/null
+++ b/third_party/rust/warp/tests/redirect.rs
@@ -0,0 +1,13 @@
+#![deny(warnings)]
+use warp::{http::Uri, Filter};
+
+#[tokio::test]
+async fn redirect_uri() {
+ let over_there = warp::any().map(|| warp::redirect(Uri::from_static("/over-there")));
+
+ let req = warp::test::request();
+ let resp = req.reply(&over_there).await;
+
+ assert_eq!(resp.status(), 301);
+ assert_eq!(resp.headers()["location"], "/over-there");
+}
diff --git a/third_party/rust/warp/tests/reply_with.rs b/third_party/rust/warp/tests/reply_with.rs
new file mode 100644
index 0000000000..0766924b41
--- /dev/null
+++ b/third_party/rust/warp/tests/reply_with.rs
@@ -0,0 +1,64 @@
+#![deny(warnings)]
+use warp::http::header::{HeaderMap, HeaderValue};
+use warp::Filter;
+
+#[tokio::test]
+async fn header() {
+ let header = warp::reply::with::header("foo", "bar");
+
+ let no_header = warp::any().map(warp::reply).with(&header);
+
+ let req = warp::test::request();
+ let resp = req.reply(&no_header).await;
+ assert_eq!(resp.headers()["foo"], "bar");
+
+ let prev_header = warp::reply::with::header("foo", "sean");
+ let yes_header = warp::any().map(warp::reply).with(prev_header).with(header);
+
+ let req = warp::test::request();
+ let resp = req.reply(&yes_header).await;
+ assert_eq!(resp.headers()["foo"], "bar", "replaces header");
+}
+
+#[tokio::test]
+async fn headers() {
+ let mut headers = HeaderMap::new();
+ headers.insert("server", HeaderValue::from_static("warp"));
+ headers.insert("foo", HeaderValue::from_static("bar"));
+
+ let headers = warp::reply::with::headers(headers);
+
+ let no_header = warp::any().map(warp::reply).with(&headers);
+
+ let req = warp::test::request();
+ let resp = req.reply(&no_header).await;
+ assert_eq!(resp.headers()["foo"], "bar");
+ assert_eq!(resp.headers()["server"], "warp");
+
+ let prev_header = warp::reply::with::header("foo", "sean");
+ let yes_header = warp::any().map(warp::reply).with(prev_header).with(headers);
+
+ let req = warp::test::request();
+ let resp = req.reply(&yes_header).await;
+ assert_eq!(resp.headers()["foo"], "bar", "replaces header");
+}
+
+#[tokio::test]
+async fn default_header() {
+ let def_header = warp::reply::with::default_header("foo", "bar");
+
+ let no_header = warp::any().map(warp::reply).with(&def_header);
+
+ let req = warp::test::request();
+ let resp = req.reply(&no_header).await;
+
+ assert_eq!(resp.headers()["foo"], "bar");
+
+ let header = warp::reply::with::header("foo", "sean");
+ let yes_header = warp::any().map(warp::reply).with(header).with(def_header);
+
+ let req = warp::test::request();
+ let resp = req.reply(&yes_header).await;
+
+ assert_eq!(resp.headers()["foo"], "sean", "doesn't replace header");
+}
diff --git a/third_party/rust/warp/tests/ws.rs b/third_party/rust/warp/tests/ws.rs
new file mode 100644
index 0000000000..b53fd2f16f
--- /dev/null
+++ b/third_party/rust/warp/tests/ws.rs
@@ -0,0 +1,182 @@
+#![deny(warnings)]
+
+use futures::{FutureExt, SinkExt, StreamExt};
+use warp::ws::Message;
+use warp::Filter;
+
+#[tokio::test]
+async fn upgrade() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = warp::ws().map(|ws: warp::ws::Ws| ws.on_upgrade(|_| async {}));
+
+ // From https://tools.ietf.org/html/rfc6455#section-1.2
+ let key = "dGhlIHNhbXBsZSBub25jZQ==";
+ let accept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=";
+
+ let resp = warp::test::request()
+ .header("connection", "upgrade")
+ .header("upgrade", "websocket")
+ .header("sec-websocket-version", "13")
+ .header("sec-websocket-key", key)
+ .reply(&route)
+ .await;
+
+ assert_eq!(resp.status(), 101);
+ assert_eq!(resp.headers()["connection"], "upgrade");
+ assert_eq!(resp.headers()["upgrade"], "websocket");
+ assert_eq!(resp.headers()["sec-websocket-accept"], accept);
+
+ let resp = warp::test::request()
+ .header("connection", "keep-alive, Upgrade")
+ .header("upgrade", "Websocket")
+ .header("sec-websocket-version", "13")
+ .header("sec-websocket-key", key)
+ .reply(&route)
+ .await;
+
+ assert_eq!(resp.status(), 101);
+}
+
+#[tokio::test]
+async fn fail() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = warp::any().map(warp::reply);
+
+ warp::test::ws()
+ .handshake(route)
+ .await
+ .expect_err("handshake non-websocket route should fail");
+}
+
+#[tokio::test]
+async fn text() {
+ let _ = pretty_env_logger::try_init();
+
+ let mut client = warp::test::ws()
+ .handshake(ws_echo())
+ .await
+ .expect("handshake");
+
+ client.send_text("hello warp").await;
+
+ let msg = client.recv().await.expect("recv");
+ assert_eq!(msg.to_str(), Ok("hello warp"));
+}
+
+#[tokio::test]
+async fn binary() {
+ let _ = pretty_env_logger::try_init();
+
+ let mut client = warp::test::ws()
+ .handshake(ws_echo())
+ .await
+ .expect("handshake");
+
+ client.send(warp::ws::Message::binary(&b"bonk"[..])).await;
+ let msg = client.recv().await.expect("recv");
+ assert!(msg.is_binary());
+ assert_eq!(msg.as_bytes(), &b"bonk"[..]);
+}
+
+#[tokio::test]
+async fn send_ping() {
+ let _ = pretty_env_logger::try_init();
+
+ let filter = warp::ws().map(|ws: warp::ws::Ws| {
+ ws.on_upgrade(|mut websocket| {
+ async move {
+ websocket.send(Message::ping("srv")).await.unwrap();
+ // assume the client will pong back
+ let msg = websocket.next().await.expect("item").expect("ok");
+ assert!(msg.is_pong());
+ assert_eq!(msg.as_bytes(), &b"srv"[..]);
+ }
+ })
+ });
+
+ let mut client = warp::test::ws().handshake(filter).await.expect("handshake");
+
+ let msg = client.recv().await.expect("recv");
+ assert!(msg.is_ping());
+ assert_eq!(msg.as_bytes(), &b"srv"[..]);
+
+ client.recv_closed().await.expect("closed");
+}
+
+#[tokio::test]
+async fn echo_pings() {
+ let _ = pretty_env_logger::try_init();
+
+ let mut client = warp::test::ws()
+ .handshake(ws_echo())
+ .await
+ .expect("handshake");
+
+ client.send(Message::ping("clt")).await;
+
+ // tungstenite sends the PONG first
+ let msg = client.recv().await.expect("recv");
+ assert!(msg.is_pong());
+ assert_eq!(msg.as_bytes(), &b"clt"[..]);
+
+ // and then `ws_echo` sends us back the same PING
+ let msg = client.recv().await.expect("recv");
+ assert!(msg.is_ping());
+ assert_eq!(msg.as_bytes(), &b"clt"[..]);
+
+ // and then our client would have sent *its* PONG
+ // and `ws_echo` would send *that* back too
+ let msg = client.recv().await.expect("recv");
+ assert!(msg.is_pong());
+ assert_eq!(msg.as_bytes(), &b"clt"[..]);
+}
+
+#[tokio::test]
+async fn closed() {
+ let _ = pretty_env_logger::try_init();
+
+ let route =
+ warp::ws().map(|ws: warp::ws::Ws| ws.on_upgrade(|websocket| websocket.close().map(|_| ())));
+
+ let mut client = warp::test::ws().handshake(route).await.expect("handshake");
+
+ client.recv_closed().await.expect("closed");
+}
+
+#[tokio::test]
+async fn limit_message_size() {
+ let _ = pretty_env_logger::try_init();
+
+ let echo = warp::ws().map(|ws: warp::ws::Ws| {
+ ws.max_message_size(1024).on_upgrade(|websocket| {
+ // Just echo all messages back...
+ let (tx, rx) = websocket.split();
+ rx.forward(tx).map(|result| {
+ assert!(result.is_err());
+ assert_eq!(
+ format!("{}", result.unwrap_err()).as_str(),
+ "Space limit exceeded: Message too big: 0 + 1025 > 1024"
+ );
+ })
+ })
+ });
+ let mut client = warp::test::ws().handshake(echo).await.expect("handshake");
+
+ client.send(warp::ws::Message::binary(vec![0; 1025])).await;
+ client.send_text("hello warp").await;
+ assert!(client.recv().await.is_err());
+}
+
+fn ws_echo() -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Copy {
+ warp::ws().map(|ws: warp::ws::Ws| {
+ ws.on_upgrade(|websocket| {
+ // Just echo all messages back...
+ let (tx, rx) = websocket.split();
+ rx.inspect(|i| log::debug!("ws recv: {:?}", i))
+ .forward(tx)
+ .map(|_| ())
+ })
+ })
+}