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/.github/FUNDING.yml1
-rw-r--r--third_party/rust/warp/.github/ISSUE_TEMPLATE/bug_report.md36
-rw-r--r--third_party/rust/warp/.github/ISSUE_TEMPLATE/config.yml6
-rw-r--r--third_party/rust/warp/.github/ISSUE_TEMPLATE/feature_request.md20
-rw-r--r--third_party/rust/warp/.github/workflows/ci.yml97
-rw-r--r--third_party/rust/warp/CHANGELOG.md324
-rw-r--r--third_party/rust/warp/Cargo.toml185
-rw-r--r--third_party/rust/warp/LICENSE20
-rw-r--r--third_party/rust/warp/README.md63
-rw-r--r--third_party/rust/warp/examples/README.md68
-rw-r--r--third_party/rust/warp/examples/autoreload.rs42
-rw-r--r--third_party/rust/warp/examples/body.rs30
-rw-r--r--third_party/rust/warp/examples/compression.rs34
-rw-r--r--third_party/rust/warp/examples/custom_methods.rs61
-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/query_string.rs59
-rw-r--r--third_party/rust/warp/examples/rejections.rs122
-rw-r--r--third_party/rust/warp/examples/returning.rs20
-rw-r--r--third_party/rust/warp/examples/routing.rs104
-rw-r--r--third_party/rust/warp/examples/sse.rs31
-rw-r--r--third_party/rust/warp/examples/sse_chat.rs163
-rw-r--r--third_party/rust/warp/examples/tls.rs25
-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.rs291
-rw-r--r--third_party/rust/warp/examples/tracing.rs59
-rw-r--r--third_party/rust/warp/examples/unix_socket.rs22
-rw-r--r--third_party/rust/warp/examples/websockets.rs27
-rw-r--r--third_party/rust/warp/examples/websockets_chat.rs175
-rw-r--r--third_party/rust/warp/examples/wrapping.rs31
-rw-r--r--third_party/rust/warp/src/error.rs79
-rw-r--r--third_party/rust/warp/src/filter/and.rs97
-rw-r--r--third_party/rust/warp/src/filter/and_then.rs110
-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.rs492
-rw-r--r--third_party/rust/warp/src/filter/or.rs110
-rw-r--r--third_party/rust/warp/src/filter/or_else.rs107
-rw-r--r--third_party/rust/warp/src/filter/recover.rs117
-rw-r--r--third_party/rust/warp/src/filter/service.rs137
-rw-r--r--third_party/rust/warp/src/filter/then.rs95
-rw-r--r--third_party/rust/warp/src/filter/unify.rs50
-rw-r--r--third_party/rust/warp/src/filter/untuple_one.rs52
-rw-r--r--third_party/rust/warp/src/filter/wrap.rs67
-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.rs345
-rw-r--r--third_party/rust/warp/src/filters/compression.rs292
-rw-r--r--third_party/rust/warp/src/filters/cookie.rs46
-rw-r--r--third_party/rust/warp/src/filters/cors.rs626
-rw-r--r--third_party/rust/warp/src/filters/ext.rs36
-rw-r--r--third_party/rust/warp/src/filters/fs.rs539
-rw-r--r--third_party/rust/warp/src/filters/header.rs230
-rw-r--r--third_party/rust/warp/src/filters/host.rs96
-rw-r--r--third_party/rust/warp/src/filters/log.rs280
-rw-r--r--third_party/rust/warp/src/filters/method.rs150
-rw-r--r--third_party/rust/warp/src/filters/mod.rs29
-rw-r--r--third_party/rust/warp/src/filters/multipart.rs190
-rw-r--r--third_party/rust/warp/src/filters/path.rs652
-rw-r--r--third_party/rust/warp/src/filters/query.rs91
-rw-r--r--third_party/rust/warp/src/filters/reply.rs257
-rw-r--r--third_party/rust/warp/src/filters/sse.rs511
-rw-r--r--third_party/rust/warp/src/filters/trace.rs309
-rw-r--r--third_party/rust/warp/src/filters/ws.rs410
-rw-r--r--third_party/rust/warp/src/generic.rs250
-rw-r--r--third_party/rust/warp/src/lib.rs179
-rw-r--r--third_party/rust/warp/src/redirect.rs132
-rw-r--r--third_party/rust/warp/src/reject.rs844
-rw-r--r--third_party/rust/warp/src/reply.rs584
-rw-r--r--third_party/rust/warp/src/route.rs140
-rw-r--r--third_party/rust/warp/src/server.rs576
-rw-r--r--third_party/rust/warp/src/service.rs3
-rw-r--r--third_party/rust/warp/src/test.rs764
-rw-r--r--third_party/rust/warp/src/tls.rs411
-rw-r--r--third_party/rust/warp/src/transport.rs53
-rw-r--r--third_party/rust/warp/tests/addr.rs24
-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.rs271
-rw-r--r--third_party/rust/warp/tests/header.rs71
-rw-r--r--third_party/rust/warp/tests/host.rs87
-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.rs411
-rw-r--r--third_party/rust/warp/tests/query.rs139
-rw-r--r--third_party/rust/warp/tests/redirect.rs57
-rw-r--r--third_party/rust/warp/tests/reply_with.rs64
-rw-r--r--third_party/rust/warp/tests/tracing.rs48
-rw-r--r--third_party/rust/warp/tests/ws.rs288
102 files changed, 15413 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..2c8f120eee
--- /dev/null
+++ b/third_party/rust/warp/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{".github/FUNDING.yml":"e65635279e36972a4cecce26f3bb8115c09fd6d5d3e3e79d11a973877d4600a8",".github/ISSUE_TEMPLATE/bug_report.md":"2d4a83ec915a1de017f74266da90c71bb522c8f3c16f13d19c6f60b214461ca7",".github/ISSUE_TEMPLATE/config.yml":"ca96483f1f9c2a83e2beb3025f6b9965928f4ce0774b36f38cd9e55c5cd7c2f2",".github/ISSUE_TEMPLATE/feature_request.md":"aa20f06cc398fde7b9ce6be01f4d39f7c5b6e8efbaf40b229983f994d5f5e49e",".github/workflows/ci.yml":"f40e040de6bc5d78c3e60102d3aaf9551f5c8e8eea24e129aed544a20500595e","CHANGELOG.md":"61f8420ef01d4b6d88ed4768cb1ed368a74107499b112f7c12e30bf66089e8f1","Cargo.toml":"c297b5e2bbf7319954bf15f36e97c22a96649d40c746ee849cefd17b4deb3df0","LICENSE":"f9e46cb84d28dce0b6c08e84496cde893896a2816f4ffaac23e6552b7daf20f0","README.md":"d0cb27a8b2be3f852a5d1e0dca6b114a204936ad4ba76c7ec7424f4b0c88a1c3","examples/README.md":"aa8613c11e1528037a4b2517c8261eaee531f2d0a109bd9784a3f2633e0147b8","examples/autoreload.rs":"c4ca42f4e44917e69da0ee27553d8ba159c0bc50b9930d30caa07a3bb945e721","examples/body.rs":"57eb99b3a59c33474c5d5c7c22964bea66c663f525c755924ed75eca8fba4aa1","examples/compression.rs":"3bc90f12200fed9374cdd7f23f2adae770d8a283740e3441731571dbb5e2217f","examples/custom_methods.rs":"40cb0d2cf0ec43f55bd61863ec552d526ca7d94195624d4d19e789412a85b17c","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":"d5f8a58de075663f20686590837d6dea4355df754e9c1425caaf1c5547b77f9d","examples/handlebars_template.rs":"cf13236fbe9ad294902c0da0c21eda58f9e498b9423ab2a34d54052102a859ba","examples/headers.rs":"651cd6425219d1d09cfb4f1114fefc2dff4435070b90f7c1d1696c95c96913b5","examples/hello.rs":"9ca9f197e2365c21729bbe74fe07590c615aaf87f07aece51b5640c97a453196","examples/query_string.rs":"be67abd54d67e8a16406957360b33349f4880cd45953b879e63fa6fe2a440515","examples/rejections.rs":"a6b811a70378b245afa9179d5829553e1ae2a0ec3fe6ec7288058a1aa789e1c1","examples/returning.rs":"e19e8df9ec34d7eeb60f6053e5b1f51043159dccb7f5e7148b69597eff2061ea","examples/routing.rs":"7663edfff0bf04d4314453570aa7427dd0a5ffcfb3f4a116852afb6c2d13af76","examples/sse.rs":"d1bebebd1403d601d23231cf9520fdf565aabde8352698675966877d27911952","examples/sse_chat.rs":"9455fc09f7808cc9581d194347186c0447090df9a793f3780c75912ba84a42b6","examples/tls.rs":"4c704782caa31e6b147b0fe969935ab9ea256fad52e74093fb77ef9c4f32902f","examples/tls/cert.pem":"6b974e5654edca3664916613c9f932a64990cff3f604df5369275c35f64d36a2","examples/tls/key.rsa":"ffe615f53e98682bc470ae6fa964ce34a383b4509ae7bb9a7ee2ece1b1bdc7e3","examples/todos.rs":"5d208cfb13cafd482c238fdec54666714717f8ded7d3c248038f166beee6a264","examples/tracing.rs":"2d47c96e4ac041dfd6c9b6d2a17fc9af1bd72b69c405d184ec07085491c6ac0e","examples/unix_socket.rs":"9bebb31119df075001dcdd51a2be6ad5c1d94741b5814d0016e3ed7229154f14","examples/websockets.rs":"d7eeb5dd7b936248d7fb3fd56dc4d51aa8a07d7403a82bd11b4a69d7c31d3672","examples/websockets_chat.rs":"42c281d3c189416ab54d34253e2d93bcb3f19c0d93d30feb9b372c5f9433a1dd","examples/wrapping.rs":"c0ad14ddbdd531023df48a96ddcf680eecb741a941c816cca6c6796de9ff146f","src/error.rs":"a3b8f7ba1bb3353c0894c9bb63edd38525a120067135775b650e5bfc36046e03","src/filter/and.rs":"519cef248f5e7166bb62e0bdb3ef5194997b8a9b3fa8ded057748639e0da5de5","src/filter/and_then.rs":"f3903d6473483355d010b2eeafc965d2998bdaaa0a47c090dc7681710ec2e4fb","src/filter/boxed.rs":"9f41f214da36b673ba6234cfccba597acafbb40a02181304f9f379dc7d7643b1","src/filter/map.rs":"7b239aaf5dc129b380530a338d091bb68ad336cfcb74d89a2cbe1ea2560ab155","src/filter/map_err.rs":"37b4a0c61de36a531e6b1fef588ce87b17d2d312ace010fb4ecd2406d4936231","src/filter/mod.rs":"56bb18280f7ec89a824a5214458c2e6c5f90573c6327f096aa239bf49e9e7496","src/filter/or.rs":"0bc2d2c6dad60fc0f60609cd884db08e3a468de5d08f068b87221def3e353142","src/filter/or_else.rs":"d1c18b3d09c8d71f6dbbf1f70dba26ec623222a5797bcbb2bef49239f2447b01","src/filter/recover.rs":"9ba28ad03861b57eee7c6ecb53ad57dfc8c9f1bf321fcc5c7f0a9c3bd07c04bb","src/filter/service.rs":"8f2ba4e414655e4800f85daa090ba0f4758943ede23ab0ab09c068608d4fe14b","src/filter/then.rs":"d5c568c47cba20f8683908a194f9451ff4265f4c76225452decb155d522e8b14","src/filter/unify.rs":"41616e079082f959444c05325176f5d761850b023a73601ff259d6a897247b9a","src/filter/untuple_one.rs":"c88f292545646ad7bd11b493b24ad1e51a926bfbe45998281e735fa133dbe6aa","src/filter/wrap.rs":"a5783e8451db2818da5a7b010390d5d77865590fcbb3a2a6ad0a1f5f4c158fae","src/filters/addr.rs":"52d3336a046620e645feb2e15cb743418889a3a7d5d5337ac5cd806dd0ade4ed","src/filters/any.rs":"ad57b333abad579c3822338bd44035f44902e5a9bbf167fe44f057cffb723f07","src/filters/body.rs":"565758830fa5accc63825f32d80e87e70d2b7a0f7eb3ad57863b97c4565fde9e","src/filters/compression.rs":"2e6428e546791f332be8fcb42b6a4c7df2c389f4d002848a206a0f0ee2ed5cd1","src/filters/cookie.rs":"09082d991cf6a7fcf372dd83611234d1250711f1ff2e5d6392b7241820a6fc95","src/filters/cors.rs":"7fd739d8dea6a3e7f0b2c8640907ec4a01142f972933065eb41244f1638ac6b7","src/filters/ext.rs":"76dd310d51d29fa2531a21a947bfdb7fda744b9a71fd8c8104e428f1f6b8f735","src/filters/fs.rs":"3f047ad611e347e031ef00c7ee4da6d8a0b1366362256a5c922cecaaab341764","src/filters/header.rs":"b02e37f47ffa86a53097127759a16fdbc0e09c5d02f961a15f5972a59a96fae9","src/filters/host.rs":"5cb5e207e934173b3299a30f427c19ae112dbd7613a770ceeda51ba0f38d845a","src/filters/log.rs":"29e0e243e0084b7d612b05369eb48482e957a207d8c651629bf217d05f747beb","src/filters/method.rs":"0a64e1c1c14ce82511cd99a489ff13eebaaacce04394a5eb282e5c43ff5a6e92","src/filters/mod.rs":"bd6d0af8fec10cf1cbf120a1dd4b271a129bfb09d4071526e865cb9e2d619a8b","src/filters/multipart.rs":"386b3c1ce184e702d47a480bb374fbf5eb48fb8942d2e80ece17e6e4083cf453","src/filters/path.rs":"d087f6f2f7cb869721a66ab16a5d52d69a0312a28ba4b349775a3f810b6a7e48","src/filters/query.rs":"7985b040b6f38252355d2d369b384d66bb4b3978012c47f2105d0b62e5221d3b","src/filters/reply.rs":"6102b63e142dfe2c55bdd37f1b3e9d25852a1ed7f1ec2ec4fec91f478cf672a4","src/filters/sse.rs":"7ce9362323a2d7a481fc72f429c5a45e433505645d29aa7672a21a21727173cf","src/filters/trace.rs":"c037cf03740d00fe6c67ad47f517452d242b623e8bb1a3ad5625e2f4009ecd04","src/filters/ws.rs":"8e0d8d5d3eead2676e64135f6f80af64c8dc2c07e2824f9f6b4c72da1094cff3","src/generic.rs":"a7afd6804059c16c4397028c85120bf1b29ce86bbfa134e3670291764a3ef257","src/lib.rs":"87cb3bec87439a1e90e4bd6bd5b86008c5334fe6e3bd616584ae9fe03cd51d5b","src/redirect.rs":"ab104a9c48e08ffeb93bff6c5e81174a378f6ee98b2dadd61e038641da5cf976","src/reject.rs":"63f56d59a0c530cea2dfbdf30838b15350bb5e460ae97a3559d74ee34cd9659d","src/reply.rs":"b45d7740903a47254d23ba23e1c72d1ed578515737a292a61ed7b03fba6976c0","src/route.rs":"1a246b76d481ac0184511d342ca0dae0754384221ef37445bcee28ee7cd40b06","src/server.rs":"f06cab83c7fe242a095df41f932c7f2916e44434085d7d0dd3b1a3d8b136a3fc","src/service.rs":"4564ec95e98a2314f73df24582ca8f6ec96cc8eda90fb5a5d1d83a9d5c841b86","src/test.rs":"b68c006fe0b5fc1f2f3ebef3e4c3423588fcad4c12c16cee6245c82e09fb0b30","src/tls.rs":"20029156fea45d6e090ccf5c18f24434d1b3541880470bf6147ed0c9197df2d8","src/transport.rs":"6940d28bd3b1720e6e86676d46ace2855c78cd302ef167bad955f5367b022a67","tests/addr.rs":"2946596c8c5eb71dbb7339492d1805d12b5f9941b9d855c3dc6bd17597687296","tests/body.rs":"595e25b2aabbdd6619da44a4b8cbb5c4f580c56450e7c614b8ac197bcbe32a29","tests/cookie.rs":"1bb5cdacddca5dc7028796d06bd1b7bf599bc81759a904215b61beda735bf2d1","tests/cors.rs":"9ee68d8212fdbd171bfa2636466db425cf7d7de32b6afdf387b2ba21569aca4f","tests/ext.rs":"93d6527288f71ee20b63f6a47f616f055735373b0f203f0863f27c2b65fd8991","tests/filter.rs":"76c05031f1e0d6271ff2ac7eb596b4c97ffd0fe93d4d49af4783cc4d862dae25","tests/fs.rs":"2656bdcfe76f90e30e044c6c464028e96d475464bee8acb2d5c46a3b76ac03fe","tests/header.rs":"78d7b4fd80025694cde65010cf04e4cc23ad4ac91fff3cb527542c278e6cfe4b","tests/host.rs":"712f883c133041ead3cec66379e85279547189ed9ac366796732cabf486114a4","tests/method.rs":"6ae1f188b06b07822bbd97f671886259ee8e7008313449ec04604c8f396cf59b","tests/multipart.rs":"03e366749f4dae82ea864d3da795d940163ba7c2b2214a1db36f66b3f4b9d655","tests/path.rs":"db1884d961d83a624f7c63c8de09d9196f10a21a11bb43005e2015aee68d7ccf","tests/query.rs":"993133adc0e47eea4defea60dbca5365ad54a7f49069a1b423b9642d9fda6e3b","tests/redirect.rs":"a292860964a3ad2edca4f43d1b462226a4ca4537a71aae31520857dcb662b33d","tests/reply_with.rs":"35fdfe9653ffab0776fe9fb65e231f9ea647c9f391b17794010adbcbd5009e65","tests/tracing.rs":"f5223781843d97f78e598ae580234567691afca38952b07cac161597749f6332","tests/ws.rs":"b130f4a58198641c0853269f1e21e1b1ba7acba10b114161d499881740621f8a"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/warp/.github/FUNDING.yml b/third_party/rust/warp/.github/FUNDING.yml
new file mode 100644
index 0000000000..a6b3376dae
--- /dev/null
+++ b/third_party/rust/warp/.github/FUNDING.yml
@@ -0,0 +1 @@
+github: [seanmonstar]
diff --git a/third_party/rust/warp/.github/ISSUE_TEMPLATE/bug_report.md b/third_party/rust/warp/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000000..34ee804cb6
--- /dev/null
+++ b/third_party/rust/warp/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,36 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: bug
+assignees: ''
+
+---
+
+**Version**
+List the versions of all `warp` crates you are using. The easiest way to get
+this information is using `cargo-tree`.
+
+`cargo install cargo-tree`
+(see install here: https://github.com/sfackler/cargo-tree)
+
+Then:
+
+`cargo tree | grep warp`
+
+**Platform**
+The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
+
+**Description**
+Enter your issue details here.
+One way to structure the description:
+
+[short summary of the bug]
+
+I tried this code:
+
+[code sample that causes the bug]
+
+I expected to see this happen: [explanation]
+
+Instead, this happened: [explanation]
diff --git a/third_party/rust/warp/.github/ISSUE_TEMPLATE/config.yml b/third_party/rust/warp/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000000..be346fecb4
--- /dev/null
+++ b/third_party/rust/warp/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,6 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Question
+ url: https://discord.gg/RFsPjyt
+ about: 'Please post your question on the #warp discord channel. You may
+ also be able to find help at https://users.rust-lang.org/.'
diff --git a/third_party/rust/warp/.github/ISSUE_TEMPLATE/feature_request.md b/third_party/rust/warp/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000000..4fe86d5ec8
--- /dev/null
+++ b/third_party/rust/warp/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: feature
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/third_party/rust/warp/.github/workflows/ci.yml b/third_party/rust/warp/.github/workflows/ci.yml
new file mode 100644
index 0000000000..7a1cd98a24
--- /dev/null
+++ b/third_party/rust/warp/.github/workflows/ci.yml
@@ -0,0 +1,97 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+
+jobs:
+ style:
+ name: Check Style
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Install rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: stable
+ components: rustfmt
+ profile: minimal
+ override: true
+
+ - name: cargo fmt -- --check
+ uses: actions-rs/cargo@v1
+ with:
+ command: fmt
+ args: --all -- --check
+
+ test:
+ name: Test
+ needs: [style]
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ build: [stable, beta, nightly, tls, no-default-features, compression]
+
+ include:
+ - build: beta
+ rust: beta
+ - build: nightly
+ rust: nightly
+ benches: true
+ - build: tls
+ features: "--features tls"
+ - build: no-default-features
+ features: "--no-default-features"
+ - build: compression
+ features: "--features compression"
+
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Install rust
+ uses: actions-rs/toolchain@v1
+ with:
+ toolchain: ${{ matrix.rust || 'stable' }}
+ profile: minimal
+ override: true
+
+ - name: Test
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: ${{ matrix.features }}
+
+ - name: Test all benches
+ if: matrix.benches
+ uses: actions-rs/cargo@v1
+ with:
+ command: test
+ args: --benches ${{ matrix.features }}
+
+ doc:
+ name: Build docs
+ needs: [style, test]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v1
+
+ - name: Install Rust
+ uses: actions-rs/toolchain@v1
+ with:
+ profile: minimal
+ toolchain: nightly
+ override: true
+
+ - name: cargo doc
+ uses: actions-rs/cargo@v1
+ with:
+ command: rustdoc
+ args: -- -D broken_intra_doc_links
diff --git a/third_party/rust/warp/CHANGELOG.md b/third_party/rust/warp/CHANGELOG.md
new file mode 100644
index 0000000000..dbf80b8795
--- /dev/null
+++ b/third_party/rust/warp/CHANGELOG.md
@@ -0,0 +1,324 @@
+### v0.3.3 (September 27, 2022)
+
+- **Fixes**:
+ - Fix `fs` filters path sanitization to reject colons on Windows.
+
+### v0.3.2 (November 9, 2021)
+
+- **Features**:
+ - Add `Filter::then()`, which is like `Filter::map()` in that it's infallible, but is async like `Filter::and_then()`.
+ - Add `redirect::found()` reply helper that returns `302 Found`.
+ - Add `compression-brotli` and `compression-gzip` cargo features to enable only the compression you need.
+ - Allow `HEAD` requests to be served to `fs::dir()` filters.
+ - Allow `path!()` with no arguments.
+- **Fixes**:
+ - Update private dependencies Tungstenite and Multipart.
+ - Replaces uses of `futures` with `futures-util`, which is a smaller dependency.
+
+
+### v0.3.1 (March 24, 2021)
+
+- **Features**:
+ - Add `pong` constructor to websocket messages.
+ - Add `redirect::see_other` and `redirect::permanent` helpers.
+- **Fixes**:
+ - Fix `fs` filters sometimes having an off-by-one error with range requests.
+ - Fix CORS to allow spaces when checking `Access-Control-Request-Headers`.
+
+## v0.3.0 (January 19, 2021)
+
+- **Features**:
+ - Add TLS client authentication support.
+ - Add TLS OCSP stapling support.
+ - Add `From<Reject>` for `Rejection`.
+ - Add `close_frame` accessor to `ws::Message`.
+- **Changes**:
+ - Update to Tokio v1.
+ - Update to Bytes v1.
+ - Update to hyper v0.14.
+ - Rework `sse` filter to be more like `ws`, with a single `Event` type and builder.
+ - Change `cookie` filter to extract a generic `FromStr` value.
+
+
+### v0.2.5 (August 31, 2020)
+
+- **Features**:
+ - Add `wrap_fn`, which can be used to create a `Wrap` from a closure. These in turn are used with `Filter::with()`.
+ - Add `warp::host` filters to deal with `Host`/`:authority` headers.
+ - Relax some lifetime bounds on `Server`.
+- **Fixes**:
+ - Fix panic when URI doesn't have a slash (for example, `CONNECT foo.bar`).
+
+### v0.2.4 (July 20, 2020)
+
+- **Features**:
+ - Add `tracing` internals in place of `log` (log is still emitted for backwards compatibility).
+ - Add `warp::trace` module set of filters to customize `tracing` dianostics.
+ - Add `path` method to `warp::fs::File` reply.
+ - Add `source` implementation for `BodyDeserializeError`.
+ - Make `warp::ws::MissingConnectionUpgrade` rejection public.
+
+### v0.2.3 (May 19, 2020)
+
+- **Features**:
+ - Add `warp::compression` filters, which will compress response bodies.
+ - Add `warp::header::value()` filter to get a request `HeaderValue`.
+ - Add `request_headers` method to `warp::log::Info`.
+ - Add `max_frame_size` to `warp::ws::Ws` builder.
+ - Add `remote_addr` to `warp::test::RequestBuilder`.
+ - Add `try_bind_with_graceful_shutdown` to `warp::Server` builder.
+ - Add `serve_incoming_with_graceful_shutdown` to `warp::Server` builder.
+- **Fixes**:
+ - Fix `warp::addr::remote` when used with `Server::tls`.
+ - Fix panic in `warp::path::{peek, tail, full}` filters when the request URI is in authority-form or asterisk-form.
+
+### 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)
+
+- Initial release.
diff --git a/third_party/rust/warp/Cargo.toml b/third_party/rust/warp/Cargo.toml
new file mode 100644
index 0000000000..71a2f85348
--- /dev/null
+++ b/third_party/rust/warp/Cargo.toml
@@ -0,0 +1,185 @@
+# 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 are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2018"
+name = "warp"
+version = "0.3.3"
+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]
+all-features = true
+
+[profile.bench]
+codegen-units = 1
+incremental = false
+
+[profile.release]
+codegen-units = 1
+incremental = false
+
+[[example]]
+name = "compression"
+required-features = ["compression"]
+
+[[example]]
+name = "unix_socket"
+
+[[example]]
+name = "websockets"
+required-features = ["websocket"]
+
+[[example]]
+name = "websockets_chat"
+required-features = ["websocket"]
+
+[[example]]
+name = "query_string"
+
+[[test]]
+name = "multipart"
+required-features = ["multipart"]
+
+[[test]]
+name = "ws"
+required-features = ["websocket"]
+
+[dependencies]
+bytes = "1.0"
+headers = "0.3"
+http = "0.2"
+log = "0.4"
+mime = "0.3"
+mime_guess = "2.0.0"
+percent-encoding = "2.1"
+pin-project = "1.0"
+scoped-tls = "1.0"
+serde = "1.0"
+serde_json = "1.0"
+serde_urlencoded = "0.7"
+tokio-stream = "0.1.1"
+tower-service = "0.3"
+
+[dependencies.async-compression]
+version = "0.3.7"
+features = ["tokio"]
+optional = true
+
+[dependencies.futures-channel]
+version = "0.3.17"
+features = ["sink"]
+
+[dependencies.futures-util]
+version = "0.3"
+features = ["sink"]
+default-features = false
+
+[dependencies.hyper]
+version = "0.14"
+features = [
+ "stream",
+ "server",
+ "http1",
+ "http2",
+ "tcp",
+ "client",
+]
+
+[dependencies.multipart]
+version = "0.18"
+features = ["server"]
+optional = true
+default-features = false
+
+[dependencies.rustls-pemfile]
+version = "0.2"
+optional = true
+
+[dependencies.tokio]
+version = "1.0"
+features = [
+ "fs",
+ "sync",
+ "time",
+]
+
+[dependencies.tokio-rustls]
+version = "0.23"
+optional = true
+
+[dependencies.tokio-tungstenite]
+version = "0.17"
+optional = true
+
+[dependencies.tokio-util]
+version = "0.7"
+features = ["io"]
+
+[dependencies.tracing]
+version = "0.1.21"
+features = [
+ "log",
+ "std",
+]
+default-features = false
+
+[dev-dependencies]
+handlebars = "4.0"
+listenfd = "0.3"
+pretty_env_logger = "0.4"
+serde_derive = "1.0"
+tracing-log = "0.1"
+tracing-subscriber = "0.2.7"
+
+[dev-dependencies.tokio]
+version = "1.0"
+features = [
+ "macros",
+ "rt-multi-thread",
+]
+
+[dev-dependencies.tokio-stream]
+version = "0.1.1"
+features = ["net"]
+
+[features]
+compression = [
+ "compression-brotli",
+ "compression-gzip",
+]
+compression-brotli = ["async-compression/brotli"]
+compression-gzip = [
+ "async-compression/deflate",
+ "async-compression/gzip",
+]
+default = [
+ "multipart",
+ "websocket",
+]
+tls = [
+ "tokio-rustls",
+ "rustls-pemfile",
+]
+websocket = ["tokio-tungstenite"]
diff --git a/third_party/rust/warp/LICENSE b/third_party/rust/warp/LICENSE
new file mode 100644
index 0000000000..4ba959d325
--- /dev/null
+++ b/third_party/rust/warp/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2018-2020 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..0f65594077
--- /dev/null
+++ b/third_party/rust/warp/README.md
@@ -0,0 +1,63 @@
+# warp
+
+[![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)
+[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
+[![GHA Build Status](https://github.com/seanmonstar/warp/workflows/CI/badge.svg)](https://github.com/seanmonstar/warp/actions?query=workflow%3ACI)
+[![Discord chat][discord-badge]][discord-url]
+
+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
+* Gzip, Deflate, and Brotli compression
+
+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 = "1", features = ["full"] }
+warp = "0.3"
+```
+
+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).
+
+[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord
+[discord-url]: https://discord.gg/RFsPjyt
diff --git a/third_party/rust/warp/examples/README.md b/third_party/rust/warp/examples/README.md
new file mode 100644
index 0000000000..1b3bb8a2ae
--- /dev/null
+++ b/third_party/rust/warp/examples/README.md
@@ -0,0 +1,68 @@
+# Examples
+
+Welcome to the examples! These show off `warp`'s functionality and explain how to use it.
+
+## Getting Started
+
+To get started, run `examples/hello.rs` with:
+
+```bash
+> cargo run --example hello
+```
+
+This will start a simple "hello world" service running on your localhost port 3030.
+
+Open another terminal and run:
+
+```bash
+> curl http://localhost:3030/hi
+Hello, World!%
+```
+
+Congratulations, you have just run your first warp service!
+
+You can run other examples with `cargo run --example [example name]`:
+
+- [`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!
+
+### Debugging
+
+- [`tracing.rs`](./tracing.rs) - Warp has built-in support for rich diagnostics with [`tracing`](https://docs.rs/tracing)!
+
+## Custom HTTP Methods
+
+- [`custom_methods.rs`](./custom_methods.rs) - It is also possible to use Warp with custom HTTP methods.
diff --git a/third_party/rust/warp/examples/autoreload.rs b/third_party/rust/warp/examples/autoreload.rs
new file mode 100644
index 0000000000..a21d9b1369
--- /dev/null
+++ b/third_party/rust/warp/examples/autoreload.rs
@@ -0,0 +1,42 @@
+#![deny(warnings)]
+use hyper::server::Server;
+use listenfd::ListenFd;
+use std::convert::Infallible;
+use warp::Filter;
+
+/// 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(|_: _| {
+ // the clone is there because not all warp filters impl Copy
+ let svc = svc.clone();
+ 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/compression.rs b/third_party/rust/warp/examples/compression.rs
new file mode 100644
index 0000000000..1a52c7a7d0
--- /dev/null
+++ b/third_party/rust/warp/examples/compression.rs
@@ -0,0 +1,34 @@
+#![deny(warnings)]
+
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ let file = warp::path("todos").and(warp::fs::file("./examples/todos.rs"));
+ // NOTE: You could double compress something by adding a compression
+ // filter here, a la
+ // ```
+ // let file = warp::path("todos")
+ // .and(warp::fs::file("./examples/todos.rs"))
+ // .with(warp::compression::brotli());
+ // ```
+ // This would result in a browser error, or downloading a file whose contents
+ // are compressed
+
+ let dir = warp::path("ws_chat").and(warp::fs::file("./examples/websockets_chat.rs"));
+
+ let file_and_dir = warp::get()
+ .and(file.or(dir))
+ .with(warp::compression::gzip());
+
+ let examples = warp::path("ex")
+ .and(warp::fs::dir("./examples/"))
+ .with(warp::compression::deflate());
+
+ // GET /todos => gzip -> toods.rs
+ // GET /ws_chat => gzip -> ws_chat.rs
+ // GET /ex/... => deflate -> ./examples/...
+ let routes = file_and_dir.or(examples);
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/examples/custom_methods.rs b/third_party/rust/warp/examples/custom_methods.rs
new file mode 100644
index 0000000000..2e26604132
--- /dev/null
+++ b/third_party/rust/warp/examples/custom_methods.rs
@@ -0,0 +1,61 @@
+#![deny(warnings)]
+use std::net::SocketAddr;
+
+use warp::hyper::StatusCode;
+use warp::{hyper::Method, reject, Filter, Rejection, Reply};
+
+#[derive(Debug)]
+struct MethodError;
+impl reject::Reject for MethodError {}
+
+const FOO_METHOD: &'static str = "FOO";
+const BAR_METHOD: &'static str = "BAR";
+
+fn method(name: &'static str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
+ warp::method()
+ .and_then(move |m: Method| async move {
+ if m == name {
+ Ok(())
+ } else {
+ Err(reject::custom(MethodError))
+ }
+ })
+ .untuple_one()
+}
+
+pub async fn handle_not_found(reject: Rejection) -> Result<impl Reply, Rejection> {
+ if reject.is_not_found() {
+ Ok(StatusCode::NOT_FOUND)
+ } else {
+ Err(reject)
+ }
+}
+
+pub async fn handle_custom(reject: Rejection) -> Result<impl Reply, Rejection> {
+ if reject.find::<MethodError>().is_some() {
+ Ok(StatusCode::METHOD_NOT_ALLOWED)
+ } else {
+ Err(reject)
+ }
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+ let address: SocketAddr = "[::]:3030".parse()?;
+
+ let foo_route = method(FOO_METHOD)
+ .and(warp::path!("foo"))
+ .map(|| "Success")
+ .recover(handle_not_found);
+
+ let bar_route = method(BAR_METHOD)
+ .and(warp::path!("bar"))
+ .map(|| "Success")
+ .recover(handle_not_found);
+
+ warp::serve(foo_route.or(bar_route).recover(handle_custom))
+ .run(address)
+ .await;
+
+ Ok(())
+}
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..43bf2f6efa
--- /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::sleep(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..78e040539e
--- /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/query_string.rs b/third_party/rust/warp/examples/query_string.rs
new file mode 100644
index 0000000000..869468eb0d
--- /dev/null
+++ b/third_party/rust/warp/examples/query_string.rs
@@ -0,0 +1,59 @@
+use serde_derive::{Deserialize, Serialize};
+use std::collections::HashMap;
+use warp::{
+ http::{Response, StatusCode},
+ Filter,
+};
+
+#[derive(Deserialize, Serialize)]
+struct MyObject {
+ key1: String,
+ key2: u32,
+}
+
+#[tokio::main]
+async fn main() {
+ pretty_env_logger::init();
+
+ // get /example1?key=value
+ // demonstrates an optional parameter.
+ let example1 = warp::get()
+ .and(warp::path("example1"))
+ .and(warp::query::<HashMap<String, String>>())
+ .map(|p: HashMap<String, String>| match p.get("key") {
+ Some(key) => Response::builder().body(format!("key = {}", key)),
+ None => Response::builder().body(String::from("No \"key\" param in query.")),
+ });
+
+ // get /example2?key1=value&key2=42
+ // uses the query string to populate a custom object
+ let example2 = warp::get()
+ .and(warp::path("example2"))
+ .and(warp::query::<MyObject>())
+ .map(|p: MyObject| {
+ Response::builder().body(format!("key1 = {}, key2 = {}", p.key1, p.key2))
+ });
+
+ let opt_query = warp::query::<MyObject>()
+ .map(Some)
+ .or_else(|_| async { Ok::<(Option<MyObject>,), std::convert::Infallible>((None,)) });
+
+ // get /example3?key1=value&key2=42
+ // builds on example2 but adds custom error handling
+ let example3 =
+ warp::get()
+ .and(warp::path("example3"))
+ .and(opt_query)
+ .map(|p: Option<MyObject>| match p {
+ Some(obj) => {
+ Response::builder().body(format!("key1 = {}, key2 = {}", obj.key1, obj.key2))
+ }
+ None => Response::builder()
+ .status(StatusCode::BAD_REQUEST)
+ .body(String::from("Failed to decode query param.")),
+ });
+
+ warp::serve(example1.or(example2).or(example3))
+ .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..721e69ecaa
--- /dev/null
+++ b/third_party/rust/warp/examples/rejections.rs
@@ -0,0 +1,122 @@
+#![deny(warnings)]
+
+use std::convert::Infallible;
+use std::error::Error;
+use std::num::NonZeroU16;
+
+use serde_derive::{Deserialize, 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);
+ let div_with_header = math
+ .and(warp::get())
+ .and(div_by())
+ .map(|num: u16, denom: NonZeroU16| {
+ warp::reply::json(&Math {
+ op: format!("{} / {}", num, denom),
+ output: num / denom.get(),
+ })
+ });
+
+ let div_with_body =
+ math.and(warp::post())
+ .and(warp::body::json())
+ .map(|num: u16, body: DenomRequest| {
+ warp::reply::json(&Math {
+ op: format!("{} / {}", num, body.denom),
+ output: num / body.denom.get(),
+ })
+ });
+
+ let routes = div_with_header.or(div_with_body).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(Deserialize)]
+struct DenomRequest {
+ pub denom: NonZeroU16,
+}
+
+#[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(e) = err.find::<warp::filters::body::BodyDeserializeError>() {
+ // This error happens if the body could not be deserialized correctly
+ // We can use the cause to analyze the error and customize the error message
+ message = match e.source() {
+ Some(cause) => {
+ if cause.to_string().contains("denom") {
+ "FIELD_ERROR: denom"
+ } else {
+ "BAD_REQUEST"
+ }
+ }
+ None => "BAD_REQUEST",
+ };
+ code = StatusCode::BAD_REQUEST;
+ } 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..b2ad8c278d
--- /dev/null
+++ b/third_party/rust/warp/examples/routing.rs
@@ -0,0 +1,104 @@
+#![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 /
+ let hello_world = warp::path::end().map(|| "Hello, World at root!");
+
+ // 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 that the
+ // method is GET, so we'll do that too!)
+ //
+ // GET /
+ // GET /hi
+ // GET /hello/from/warp
+ // GET /bye/:string
+ // GET /math/sum/:u32/:u32
+ // GET /math/:u16/times/:u16
+
+ let routes = warp::get().and(
+ hello_world
+ .or(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..bce1fb6b1a
--- /dev/null
+++ b/third_party/rust/warp/examples/sse.rs
@@ -0,0 +1,31 @@
+use futures_util::StreamExt;
+use std::convert::Infallible;
+use std::time::Duration;
+use tokio::time::interval;
+use tokio_stream::wrappers::IntervalStream;
+use warp::{sse::Event, Filter};
+
+// create server-sent event
+fn sse_counter(counter: u64) -> Result<Event, Infallible> {
+ Ok(warp::sse::Event::default().data(counter.to_string()))
+}
+
+#[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 interval = interval(Duration::from_secs(1));
+ let stream = IntervalStream::new(interval);
+ let event_stream = stream.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..6e064b1824
--- /dev/null
+++ b/third_party/rust/warp/examples/sse_chat.rs
@@ -0,0 +1,163 @@
+use futures_util::{Stream, StreamExt};
+use std::collections::HashMap;
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc, Mutex,
+};
+use tokio::sync::mpsc;
+use tokio_stream::wrappers::UnboundedReceiverStream;
+use warp::{sse::Event, 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<Event, 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();
+ let rx = UnboundedReceiverStream::new(rx);
+
+ 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(Event::default().event("user").data(my_id.to_string())),
+ Message::Reply(reply) => Ok(Event::default().data(reply)),
+ })
+}
+
+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..7d28e03a3a
--- /dev/null
+++ b/third_party/rust/warp/examples/tls.rs
@@ -0,0 +1,25 @@
+#![deny(warnings)]
+
+// Don't copy this `cfg`, it's only needed because this file is within
+// the warp repository.
+// Instead, specify the "tls" feature in your warp dependency declaration.
+#[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..904d604e8f
--- /dev/null
+++ b/third_party/rust/warp/examples/todos.rs
@@ -0,0 +1,291 @@
+#![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(&todo1())
+ .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/tracing.rs b/third_party/rust/warp/examples/tracing.rs
new file mode 100644
index 0000000000..103f747a93
--- /dev/null
+++ b/third_party/rust/warp/examples/tracing.rs
@@ -0,0 +1,59 @@
+//! [`tracing`] is a framework for instrumenting Rust programs to
+//! collect scoped, structured, and async-aware diagnostics. This example
+//! demonstrates how the `warp::trace` module can be used to instrument `warp`
+//! applications with `tracing`.
+//!
+//! [`tracing`]: https://crates.io/crates/tracing
+#![deny(warnings)]
+use tracing_subscriber::fmt::format::FmtSpan;
+use warp::Filter;
+
+#[tokio::main]
+async fn main() {
+ // Filter traces based on the RUST_LOG env var, or, if it's not set,
+ // default to show the output of the example.
+ let filter = std::env::var("RUST_LOG").unwrap_or_else(|_| "tracing=info,warp=debug".to_owned());
+
+ // Configure the default `tracing` subscriber.
+ // The `fmt` subscriber from the `tracing-subscriber` crate logs `tracing`
+ // events to stdout. Other subscribers are available for integrating with
+ // distributed tracing systems such as OpenTelemetry.
+ tracing_subscriber::fmt()
+ // Use the filter we built above to determine which traces to record.
+ .with_env_filter(filter)
+ // Record an event when each span closes. This can be used to time our
+ // routes' durations!
+ .with_span_events(FmtSpan::CLOSE)
+ .init();
+
+ let hello = warp::path("hello")
+ .and(warp::get())
+ // When the `hello` route is called, emit a `tracing` event.
+ .map(|| {
+ tracing::info!("saying hello...");
+ "Hello, World!"
+ })
+ // Wrap the route in a `tracing` span to add the route's name as context
+ // to any events that occur inside it.
+ .with(warp::trace::named("hello"));
+
+ let goodbye = warp::path("goodbye")
+ .and(warp::get())
+ .map(|| {
+ tracing::info!("saying goodbye...");
+ "So long and thanks for all the fish!"
+ })
+ // We can also provide our own custom `tracing` spans to wrap a route.
+ .with(warp::trace(|info| {
+ // Construct our own custom span for this route.
+ tracing::info_span!("goodbye", req.path = ?info.path())
+ }));
+
+ let routes = hello
+ .or(goodbye)
+ // Wrap all the routes with a filter that creates a `tracing` span for
+ // each request we receive, including data about the request.
+ .with(warp::trace::request());
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
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..521aeead21
--- /dev/null
+++ b/third_party/rust/warp/examples/unix_socket.rs
@@ -0,0 +1,22 @@
+#![deny(warnings)]
+
+#[cfg(unix)]
+#[tokio::main]
+async fn main() {
+ use tokio::net::UnixListener;
+ use tokio_stream::wrappers::UnixListenerStream;
+
+ pretty_env_logger::init();
+
+ let listener = UnixListener::bind("/tmp/warp.sock").unwrap();
+ let incoming = UnixListenerStream::new(listener);
+ warp::serve(warp::fs::dir("examples/dir"))
+ .run_incoming(incoming)
+ .await;
+}
+
+#[cfg(not(unix))]
+#[tokio::main]
+async fn main() {
+ panic!("Must run under Unix-like platform!");
+}
diff --git a/third_party/rust/warp/examples/websockets.rs b/third_party/rust/warp/examples/websockets.rs
new file mode 100644
index 0000000000..b0de205743
--- /dev/null
+++ b/third_party/rust/warp/examples/websockets.rs
@@ -0,0 +1,27 @@
+#![deny(warnings)]
+
+use futures_util::{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..21e2286f6f
--- /dev/null
+++ b/third_party/rust/warp/examples/websockets_chat.rs
@@ -0,0 +1,175 @@
+// #![deny(warnings)]
+use std::collections::HashMap;
+use std::sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+};
+
+use futures_util::{SinkExt, StreamExt, TryFutureExt};
+use tokio::sync::{mpsc, RwLock};
+use tokio_stream::wrappers::UnboundedReceiverStream;
+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<RwLock<HashMap<usize, mpsc::UnboundedSender<Message>>>>;
+
+#[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 = Users::default();
+ // 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 (mut 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();
+ let mut rx = UnboundedReceiverStream::new(rx);
+
+ tokio::task::spawn(async move {
+ while let Some(message) = rx.next().await {
+ user_ws_tx
+ .send(message)
+ .unwrap_or_else(|e| {
+ eprintln!("websocket send error: {}", e);
+ })
+ .await;
+ }
+ });
+
+ // Save the sender in our list of connected users.
+ users.write().await.insert(my_id, tx);
+
+ // Return a `Future` that is basically a state machine managing
+ // this specific user's connection.
+
+ // 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, &users).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)...
+ for (&uid, tx) in users.read().await.iter() {
+ if my_id != uid {
+ if let Err(_disconnected) = tx.send(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.write().await.remove(&my_id);
+}
+
+static INDEX_HTML: &str = r#"<!DOCTYPE html>
+<html lang="en">
+ <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">
+ const chat = document.getElementById('chat');
+ const text = document.getElementById('text');
+ const uri = 'ws://' + location.host + '/chat';
+ const ws = new WebSocket(uri);
+
+ function message(data) {
+ const 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);
+ };
+
+ ws.onclose = function() {
+ chat.getElementsByTagName('em')[0].innerText = 'Disconnected!';
+ };
+
+ send.onclick = function() {
+ const msg = text.value;
+ ws.send(msg);
+ text.value = '';
+
+ message('<You>: ' + msg);
+ };
+ </script>
+ </body>
+</html>
+"#;
diff --git a/third_party/rust/warp/examples/wrapping.rs b/third_party/rust/warp/examples/wrapping.rs
new file mode 100644
index 0000000000..bfc84f980f
--- /dev/null
+++ b/third_party/rust/warp/examples/wrapping.rs
@@ -0,0 +1,31 @@
+#![deny(warnings)]
+use warp::Filter;
+
+fn hello_wrapper<F, T>(
+ filter: F,
+) -> impl Filter<Extract = (&'static str,)> + Clone + Send + Sync + 'static
+where
+ F: Filter<Extract = (T,), Error = std::convert::Infallible> + Clone + Send + Sync + 'static,
+ F::Extract: warp::Reply,
+{
+ warp::any()
+ .map(|| {
+ println!("before filter");
+ })
+ .untuple_one()
+ .and(filter)
+ .map(|_arg| "wrapped hello world")
+}
+
+#[tokio::main]
+async fn main() {
+ // Match any request and return hello world!
+ let routes = warp::any()
+ .map(|| "hello world")
+ .boxed()
+ .recover(|_err| async { Ok("recovered") })
+ // wrap the filter with hello_wrapper
+ .with(warp::wrap_fn(hello_wrapper));
+
+ warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
+}
diff --git a/third_party/rust/warp/src/error.rs b/third_party/rust/warp/src/error.rs
new file mode 100644
index 0000000000..64220b633e
--- /dev/null
+++ b/third_party/rust/warp/src/error.rs
@@ -0,0 +1,79 @@
+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 {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ Some(self.inner.as_ref())
+ }
+}
+
+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
+ );
+}
+
+#[test]
+fn error_source() {
+ let e = Error::new(std::fmt::Error {});
+ assert!(e.source().unwrap().is::<std::fmt::Error>());
+}
+
+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..5edd90fed8
--- /dev/null
+++ b/third_party/rust/warp/src/filter/and.rs
@@ -0,0 +1,97 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::ready;
+use pin_project::pin_project;
+
+use super::{Combine, Filter, FilterBase, Internal, Tuple};
+use crate::generic::CombinedTuples;
+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,
+ CombinedTuples<T::Extract, U::Extract>: Send,
+ U::Error: CombineRejection<T::Error>,
+{
+ type Extract = CombinedTuples<T::Extract, U::Extract>;
+ 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::Future, T::Extract, U>,
+}
+
+#[pin_project(project = StateProj)]
+enum State<T, TE, U: Filter> {
+ First(#[pin] T, U),
+ Second(Option<TE>, #[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<
+ CombinedTuples<T::Extract, U::Extract>,
+ <U::Error as CombineRejection<T::Error>>::One,
+ >;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ self.project().state.poll(cx)
+ }
+}
+
+impl<T, TE, U, E> Future for State<T, TE, U>
+where
+ T: Future<Output = Result<TE, E>>,
+ U: Filter,
+ TE: Tuple,
+ TE::HList: Combine<<U::Extract as Tuple>::HList> + Send,
+ U::Error: CombineRejection<E>,
+{
+ type Output = Result<CombinedTuples<TE, U::Extract>, <U::Error as CombineRejection<E>>::One>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ match self.as_mut().project() {
+ StateProj::First(first, second) => {
+ let ex1 = ready!(first.poll(cx))?;
+ let fut2 = second.filter(Internal);
+ self.set(State::Second(Some(ex1), fut2));
+ }
+ StateProj::Second(ex1, second) => {
+ let ex2 = ready!(second.poll(cx))?;
+ let ex3 = ex1.take().unwrap().combine(ex2);
+ self.set(State::Done);
+ return Poll::Ready(Ok(ex3));
+ }
+ StateProj::Done => panic!("polled after complete"),
+ }
+ }
+ }
+}
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..efed5fe8cf
--- /dev/null
+++ b/third_party/rust/warp/src/filter/and_then.rs
@@ -0,0 +1,110 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{ready, TryFuture};
+use pin_project::pin_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, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ #[pin]
+ state: State<T::Future, F>,
+}
+
+#[pin_project(project = StateProj)]
+enum State<T, F>
+where
+ T: TryFuture,
+ F: Func<T::Ok>,
+ F::Output: TryFuture + Send,
+ <F::Output as TryFuture>::Error: CombineRejection<T::Error>,
+{
+ First(#[pin] T, 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,
+ >;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ self.project().state.poll(cx)
+ }
+}
+
+impl<T, F> Future for State<T, F>
+where
+ T: TryFuture,
+ F: Func<T::Ok>,
+ 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,
+ >;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ match self.as_mut().project() {
+ StateProj::First(first, second) => {
+ let ex1 = ready!(first.try_poll(cx))?;
+ let fut2 = second.call(ex1);
+ self.set(State::Second(fut2));
+ }
+ StateProj::Second(second) => {
+ let ex2 = match ready!(second.try_poll(cx)) {
+ Ok(item) => Ok((item,)),
+ Err(err) => Err(From::from(err)),
+ };
+ self.set(State::Done);
+ return Poll::Ready(ex2);
+ }
+ StateProj::Done => panic!("polled after complete"),
+ }
+ }
+ }
+}
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..1ed4a7d800
--- /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_util::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..ec3173994e
--- /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_util::{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..f4659e6ff6
--- /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_util::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..263650768d
--- /dev/null
+++ b/third_party/rust/warp/src/filter/mod.rs
@@ -0,0 +1,492 @@
+mod and;
+mod and_then;
+mod boxed;
+mod map;
+mod map_err;
+mod or;
+mod or_else;
+mod recover;
+pub(crate) mod service;
+mod then;
+mod unify;
+mod untuple_one;
+mod wrap;
+
+use std::future::Future;
+
+use futures_util::{future, TryFuture, TryFutureExt};
+
+pub(crate) use crate::generic::{one, Combine, Either, Func, 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::then::Then;
+use self::unify::Unify;
+use self::untuple_one::UntupleOne;
+pub use self::wrap::wrap_fn;
+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 an async function receiving
+ /// the extracted value.
+ ///
+ /// The function should return some `Future` type.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use warp::Filter;
+ ///
+ /// // Map `/:id`
+ /// warp::path::param().then(|id: u64| async move {
+ /// format!("Hello #{}", id)
+ /// });
+ /// ```
+ fn then<F>(self, fun: F) -> Then<Self, F>
+ where
+ Self: Sized,
+ F: Func<Self::Extract> + Clone,
+ F::Output: Future + Send,
+ {
+ Then {
+ filter: self,
+ callback: fun,
+ }
+ }
+
+ /// Composes this `Filter` with a fallible async 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.
+ ///
+ /// Rejections are meant to say "this filter didn't accept the request,
+ /// maybe another can". So for application-level errors, consider using
+ /// [`Filter::then`] instead.
+ ///
+ /// # 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,
+) -> impl Filter<Extract = (U::Ok,), Error = U::Error> + Copy
+where
+ F: Fn(&mut Route) -> U + Copy,
+ U: TryFuture + Send + 'static,
+ U::Ok: Send,
+ U::Error: IsReject,
+{
+ filter_fn(move |route| func(route).map_ok(|item| (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 = future::IntoFuture<U>;
+
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ 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..774067fb59
--- /dev/null
+++ b/third_party/rust/warp/src/filter/or.rs
@@ -0,0 +1,110 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{ready, TryFuture};
+use pin_project::pin_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(project = StateProj)]
+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>>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ let (err1, fut2) = match pin.state.project() {
+ StateProj::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))
+ }
+ },
+ StateProj::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);
+ }
+ StateProj::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..aaf23243ec
--- /dev/null
+++ b/third_party/rust/warp/src/filter/or_else.rs
@@ -0,0 +1,107 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{ready, TryFuture};
+use pin_project::pin_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(project = StateProj)]
+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>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ let (err, second) = match pin.state.project() {
+ StateProj::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(ex) => return Poll::Ready(Ok(ex)),
+ Err(err) => (err, second),
+ },
+ StateProj::Second(second) => {
+ let ex2 = ready!(second.try_poll(cx));
+ self.set(OrElseFuture {
+ state: State::Done,
+ ..*self
+ });
+ return Poll::Ready(ex2);
+ }
+ StateProj::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..100e9398c2
--- /dev/null
+++ b/third_party/rust/warp/src/filter/recover.rs
@@ -0,0 +1,117 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{ready, TryFuture};
+use pin_project::pin_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(project = StateProj)]
+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,
+ >;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ let pin = self.as_mut().project();
+ let (err, second) = match pin.state.project() {
+ StateProj::First(first, second) => match ready!(first.try_poll(cx)) {
+ Ok(ex) => return Poll::Ready(Ok((Either::A(ex),))),
+ Err(err) => (err, second),
+ },
+ StateProj::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);
+ }
+ StateProj::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..3de12a02ed
--- /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_util::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)) => {
+ tracing::debug!("rejected: {:?}", err);
+ Poll::Ready(Ok(err.into_response()))
+ }
+ }
+ }
+}
diff --git a/third_party/rust/warp/src/filter/then.rs b/third_party/rust/warp/src/filter/then.rs
new file mode 100644
index 0000000000..543a22669a
--- /dev/null
+++ b/third_party/rust/warp/src/filter/then.rs
@@ -0,0 +1,95 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{ready, TryFuture};
+use pin_project::pin_project;
+
+use super::{Filter, FilterBase, Func, Internal};
+
+#[derive(Clone, Copy, Debug)]
+pub struct Then<T, F> {
+ pub(super) filter: T,
+ pub(super) callback: F,
+}
+
+impl<T, F> FilterBase for Then<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract> + Clone + Send,
+ F::Output: Future + Send,
+{
+ type Extract = (<F::Output as Future>::Output,);
+ type Error = T::Error;
+ type Future = ThenFuture<T, F>;
+ #[inline]
+ fn filter(&self, _: Internal) -> Self::Future {
+ ThenFuture {
+ state: State::First(self.filter.filter(Internal), self.callback.clone()),
+ }
+ }
+}
+
+#[allow(missing_debug_implementations)]
+#[pin_project]
+pub struct ThenFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: Future + Send,
+{
+ #[pin]
+ state: State<T::Future, F>,
+}
+
+#[pin_project(project = StateProj)]
+enum State<T, F>
+where
+ T: TryFuture,
+ F: Func<T::Ok>,
+ F::Output: Future + Send,
+{
+ First(#[pin] T, F),
+ Second(#[pin] F::Output),
+ Done,
+}
+
+impl<T, F> Future for ThenFuture<T, F>
+where
+ T: Filter,
+ F: Func<T::Extract>,
+ F::Output: Future + Send,
+{
+ type Output = Result<(<F::Output as Future>::Output,), T::Error>;
+
+ fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ self.project().state.poll(cx)
+ }
+}
+
+impl<T, F> Future for State<T, F>
+where
+ T: TryFuture,
+ F: Func<T::Ok>,
+ F::Output: Future + Send,
+{
+ type Output = Result<(<F::Output as Future>::Output,), T::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ loop {
+ match self.as_mut().project() {
+ StateProj::First(first, second) => {
+ let ex1 = ready!(first.try_poll(cx))?;
+ let fut2 = second.call(ex1);
+ self.set(State::Second(fut2));
+ }
+ StateProj::Second(second) => {
+ let ex2 = (ready!(second.poll(cx)),);
+ self.set(State::Done);
+ return Poll::Ready(Ok(ex2));
+ }
+ StateProj::Done => panic!("polled after complete"),
+ }
+ }
+ }
+}
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..0cf670daa2
--- /dev/null
+++ b/third_party/rust/warp/src/filter/unify.rs
@@ -0,0 +1,50 @@
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use futures_util::{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> {
+ Poll::Ready(match ready!(self.project().inner.try_poll(cx))? {
+ (Either::A(x),) | (Either::B(x),) => Ok(x),
+ })
+ }
+}
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..0fb0de6748
--- /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_util::{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..faceb705f3
--- /dev/null
+++ b/third_party/rust/warp/src/filter/wrap.rs
@@ -0,0 +1,67 @@
+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,
+{
+}
+
+/// Combines received filter with pre and after filters
+///
+/// # Example
+///
+/// ```
+/// use crate::warp::Filter;
+///
+/// let route = warp::any()
+/// .map(|| "hello world")
+/// .with(warp::wrap_fn(|filter| filter));
+/// ```
+///
+/// You can find the full example in the [usage example](https://github.com/seanmonstar/warp/blob/master/examples/wrapping.rs).
+pub fn wrap_fn<F, T, U>(func: F) -> WrapFn<F>
+where
+ F: Fn(T) -> U,
+ T: Filter,
+ U: Filter,
+{
+ WrapFn { func }
+}
+
+#[derive(Debug)]
+pub struct WrapFn<F> {
+ func: F,
+}
+
+impl<F, T, U> WrapSealed<T> for WrapFn<F>
+where
+ F: Fn(T) -> U,
+ T: Filter,
+ U: Filter,
+{
+ type Wrapped = U;
+
+ fn wrap(&self, filter: T) -> Self::Wrapped {
+ (self.func)(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..3d630705a1
--- /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_util::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..707fd82dd4
--- /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..3bb08d2b4c
--- /dev/null
+++ b/third_party/rust/warp/src/filters/body.rs
@@ -0,0 +1,345 @@
+//! 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, Bytes};
+use futures_util::{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(|| {
+ tracing::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, |_| {
+ tracing::debug!("content-length missing");
+ reject::length_required()
+ })
+ .and_then(move |ContentLength(length)| {
+ if length <= limit {
+ future::ok(())
+ } else {
+ tracing::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| {
+ tracing::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.chunk());
+/// let cnt = body.chunk().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| {
+ tracing::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(bytes())
+ .and_then(|buf| async move {
+ Json::decode(buf).map_err(|err| {
+ tracing::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| {
+ tracing::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>(mut buf: B) -> Result<T, BoxError> {
+ serde_json::from_slice(&buf.copy_to_bytes(buf.remaining())).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) {
+ tracing::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 {
+ tracing::debug!(
+ "content-type {:?} doesn't match {}/{}",
+ value,
+ type_,
+ subtype
+ );
+ future::err(reject::unsupported_media_type())
+ }
+ } else {
+ tracing::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!
+ tracing::trace!("no content-type header, assuming {}/{}", type_, subtype);
+ future::ok(())
+ } else {
+ tracing::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 {
+ fn source(&self) -> Option<&(dyn StdError + 'static)> {
+ Some(self.cause.as_ref())
+ }
+}
+
+#[derive(Debug)]
+pub(crate) struct BodyReadError(::hyper::Error);
+
+impl fmt::Display for BodyReadError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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/compression.rs b/third_party/rust/warp/src/filters/compression.rs
new file mode 100644
index 0000000000..244e768356
--- /dev/null
+++ b/third_party/rust/warp/src/filters/compression.rs
@@ -0,0 +1,292 @@
+//! Compression Filters
+//!
+//! Filters that compress the body of a response.
+
+#[cfg(feature = "compression-brotli")]
+use async_compression::tokio::bufread::BrotliEncoder;
+
+#[cfg(feature = "compression-gzip")]
+use async_compression::tokio::bufread::{DeflateEncoder, GzipEncoder};
+
+use http::header::HeaderValue;
+use hyper::{
+ header::{CONTENT_ENCODING, CONTENT_LENGTH},
+ Body,
+};
+use tokio_util::io::{ReaderStream, StreamReader};
+
+use crate::filter::{Filter, WrapSealed};
+use crate::reject::IsReject;
+use crate::reply::{Reply, Response};
+
+use self::internal::{CompressionProps, WithCompression};
+
+enum CompressionAlgo {
+ #[cfg(feature = "compression-brotli")]
+ BR,
+ #[cfg(feature = "compression-gzip")]
+ DEFLATE,
+ #[cfg(feature = "compression-gzip")]
+ GZIP,
+}
+
+impl From<CompressionAlgo> for HeaderValue {
+ #[inline]
+ fn from(algo: CompressionAlgo) -> Self {
+ HeaderValue::from_static(match algo {
+ #[cfg(feature = "compression-brotli")]
+ CompressionAlgo::BR => "br",
+ #[cfg(feature = "compression-gzip")]
+ CompressionAlgo::DEFLATE => "deflate",
+ #[cfg(feature = "compression-gzip")]
+ CompressionAlgo::GZIP => "gzip",
+ })
+ }
+}
+
+/// Compression
+#[derive(Clone, Copy, Debug)]
+pub struct Compression<F> {
+ func: F,
+}
+
+// TODO: The implementation of `gzip()`, `deflate()`, and `brotli()` could be replaced with
+// generics or a macro
+
+/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
+/// using gzip, adding `content-encoding: gzip` to the Response's [`HeaderMap`](hyper::HeaderMap)
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::get()
+/// .and(warp::path::end())
+/// .and(warp::fs::file("./README.md"))
+/// .with(warp::compression::gzip());
+/// ```
+#[cfg(feature = "compression-gzip")]
+pub fn gzip() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
+ let func = move |mut props: CompressionProps| {
+ let body = Body::wrap_stream(ReaderStream::new(GzipEncoder::new(StreamReader::new(
+ props.body,
+ ))));
+ props
+ .head
+ .headers
+ .append(CONTENT_ENCODING, CompressionAlgo::GZIP.into());
+ props.head.headers.remove(CONTENT_LENGTH);
+ Response::from_parts(props.head, body)
+ };
+ Compression { func }
+}
+
+/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
+/// using deflate, adding `content-encoding: deflate` to the Response's [`HeaderMap`](hyper::HeaderMap)
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::get()
+/// .and(warp::path::end())
+/// .and(warp::fs::file("./README.md"))
+/// .with(warp::compression::deflate());
+/// ```
+#[cfg(feature = "compression-gzip")]
+pub fn deflate() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
+ let func = move |mut props: CompressionProps| {
+ let body = Body::wrap_stream(ReaderStream::new(DeflateEncoder::new(StreamReader::new(
+ props.body,
+ ))));
+ props
+ .head
+ .headers
+ .append(CONTENT_ENCODING, CompressionAlgo::DEFLATE.into());
+ props.head.headers.remove(CONTENT_LENGTH);
+ Response::from_parts(props.head, body)
+ };
+ Compression { func }
+}
+
+/// Create a wrapping filter that compresses the Body of a [`Response`](crate::reply::Response)
+/// using brotli, adding `content-encoding: br` to the Response's [`HeaderMap`](hyper::HeaderMap)
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::get()
+/// .and(warp::path::end())
+/// .and(warp::fs::file("./README.md"))
+/// .with(warp::compression::brotli());
+/// ```
+#[cfg(feature = "compression-brotli")]
+pub fn brotli() -> Compression<impl Fn(CompressionProps) -> Response + Copy> {
+ let func = move |mut props: CompressionProps| {
+ let body = Body::wrap_stream(ReaderStream::new(BrotliEncoder::new(StreamReader::new(
+ props.body,
+ ))));
+ props
+ .head
+ .headers
+ .append(CONTENT_ENCODING, CompressionAlgo::BR.into());
+ props.head.headers.remove(CONTENT_LENGTH);
+ Response::from_parts(props.head, body)
+ };
+ Compression { func }
+}
+
+impl<FN, F> WrapSealed<F> for Compression<FN>
+where
+ FN: Fn(CompressionProps) -> Response + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+{
+ type Wrapped = WithCompression<FN, F>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ WithCompression {
+ filter,
+ compress: self.clone(),
+ }
+ }
+}
+
+mod internal {
+ use std::future::Future;
+ use std::pin::Pin;
+ use std::task::{Context, Poll};
+
+ use bytes::Bytes;
+ use futures_util::{ready, Stream, TryFuture};
+ use hyper::Body;
+ use pin_project::pin_project;
+
+ use crate::filter::{Filter, FilterBase, Internal};
+ use crate::reject::IsReject;
+ use crate::reply::{Reply, Response};
+
+ use super::Compression;
+
+ /// A wrapper around any type that implements [`Stream`](futures::Stream) to be
+ /// compatible with async_compression's Stream based encoders
+ #[pin_project]
+ #[derive(Debug)]
+ pub struct CompressableBody<S, E>
+ where
+ E: std::error::Error,
+ S: Stream<Item = Result<Bytes, E>>,
+ {
+ #[pin]
+ body: S,
+ }
+
+ impl<S, E> Stream for CompressableBody<S, E>
+ where
+ E: std::error::Error,
+ S: Stream<Item = Result<Bytes, E>>,
+ {
+ type Item = std::io::Result<Bytes>;
+
+ fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+ use std::io::{Error, ErrorKind};
+
+ let pin = self.project();
+ S::poll_next(pin.body, cx).map_err(|_| Error::from(ErrorKind::InvalidData))
+ }
+ }
+
+ impl From<Body> for CompressableBody<Body, hyper::Error> {
+ fn from(body: Body) -> Self {
+ CompressableBody { body }
+ }
+ }
+
+ /// Compression Props
+ #[derive(Debug)]
+ pub struct CompressionProps {
+ pub(super) body: CompressableBody<Body, hyper::Error>,
+ pub(super) head: http::response::Parts,
+ }
+
+ impl From<http::Response<Body>> for CompressionProps {
+ fn from(resp: http::Response<Body>) -> Self {
+ let (head, body) = resp.into_parts();
+ CompressionProps {
+ body: body.into(),
+ head,
+ }
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ pub struct Compressed(pub(super) Response);
+
+ impl Reply for Compressed {
+ #[inline]
+ fn into_response(self) -> Response {
+ self.0
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ #[derive(Clone, Copy)]
+ pub struct WithCompression<FN, F> {
+ pub(super) compress: Compression<FN>,
+ pub(super) filter: F,
+ }
+
+ impl<FN, F> FilterBase for WithCompression<FN, F>
+ where
+ FN: Fn(CompressionProps) -> Response + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+ {
+ type Extract = (Compressed,);
+ type Error = F::Error;
+ type Future = WithCompressionFuture<FN, F::Future>;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ WithCompressionFuture {
+ compress: self.compress.clone(),
+ future: self.filter.filter(Internal),
+ }
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ #[pin_project]
+ pub struct WithCompressionFuture<FN, F> {
+ compress: Compression<FN>,
+ #[pin]
+ future: F,
+ }
+
+ impl<FN, F> Future for WithCompressionFuture<FN, F>
+ where
+ FN: Fn(CompressionProps) -> Response,
+ F: TryFuture,
+ F::Ok: Reply,
+ F::Error: IsReject,
+ {
+ type Output = Result<(Compressed,), F::Error>;
+
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
+ let pin = self.as_mut().project();
+ let result = ready!(pin.future.try_poll(cx));
+ match result {
+ Ok(reply) => {
+ let resp = (self.compress.func)(reply.into_response().into());
+ Poll::Ready(Ok((Compressed(resp),)))
+ }
+ Err(reject) => Poll::Ready(Err(reject)),
+ }
+ }
+ }
+}
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..53695e85c8
--- /dev/null
+++ b/third_party/rust/warp/src/filters/cookie.rs
@@ -0,0 +1,46 @@
+//! Cookie Filters
+
+use futures_util::future;
+use headers::Cookie;
+
+use super::header;
+use crate::filter::{Filter, One};
+use crate::reject::Rejection;
+use std::convert::Infallible;
+use std::str::FromStr;
+
+/// Creates a `Filter` that requires a cookie by name.
+///
+/// If found, extracts the value of the cookie, otherwise rejects.
+pub fn cookie<T>(name: &'static str) -> impl Filter<Extract = One<T>, Error = Rejection> + Copy
+where
+ T: FromStr + Send + 'static,
+{
+ header::header2().and_then(move |cookie: Cookie| {
+ let cookie = cookie
+ .get(name)
+ .ok_or_else(|| crate::reject::missing_cookie(name))
+ .and_then(|s| T::from_str(s).map_err(|_| 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<T>(
+ name: &'static str,
+) -> impl Filter<Extract = One<Option<T>>, Error = Infallible> + Copy
+where
+ T: FromStr + Send + 'static,
+{
+ header::optional2().map(move |opt: Option<Cookie>| {
+ let cookie = opt.and_then(|cookie| cookie.get(name).map(|x| T::from_str(x)));
+ match cookie {
+ Some(Ok(t)) => Some(t),
+ Some(Err(_)) => None,
+ None => None,
+ }
+ })
+}
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..fa28893c20
--- /dev/null
+++ b/third_party/rust/warp/src/filters/cors.rs
@@ -0,0 +1,626 @@
+//! CORS Filters
+
+use std::collections::HashSet;
+use std::convert::TryFrom;
+use std::error::Error as StdError;
+use std::fmt;
+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.
+ ///
+ /// **Note**: These should match the values the browser sends via `Access-Control-Request-Headers`, e.g. `content-type`.
+ ///
+ /// # 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.
+ ///
+ /// **Note**: These should match the values the browser sends via `Access-Control-Request-Headers`, e.g.`content-type`.
+ ///
+ /// # 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 intend 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 fmt::Debug for CorsForbidden {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_tuple("CorsForbidden").field(&self.kind).finish()
+ }
+}
+
+impl fmt::Display for CorsForbidden {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 {
+ tracing::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.trim()) {
+ return Err(Forbidden::HeaderNotAllowed);
+ }
+ }
+ }
+
+ Ok(Validated::Preflight(origin.clone()))
+ }
+ (Some(origin), _) => {
+ // Any other method, simply check for a valid origin...
+
+ tracing::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_util::{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..985bbfb61c
--- /dev/null
+++ b/third_party/rust/warp/src/filters/ext.rs
@@ -0,0 +1,36 @@
+//! Request Extensions
+
+use std::convert::Infallible;
+
+use futures_util::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..0949b66ecb
--- /dev/null
+++ b/third_party/rust/warp/src/filters/fs.rs
@@ -0,0 +1,539 @@
+//! 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_util::future::Either;
+use futures_util::{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 percent_encoding::percent_decode_str;
+use tokio::fs::File as TkFile;
+use tokio::io::AsyncSeekExt;
+use tokio_util::io::poll_read_buf;
+
+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 || {
+ tracing::trace!("file: {:?}", path);
+ ArcPath(path.clone())
+ })
+ .and(conditionals())
+ .and_then(file_reply)
+}
+
+/// 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()
+ .or(crate::head())
+ .unify()
+ .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 {
+ tracing::debug!("dir: appending index.html to directory path");
+ buf.push("index.html");
+ }
+ tracing::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 percent_decode_str(tail).decode_utf8() {
+ Ok(p) => p,
+ Err(err) => {
+ tracing::debug!("dir: failed to decode route={:?}: {:?}", tail, err);
+ return Err(reject::not_found());
+ }
+ };
+ tracing::trace!("dir? base={:?}, route={:?}", base.as_ref(), p);
+ for seg in p.split('/') {
+ if seg.starts_with("..") {
+ tracing::warn!("dir: rejecting segment starting with '..'");
+ return Err(reject::not_found());
+ } else if seg.contains('\\') {
+ tracing::warn!("dir: rejecting segment containing backslash (\\)");
+ return Err(reject::not_found());
+ } else if cfg!(windows) && seg.contains(':') {
+ tracing::warn!("dir: rejecting segment containing colon (:)");
+ 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);
+
+ tracing::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 {
+ tracing::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 {
+ tracing::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,
+ path: ArcPath,
+}
+
+impl File {
+ /// Extract the `&Path` of the file this `Response` delivers.
+ ///
+ /// # Example
+ ///
+ /// The example below changes the Content-Type response header for every file called `video.mp4`.
+ ///
+ /// ```
+ /// use warp::{Filter, reply::Reply};
+ ///
+ /// let route = warp::path("static")
+ /// .and(warp::fs::dir("/www/static"))
+ /// .map(|reply: warp::filters::fs::File| {
+ /// if reply.path().ends_with("video.mp4") {
+ /// warp::reply::with_header(reply, "Content-Type", "video/mp4").into_response()
+ /// } else {
+ /// reply.into_response()
+ /// }
+ /// });
+ /// ```
+ pub fn path(&self) -> &Path {
+ self.path.as_ref()
+ }
+}
+
+// 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 => {
+ tracing::debug!("file not found: {:?}", path.as_ref().display());
+ reject::not_found()
+ }
+ io::ErrorKind::PermissionDenied => {
+ tracing::warn!("file permission denied: {:?}", path.as_ref().display());
+ reject::known(FilePermissionError { _p: () })
+ }
+ _ => {
+ tracing::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) => {
+ tracing::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, path }
+ })
+}
+
+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) => {
+ // For the special case where s == the file size
+ if s == max_len {
+ s
+ } else {
+ s + 1
+ }
+ }
+ Bound::Excluded(s) => s,
+ };
+
+ if start < end && end <= max_len {
+ Ok((start, end))
+ } else {
+ tracing::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!(poll_read_buf(Pin::new(&mut f), cx, &mut buf)) {
+ Ok(n) => n as u64,
+ Err(err) => {
+ tracing::debug!("file read error: {}", err);
+ return Poll::Ready(Some(Err(err)));
+ }
+ };
+
+ if n == 0 {
+ tracing::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..0c535a38b5
--- /dev/null
+++ b/third_party/rust/warp/src/filters/header.rs
@@ -0,0 +1,230 @@
+//! 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_util::future;
+use headers::{Header, HeaderMapExt};
+use http::header::HeaderValue;
+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| {
+ tracing::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| {
+ tracing::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| {
+ tracing::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| {
+ tracing::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| {
+ tracing::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_ignore_case("connection", "keep-alive");
+/// ```
+pub fn exact_ignore_case(
+ name: &'static str,
+ value: &'static str,
+) -> impl Filter<Extract = (), Error = Rejection> + Copy {
+ filter_fn(move |route| {
+ tracing::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 gets a `HeaderValue` for the name.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Filter, http::header::HeaderValue};
+///
+/// let filter = warp::header::value("x-token")
+/// .map(|value: HeaderValue| {
+/// format!("header value bytes: {:?}", value)
+/// });
+/// ```
+pub fn value(
+ name: &'static str,
+) -> impl Filter<Extract = One<HeaderValue>, Error = Rejection> + Copy {
+ filter_fn_one(move |route| {
+ tracing::trace!("value({:?})", name);
+ let route = route
+ .headers()
+ .get(name)
+ .cloned()
+ .ok_or_else(|| reject::missing_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/host.rs b/third_party/rust/warp/src/filters/host.rs
new file mode 100644
index 0000000000..9aae039c19
--- /dev/null
+++ b/third_party/rust/warp/src/filters/host.rs
@@ -0,0 +1,96 @@
+//! Host ("authority") filter
+//!
+use crate::filter::{filter_fn_one, Filter, One};
+use crate::reject::{self, Rejection};
+use futures_util::future;
+pub use http::uri::Authority;
+use std::str::FromStr;
+
+/// Creates a `Filter` that requires a specific authority (target server's
+/// host and port) in the request.
+///
+/// Authority is specified either in the `Host` header or in the target URI.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let multihost =
+/// warp::host::exact("foo.com").map(|| "you've reached foo.com")
+/// .or(warp::host::exact("bar.com").map(|| "you've reached bar.com"));
+/// ```
+pub fn exact(expected: &str) -> impl Filter<Extract = (), Error = Rejection> + Clone {
+ let expected = Authority::from_str(expected).expect("invalid host/authority");
+ optional()
+ .and_then(move |option: Option<Authority>| match option {
+ Some(authority) if authority == expected => future::ok(()),
+ _ => future::err(reject::not_found()),
+ })
+ .untuple_one()
+}
+
+/// Creates a `Filter` that looks for an authority (target server's host
+/// and port) in the request.
+///
+/// Authority is specified either in the `Host` header or in the target URI.
+///
+/// If found, extracts the `Authority`, otherwise continues the request,
+/// extracting `None`.
+///
+/// Rejects with `400 Bad Request` if the `Host` header is malformed or if there
+/// is a mismatch between the `Host` header and the target URI.
+///
+/// # Example
+///
+/// ```
+/// use warp::{Filter, host::Authority};
+///
+/// let host = warp::host::optional()
+/// .map(|authority: Option<Authority>| {
+/// if let Some(a) = authority {
+/// format!("{} is currently not at home", a.host())
+/// } else {
+/// "please state who you're trying to reach".to_owned()
+/// }
+/// });
+/// ```
+pub fn optional() -> impl Filter<Extract = One<Option<Authority>>, Error = Rejection> + Copy {
+ filter_fn_one(move |route| {
+ // The authority can be sent by clients in various ways:
+ //
+ // 1) in the "target URI"
+ // a) serialized in the start line (HTTP/1.1 proxy requests)
+ // b) serialized in `:authority` pseudo-header (HTTP/2 generated - "SHOULD")
+ // 2) in the `Host` header (HTTP/1.1 origin requests, HTTP/2 converted)
+ //
+ // Hyper transparently handles 1a/1b, but not 2, so we must look at both.
+
+ let from_uri = route.uri().authority();
+
+ let name = "host";
+ let from_header = route.headers()
+ .get(name)
+ .map(|value|
+ // Header present, parse it
+ value.to_str().map_err(|_| reject::invalid_header(name))
+ .and_then(|value| Authority::from_str(value).map_err(|_| reject::invalid_header(name)))
+ );
+
+ future::ready(match (from_uri, from_header) {
+ // no authority in the request (HTTP/1.0 or non-conforming)
+ (None, None) => Ok(None),
+
+ // authority specified in either or both matching
+ (Some(a), None) => Ok(Some(a.clone())),
+ (None, Some(Ok(a))) => Ok(Some(a)),
+ (Some(a), Some(Ok(b))) if *a == b => Ok(Some(b)),
+
+ // mismatch
+ (Some(_), Some(Ok(_))) => Err(reject::invalid_header(name)),
+
+ // parse error
+ (_, Some(Err(r))) => Err(r),
+ })
+ })
+}
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..eae5974024
--- /dev/null
+++ b/third_party/rust/warp/src/filters/log.rs
@@ -0,0 +1,280 @@
+//! 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())
+ }
+
+ /// Access the full headers of the request
+ pub fn request_headers(&self) -> &http::HeaderMap {
+ self.route.headers()
+ }
+}
+
+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_util::{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..c4d7462720
--- /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_util::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();
+ tracing::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..bd1c48718c
--- /dev/null
+++ b/third_party/rust/warp/src/filters/mod.rs
@@ -0,0 +1,29 @@
+//! 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;
+#[cfg(any(feature = "compression-brotli", feature = "compression-gzip"))]
+pub mod compression;
+pub mod cookie;
+pub mod cors;
+pub mod ext;
+pub mod fs;
+pub mod header;
+pub mod host;
+pub mod log;
+pub mod method;
+#[cfg(feature = "multipart")]
+pub mod multipart;
+pub mod path;
+pub mod query;
+pub mod reply;
+pub mod sse;
+pub mod trace;
+#[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..ef2ec92682
--- /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_util::{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 extract 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_deref()
+ }
+
+ /// Get the content-type of this part, if present.
+ pub fn content_type(&self) -> Option<&str> {
+ self.content_type.as_deref()
+ }
+
+ /// 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..179a8d1c91
--- /dev/null
+++ b/third_party/rust/warp/src/filters/path.rs
@@ -0,0 +1,652 @@
+//! 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_util::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| {
+ tracing::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| {
+ tracing::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| {
+ tracing::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 the 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 the tail part of a request path, returned by the [`peek()`] 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()
+ .cloned()
+ .unwrap_or_else(|| PathAndQuery::from_static(""))
+}
+
+/// 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`](crate::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) => (
+ $crate::path::end()
+ );
+ (@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..7aee569484
--- /dev/null
+++ b/third_party/rust/warp/src/filters/query.rs
@@ -0,0 +1,91 @@
+//! Query Filters
+
+use futures_util::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`.
+///
+/// # Example
+///
+/// ```
+/// use std::collections::HashMap;
+/// use warp::{
+/// http::Response,
+/// Filter,
+/// };
+///
+/// let route = warp::any()
+/// .and(warp::query::<HashMap<String, String>>())
+/// .map(|map: HashMap<String, String>| {
+/// let mut response: Vec<String> = Vec::new();
+/// for (key, value) in map.into_iter() {
+/// response.push(format!("{}={}", key, value))
+/// }
+/// Response::builder().body(response.join(";"))
+/// });
+/// ```
+///
+/// You can define your custom query object and deserialize with [Serde][Serde]. Ensure to include
+/// the crate in your dependencies before usage.
+///
+/// ```
+/// use serde_derive::{Deserialize, Serialize};
+/// use std::collections::HashMap;
+/// use warp::{
+/// http::Response,
+/// Filter,
+/// };
+///
+/// #[derive(Serialize, Deserialize)]
+/// struct FooQuery {
+/// foo: Option<String>,
+/// bar: u8,
+/// }
+///
+/// let route = warp::any()
+/// .and(warp::query::<FooQuery>())
+/// .map(|q: FooQuery| {
+/// if let Some(foo) = q.foo {
+/// Response::builder().body(format!("foo={}", foo))
+/// } else {
+/// Response::builder().body(format!("bar={}", q.bar))
+/// }
+/// });
+/// ```
+///
+/// For more examples, please take a look at [examples/query_string.rs](https://github.com/seanmonstar/warp/blob/master/examples/query_string.rs).
+///
+/// [Serde]: https://docs.rs/serde
+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(|| {
+ tracing::debug!("route was called without a query string, defaulting to empty");
+ ""
+ });
+
+ let query_encoded = serde_urlencoded::from_str(query_string).map_err(|e| {
+ tracing::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..2fb9b7ef11
--- /dev/null
+++ b/third_party/rust/warp/src/filters/sse.rs
@@ -0,0 +1,511 @@
+//! Server-Sent Events (SSE)
+//!
+//! # Example
+//!
+//! ```
+//!
+//! use std::time::Duration;
+//! use std::convert::Infallible;
+//! use warp::{Filter, sse::Event};
+//! use futures_util::{stream::iter, Stream};
+//!
+//! fn sse_events() -> impl Stream<Item = Result<Event, Infallible>> {
+//! iter(vec![
+//! Ok(Event::default().data("unnamed event")),
+//! Ok(
+//! Event::default().event("chat")
+//! .data("chat message")
+//! ),
+//! Ok(
+//! Event::default().id(13.to_string())
+//! .event("chat")
+//! .data("other chat message\nwith next line")
+//! .retry(Duration::from_millis(5000))
+//! )
+//! ])
+//! }
+//!
+//! 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 serde::Serialize;
+use std::borrow::Cow;
+use std::error::Error as StdError;
+use std::fmt::{self, Write};
+use std::future::Future;
+use std::pin::Pin;
+use std::str::FromStr;
+use std::task::{Context, Poll};
+use std::time::Duration;
+
+use futures_util::{future, Stream, TryStream, TryStreamExt};
+use http::header::{HeaderValue, CACHE_CONTROL, CONTENT_TYPE};
+use hyper::Body;
+use pin_project::pin_project;
+use serde_json::{self, Error};
+use tokio::time::{self, Sleep};
+
+use self::sealed::SseError;
+use super::header;
+use crate::filter::One;
+use crate::reply::Response;
+use crate::{Filter, Rejection, Reply};
+
+// Server-sent event data type
+#[derive(Debug)]
+enum DataType {
+ Text(String),
+ Json(String),
+}
+
+/// Server-sent event
+#[derive(Default, Debug)]
+pub struct Event {
+ id: Option<String>,
+ data: Option<DataType>,
+ event: Option<String>,
+ comment: Option<String>,
+ retry: Option<Duration>,
+}
+
+impl Event {
+ /// Set Server-sent event data
+ /// data field(s) ("data:<content>")
+ pub fn data<T: Into<String>>(mut self, data: T) -> Event {
+ self.data = Some(DataType::Text(data.into()));
+ self
+ }
+
+ /// Set Server-sent event data
+ /// data field(s) ("data:<content>")
+ pub fn json_data<T: Serialize>(mut self, data: T) -> Result<Event, Error> {
+ self.data = Some(DataType::Json(serde_json::to_string(&data)?));
+ Ok(self)
+ }
+
+ /// Set Server-sent event comment
+ /// Comment field (":<comment-text>")
+ pub fn comment<T: Into<String>>(mut self, comment: T) -> Event {
+ self.comment = Some(comment.into());
+ self
+ }
+
+ /// Set Server-sent event event
+ /// Event name field ("event:<event-name>")
+ pub fn event<T: Into<String>>(mut self, event: T) -> Event {
+ self.event = Some(event.into());
+ self
+ }
+
+ /// Set Server-sent event retry
+ /// Retry timeout field ("retry:<timeout>")
+ pub fn retry(mut self, duration: Duration) -> Event {
+ self.retry = Some(duration);
+ self
+ }
+
+ /// Set Server-sent event id
+ /// Identifier field ("id:<identifier>")
+ pub fn id<T: Into<String>>(mut self, id: T) -> Event {
+ self.id = Some(id.into());
+ self
+ }
+}
+
+impl fmt::Display for Event {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ if let Some(ref comment) = &self.comment {
+ ":".fmt(f)?;
+ comment.fmt(f)?;
+ f.write_char('\n')?;
+ }
+
+ if let Some(ref event) = &self.event {
+ "event:".fmt(f)?;
+ event.fmt(f)?;
+ f.write_char('\n')?;
+ }
+
+ match self.data {
+ Some(DataType::Text(ref data)) => {
+ for line in data.split('\n') {
+ "data:".fmt(f)?;
+ line.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ }
+ Some(DataType::Json(ref data)) => {
+ "data:".fmt(f)?;
+ data.fmt(f)?;
+ f.write_char('\n')?;
+ }
+ None => {}
+ }
+
+ if let Some(ref id) = &self.id {
+ "id:".fmt(f)?;
+ id.fmt(f)?;
+ f.write_char('\n')?;
+ }
+
+ if let Some(ref duration) = &self.retry {
+ "retry:".fmt(f)?;
+
+ let secs = duration.as_secs();
+ let millis = duration.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')?;
+ }
+
+ f.write_char('\n')?;
+ Ok(())
+ }
+}
+
+/// 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_util::Stream;
+/// use futures_util::stream::iter;
+/// use std::convert::Infallible;
+/// use warp::{Filter, sse::Event};
+/// use serde_derive::Serialize;
+///
+/// #[derive(Serialize)]
+/// struct Msg {
+/// from: u32,
+/// text: String,
+/// }
+///
+/// fn event_stream() -> impl Stream<Item = Result<Event, Infallible>> {
+/// iter(vec![
+/// // Unnamed event with data only
+/// Ok(Event::default().data("payload")),
+/// // Named event with ID and retry timeout
+/// Ok(
+/// Event::default().data("other message\nwith next line")
+/// .event("chat")
+/// .id(1.to_string())
+/// .retry(Duration::from_millis(15000))
+/// ),
+/// // Event with JSON data
+/// Ok(
+/// Event::default().id(2.to_string())
+/// .json_data(Msg {
+/// from: 2,
+/// text: "hello".into(),
+/// }).unwrap(),
+/// )
+/// ])
+/// }
+///
+/// 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<Ok = Event> + Send + 'static,
+ 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<Ok = Event> + Send + 'static,
+ 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(Ok(event.to_string())));
+
+ 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 = Event, Error = impl StdError + Send + Sync + 'static> + Send + 'static
+ where
+ S: TryStream<Ok = Event> + Send + 'static,
+ S::Error: StdError + Send + Sync + 'static,
+ {
+ let alive_timer = time::sleep(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,
+ #[pin]
+ alive_timer: Sleep,
+}
+
+/// 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_util::StreamExt;
+/// use tokio::time::interval;
+/// use tokio_stream::wrappers::IntervalStream;
+/// use warp::{Filter, Stream, sse::Event};
+///
+/// // create server-sent event
+/// fn sse_counter(counter: u64) -> Result<Event, Infallible> {
+/// Ok(Event::default().data(counter.to_string()))
+/// }
+///
+/// fn main() {
+/// let routes = warp::path("ticks")
+/// .and(warp::get())
+/// .map(|| {
+/// let mut counter: u64 = 0;
+/// let interval = interval(Duration::from_secs(15));
+/// let stream = IntervalStream::new(interval);
+/// let event_stream = stream.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<Ok = Event> + Send + 'static,
+ S::Error: StdError + Send + Sync + 'static,
+{
+ type Item = Result<Event, 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();
+ let event = Event::default().comment(comment_str);
+ Poll::Ready(Some(Ok(event)))
+ }
+ },
+ Poll::Ready(Some(Ok(event))) => {
+ // restart timer
+ pin.alive_timer
+ .reset(tokio::time::Instant::now() + *pin.max_interval);
+ Poll::Ready(Some(Ok(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 fmt::Display for SseError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "sse error")
+ }
+ }
+
+ impl StdError for SseError {}
+}
diff --git a/third_party/rust/warp/src/filters/trace.rs b/third_party/rust/warp/src/filters/trace.rs
new file mode 100644
index 0000000000..5ca4e9df96
--- /dev/null
+++ b/third_party/rust/warp/src/filters/trace.rs
@@ -0,0 +1,309 @@
+//! [`tracing`] filters.
+//!
+//! [`tracing`] is a framework for instrumenting Rust programs to
+//! collect scoped, structured, and async-aware diagnostics. This module
+//! provides a set of filters for instrumenting Warp applications with `tracing`
+//! spans. [`Spans`] can be used to associate individual events with a request,
+//! and track contexts through the application.
+//!
+//! [`tracing`]: https://crates.io/crates/tracing
+//! [`Spans`]: https://docs.rs/tracing/latest/tracing/#spans
+use tracing::Span;
+
+use std::net::SocketAddr;
+
+use http::{self, header};
+
+use crate::filter::{Filter, WrapSealed};
+use crate::reject::IsReject;
+use crate::reply::Reply;
+use crate::route::Route;
+
+use self::internal::WithTrace;
+
+/// Create a wrapping filter that instruments every request with a `tracing`
+/// [`Span`] at the [`INFO`] level, containing a summary of the request.
+/// Additionally, if the [`DEBUG`] level is enabled, the span will contain an
+/// event recording the request's headers.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(warp::trace::request());
+/// ```
+///
+/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
+/// [`INFO`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.INFO
+/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
+pub fn request() -> Trace<impl Fn(Info<'_>) -> Span + Clone> {
+ use tracing::field::{display, Empty};
+ trace(|info: Info<'_>| {
+ let span = tracing::info_span!(
+ "request",
+ remote.addr = Empty,
+ method = %info.method(),
+ path = %info.path(),
+ version = ?info.route.version(),
+ referer = Empty,
+ );
+
+ // Record optional fields.
+ if let Some(remote_addr) = info.remote_addr() {
+ span.record("remote.addr", &display(remote_addr));
+ }
+
+ if let Some(referer) = info.referer() {
+ span.record("referer", &display(referer));
+ }
+
+ tracing::debug!(parent: &span, "received request");
+
+ span
+ })
+}
+
+/// Create a wrapping filter that instruments every request with a custom
+/// `tracing` [`Span`] provided by a function.
+///
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let route = warp::any()
+/// .map(warp::reply)
+/// .with(warp::trace(|info| {
+/// // Create a span using tracing macros
+/// tracing::info_span!(
+/// "request",
+/// method = %info.method(),
+/// path = %info.path(),
+/// )
+/// }));
+/// ```
+///
+/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
+pub fn trace<F>(func: F) -> Trace<F>
+where
+ F: Fn(Info<'_>) -> Span + Clone,
+{
+ Trace { func }
+}
+
+/// Create a wrapping filter that instruments every request with a `tracing`
+/// [`Span`] at the [`DEBUG`] level representing a named context.
+///
+/// This can be used to instrument multiple routes with their own sub-spans in a
+/// per-request trace.
+///
+/// # Example
+///
+/// ```
+/// use warp::Filter;
+///
+/// let hello = warp::path("hello")
+/// .map(warp::reply)
+/// .with(warp::trace::named("hello"));
+///
+/// let goodbye = warp::path("goodbye")
+/// .map(warp::reply)
+/// .with(warp::trace::named("goodbye"));
+///
+/// let routes = hello.or(goodbye);
+/// ```
+///
+/// [`Span`]: https://docs.rs/tracing/latest/tracing/#spans
+/// [`DEBUG`]: https://docs.rs/tracing/0.1.16/tracing/struct.Level.html#associatedconstant.DEBUG
+pub fn named(name: &'static str) -> Trace<impl Fn(Info<'_>) -> Span + Copy> {
+ trace(move |_| tracing::debug_span!("context", "{}", name,))
+}
+
+/// Decorates a [`Filter`](crate::Filter) to create a [`tracing`] [span] for
+/// requests and responses.
+///
+/// [`tracing`]: https://crates.io/crates/tracing
+/// [span]: https://docs.rs/tracing/latest/tracing/#spans
+#[derive(Clone, Copy, Debug)]
+pub struct Trace<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,
+}
+
+impl<FN, F> WrapSealed<F> for Trace<FN>
+where
+ FN: Fn(Info<'_>) -> Span + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+{
+ type Wrapped = WithTrace<FN, F>;
+
+ fn wrap(&self, filter: F) -> Self::Wrapped {
+ WithTrace {
+ filter,
+ trace: 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 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 host of the request
+ pub fn host(&self) -> Option<&str> {
+ self.route
+ .headers()
+ .get(header::HOST)
+ .and_then(|v| v.to_str().ok())
+ }
+
+ /// View the request headers.
+ pub fn request_headers(&self) -> &http::HeaderMap {
+ self.route.headers()
+ }
+}
+
+mod internal {
+ use futures_util::{future::Inspect, future::MapOk, FutureExt, TryFutureExt};
+
+ use super::{Info, Trace};
+ use crate::filter::{Filter, FilterBase, Internal};
+ use crate::reject::IsReject;
+ use crate::reply::Reply;
+ use crate::reply::Response;
+ use crate::route;
+
+ #[allow(missing_debug_implementations)]
+ pub struct Traced(pub(super) Response);
+
+ impl Reply for Traced {
+ #[inline]
+ fn into_response(self) -> Response {
+ self.0
+ }
+ }
+
+ #[allow(missing_debug_implementations)]
+ #[derive(Clone, Copy)]
+ pub struct WithTrace<FN, F> {
+ pub(super) filter: F,
+ pub(super) trace: Trace<FN>,
+ }
+
+ use tracing::instrument::{Instrument, Instrumented};
+ use tracing::Span;
+
+ fn finished_logger<E: IsReject>(reply: &Result<(Traced,), E>) {
+ let (status, error) = match reply {
+ Ok((Traced(resp),)) => (resp.status(), None),
+ Err(error) => (error.status(), Some(error)),
+ };
+
+ if status.is_success() {
+ tracing::info!(
+ target: "warp::filters::trace",
+ status = status.as_u16(),
+ "finished processing with success"
+ );
+ } else if status.is_server_error() {
+ tracing::error!(
+ target: "warp::filters::trace",
+ status = status.as_u16(),
+ error = ?error,
+ "unable to process request (internal error)"
+ );
+ } else if status.is_client_error() {
+ tracing::warn!(
+ target: "warp::filters::trace",
+ status = status.as_u16(),
+ error = ?error,
+ "unable to serve request (client error)"
+ );
+ } else {
+ // Either informational or redirect
+ tracing::info!(
+ target: "warp::filters::trace",
+ status = status.as_u16(),
+ error = ?error,
+ "finished processing with status"
+ );
+ }
+ }
+
+ fn convert_reply<R: Reply>(reply: R) -> (Traced,) {
+ (Traced(reply.into_response()),)
+ }
+
+ impl<FN, F> FilterBase for WithTrace<FN, F>
+ where
+ FN: Fn(Info<'_>) -> Span + Clone + Send,
+ F: Filter + Clone + Send,
+ F::Extract: Reply,
+ F::Error: IsReject,
+ {
+ type Extract = (Traced,);
+ type Error = F::Error;
+ type Future = Instrumented<
+ Inspect<
+ MapOk<F::Future, fn(F::Extract) -> Self::Extract>,
+ fn(&Result<Self::Extract, F::Error>),
+ >,
+ >;
+
+ fn filter(&self, _: Internal) -> Self::Future {
+ let span = route::with(|route| (self.trace.func)(Info { route }));
+ let _entered = span.enter();
+
+ tracing::info!(target: "warp::filters::trace", "processing request");
+ self.filter
+ .filter(Internal)
+ .map_ok(convert_reply as fn(F::Extract) -> Self::Extract)
+ .inspect(finished_logger as fn(&Result<Self::Extract, F::Error>))
+ .instrument(span.clone())
+ }
+ }
+}
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..1e953c7a35
--- /dev/null
+++ b/third_party/rust/warp/src/filters/ws.rs
@@ -0,0 +1,410 @@
+//! Websockets Filters
+
+use std::borrow::Cow;
+use std::fmt;
+use std::future::Future;
+use std::pin::Pin;
+use std::task::{Context, Poll};
+
+use super::header;
+use crate::filter::{filter_fn_one, Filter, One};
+use crate::reject::Rejection;
+use crate::reply::{Reply, Response};
+use futures_util::{future, ready, FutureExt, Sink, Stream, TryFutureExt};
+use headers::{Connection, HeaderMapExt, SecWebsocketAccept, SecWebsocketKey, Upgrade};
+use http;
+use hyper::upgrade::OnUpgrade;
+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(on_upgrade())
+ .map(
+ move |key: SecWebsocketKey, on_upgrade: Option<OnUpgrade>| Ws {
+ config: None,
+ key,
+ on_upgrade,
+ },
+ )
+}
+
+/// Extracted by the [`ws`](ws) filter, and used to finish an upgrade.
+pub struct Ws {
+ config: Option<WebSocketConfig>,
+ key: SecWebsocketKey,
+ on_upgrade: Option<OnUpgrade>,
+}
+
+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
+ }
+
+ /// Set the maximum frame size (defaults to 16 megabytes)
+ pub fn max_frame_size(mut self, max: usize) -> Self {
+ self.config
+ .get_or_insert_with(WebSocketConfig::default)
+ .max_frame_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 {
+ if let Some(on_upgrade) = self.ws.on_upgrade {
+ let on_upgrade_cb = self.on_upgrade;
+ let config = self.ws.config;
+ let fut = on_upgrade
+ .and_then(move |upgraded| {
+ tracing::trace!("websocket upgrade complete");
+ WebSocket::from_raw_socket(upgraded, protocol::Role::Server, config).map(Ok)
+ })
+ .and_then(move |socket| on_upgrade_cb(socket).map(Ok))
+ .map(|result| {
+ if let Err(err) = result {
+ tracing::debug!("ws upgrade error: {}", err);
+ }
+ });
+ ::tokio::task::spawn(fut);
+ } else {
+ tracing::debug!("ws couldn't be upgraded since no upgrade state was present");
+ }
+
+ 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
+ }
+}
+
+// Extracts OnUpgrade state from the route.
+fn on_upgrade() -> impl Filter<Extract = (Option<OnUpgrade>,), Error = Rejection> + Copy {
+ filter_fn_one(|route| future::ready(Ok(route.extensions_mut().remove::<OnUpgrade>())))
+}
+
+/// A websocket `Stream` and `Sink`, provided to `ws` filters.
+///
+/// Ping messages sent from the client will be handled internally by replying with a Pong message.
+/// Close messages need to be handled explicitly: usually by closing the `Sink` end of the
+/// `WebSocket`.
+///
+/// **Note!**
+/// Due to rust futures nature, pings won't be handled until read part of `WebSocket` is polled
+
+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)) => {
+ tracing::debug!("websocket poll error: {}", e);
+ Poll::Ready(Some(Err(crate::Error::new(e))))
+ }
+ None => {
+ tracing::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) => {
+ tracing::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) => {
+ tracing::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.
+///
+/// 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 a new Pong `Message`.
+ ///
+ /// Note that one rarely needs to manually construct a Pong message because the underlying tungstenite socket
+ /// automatically responds to the Ping messages it receives. Manual construction might still be useful in some cases
+ /// like in tests or to send unidirectional heartbeats.
+ pub fn pong<V: Into<Vec<u8>>>(v: V) -> Message {
+ Message {
+ inner: protocol::Message::Pong(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 the close frame (close code and reason)
+ pub fn close_frame(&self) -> Option<(u16, &str)> {
+ if let protocol::Message::Close(Some(ref close_frame)) = self.inner {
+ Some((close_frame.code.into(), close_frame.reason.as_ref()))
+ } else {
+ None
+ }
+ }
+
+ /// 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(_) => &[],
+ protocol::Message::Frame(ref frame) => frame.payload(),
+ }
+ }
+
+ /// 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 From<Message> for Vec<u8> {
+ fn from(m: Message) -> Self {
+ m.into_bytes()
+ }
+}
+
+// ===== Rejections =====
+
+/// Connection header did not include 'upgrade'
+#[derive(Debug)]
+pub struct MissingConnectionUpgrade;
+
+impl fmt::Display for MissingConnectionUpgrade {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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..5350d22156
--- /dev/null
+++ b/third_party/rust/warp/src/generic.rs
@@ -0,0 +1,250 @@
+#[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;
+
+ #[inline]
+ fn combine<T>(self, other: T) -> CombinedTuples<Self, T>
+ where
+ Self: Sized,
+ T: Tuple,
+ Self::HList: Combine<T::HList>,
+ {
+ self.hlist().combine(other.hlist()).flatten()
+ }
+}
+
+pub type CombinedTuples<T, U> =
+ <<<T as Tuple>::HList as Combine<<U as Tuple>::HList>>::Output as HList>::Tuple;
+
+// 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..a965b1d1b8
--- /dev/null
+++ b/third_party/rust/warp/src/lib.rs
@@ -0,0 +1,179 @@
+#![doc(html_root_url = "https://docs.rs/warp/0.3.3")]
+#![deny(missing_docs)]
+#![deny(missing_debug_implementations)]
+#![deny(rust_2018_idioms)]
+#![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`](self::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 = "compression")]
+#[doc(hidden)]
+pub use self::filters::compression;
+#[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,
+ host,
+ 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,
+ trace,
+ // trace() function
+ trace::trace,
+};
+// ws() function
+pub use self::filter::wrap_fn;
+#[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_util::{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..ee2c3ad798
--- /dev/null
+++ b/third_party/rust/warp/src/redirect.rs
@@ -0,0 +1,132 @@
+//! 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};
+
+pub use self::sealed::AsLocation;
+use crate::reply::{self, Reply};
+
+/// A simple `301` permanent 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 `302` found redirect to a different location
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+/// .map(|| {
+/// warp::redirect::found(Uri::from_static("/v2"))
+/// });
+/// ```
+pub fn found(uri: impl AsLocation) -> impl Reply {
+ reply::with_header(StatusCode::FOUND, header::LOCATION, uri.header_value())
+}
+
+/// A simple `303` redirect to a different location.
+///
+/// The HTTP method of the request to the new location will always be `GET`.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+/// .map(|| {
+/// warp::redirect::see_other(Uri::from_static("/v2"))
+/// });
+/// ```
+pub fn see_other(uri: impl AsLocation) -> impl Reply {
+ reply::with_header(StatusCode::SEE_OTHER, header::LOCATION, uri.header_value())
+}
+
+/// A simple `307` temporary redirect to a different location.
+///
+/// This is similar to [`see_other`](fn@see_other) but the HTTP method and the body of the request
+/// to the new location will be the same as the method and body of the current request.
+///
+/// # 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(),
+ )
+}
+
+/// A simple `308` permanent redirect to a different location.
+///
+/// This is similar to [`redirect`](fn@redirect) but the HTTP method of the request to the new
+/// location will be the same as the method of the current request.
+///
+/// # Example
+///
+/// ```
+/// use warp::{http::Uri, Filter};
+///
+/// let route = warp::path("v1")
+/// .map(|| {
+/// warp::redirect::permanent(Uri::from_static("/v2"))
+/// });
+/// ```
+pub fn permanent(uri: impl AsLocation) -> impl Reply {
+ reply::with_header(
+ StatusCode::PERMANENT_REDIRECT,
+ header::LOCATION,
+ uri.header_value(),
+ )
+}
+
+mod sealed {
+ use bytes::Bytes;
+ use http::{header::HeaderValue, Uri};
+
+ /// Trait for redirect locations. Currently only a `Uri` can be used in
+ /// redirect.
+ /// This sealed trait exists 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..fd09188412
--- /dev/null
+++ b/third_party/rust/warp/src/reject.rs
@@ -0,0 +1,844 @@
+//! 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.
+//!
+//! As a request is processed by a Filter chain, the rejections are accumulated into
+//! a list contained by the [`Rejection`](struct.Rejection.html) type. Rejections from
+//! filters can be handled using [`Filter::recover`](../trait.Filter.html#method.recover).
+//! This is a convenient way to map rejections into a [`Reply`](../reply/trait.Reply.html).
+//!
+//! For a more complete example see the
+//! [Rejection Example](https://github.com/seanmonstar/warp/blob/master/examples/rejections.rs)
+//! from the repository.
+//!
+//! # Example
+//!
+//! ```
+//! use warp::{reply, Reply, Filter, reject, Rejection, http::StatusCode};
+//!
+//! #[derive(Debug)]
+//! struct InvalidParameter;
+//!
+//! impl reject::Reject for InvalidParameter {}
+//!
+//! // Custom rejection handler that maps rejections into responses.
+//! async fn handle_rejection(err: Rejection) -> Result<impl Reply, std::convert::Infallible> {
+//! if err.is_not_found() {
+//! Ok(reply::with_status("NOT_FOUND", StatusCode::NOT_FOUND))
+//! } else if let Some(e) = err.find::<InvalidParameter>() {
+//! Ok(reply::with_status("BAD_REQUEST", StatusCode::BAD_REQUEST))
+//! } else {
+//! eprintln!("unhandled rejection: {:?}", err);
+//! Ok(reply::with_status("INTERNAL_SERVER_ERROR", StatusCode::INTERNAL_SERVER_ERROR))
+//! }
+//! }
+//!
+//!
+//! // Filter on `/:id`, but reject with InvalidParameter if the `id` is `0`.
+//! // Recover from this rejection using a custom rejection handler.
+//! let route = warp::path::param()
+//! .and_then(|id: u32| async move {
+//! if id == 0 {
+//! Err(warp::reject::custom(InvalidParameter))
+//! } else {
+//! Ok("id is valid")
+//! }
+//! })
+//! .recover(handle_rejection);
+//! ```
+
+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.
+///
+/// Can be converted into Rejection.
+///
+/// # 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 {
+ matches!(self.reason, Reason::NotFound)
+ }
+}
+
+impl<T: Reject> From<T> for Rejection {
+ #[inline]
+ fn from(err: T) -> Rejection {
+ custom(err)
+ }
+}
+
+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) => {
+ tracing::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 fmt::Display for MissingHeader {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 fmt::Display for InvalidHeader {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 fmt::Display for MissingCookie {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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..74dee278de
--- /dev/null
+++ b/third_party/rust/warp/src/reply.rs
@@ -0,0 +1,584 @@
+//! 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| {
+ tracing::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 = &hearts;</h1>
+/// </body>
+/// </html>
+/// "#;
+///
+/// let route = warp::any()
+/// .map(move || {
+/// warp::reply::html(body)
+/// });
+/// ```
+pub fn html<T>(body: T) -> Html<T>
+where
+ Body: From<T>,
+ T: Send,
+{
+ Html { body }
+}
+
+/// An HTML reply.
+#[allow(missing_debug_implementations)]
+pub 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) => {
+ tracing::error!("with_header value error: {}", err.into());
+ Reply_(::reject::server_error()
+ .into_response())
+ }
+ },
+ Err(err) => {
+ tracing::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) => {
+ let err = err.into();
+ tracing::error!("with_header value error: {}", err);
+ None
+ }
+ },
+ Err(err) => {
+ let err = err.into();
+ tracing::error!("with_header name error: {}", err);
+ 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) => {
+ tracing::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..afbac4d8ba
--- /dev/null
+++ b/third_party/rust/warp/src/route.rs
@@ -0,0 +1,140 @@
+use scoped_tls::scoped_thread_local;
+use std::cell::RefCell;
+use std::mem;
+use std::net::SocketAddr;
+
+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()
+ }
+
+ #[cfg(feature = "websocket")]
+ 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.is_empty() {
+ // malformed path
+ return;
+ } else 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..f1eb33b952
--- /dev/null
+++ b/third_party/rust/warp/src/server.rs
@@ -0,0 +1,576 @@
+#[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_util::{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 tracing::Instrument;
+
+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>) {
+ let (addr, fut) = self.bind_ephemeral(addr);
+ let span = tracing::info_span!("Server::run", ?addr);
+ tracing::info!(parent: &span, "listening on http://{}", addr);
+
+ fut.instrument(span).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())
+ .instrument(tracing::info_span!("Server::run_incoming"))
+ .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);
+
+ tracing::info!("listening with custom incoming");
+
+ fut.await;
+ }
+
+ /// Bind to a socket address, returning a `Future` that can be
+ /// executed on the current 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>) {
+ let addr = addr.into();
+ let srv = match try_bind!(self, &addr) {
+ Ok((_, srv)) => srv,
+ Err(err) => {
+ tracing::error!("error binding to {}: {}", addr, err);
+ return;
+ }
+ };
+
+ srv.map(|result| {
+ if let Err(err) = result {
+ tracing::error!("server error: {}", err)
+ }
+ })
+ .await;
+ }
+
+ /// Bind to a possibly ephemeral socket address.
+ ///
+ /// Returns the bound address and a `Future` that can be executed on
+ /// the current runtime.
+ ///
+ /// # Panics
+ ///
+ /// Panics if we are unable to bind to the provided address.
+ pub fn bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr>,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(self, addr);
+ let srv = srv.map(|result| {
+ if let Err(err) = result {
+ tracing::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
+ /// the current runtime.
+ pub fn try_bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr>,
+ ) -> 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 {
+ tracing::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
+ /// the current runtime.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use warp::Filter;
+ /// use futures_util::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 {
+ tracing::error!("server error: {}", err)
+ }
+ });
+ (addr, fut)
+ }
+
+ /// Create a server with graceful shutdown signal.
+ ///
+ /// When the signal completes, the server will start the graceful shutdown
+ /// process.
+ pub fn try_bind_with_graceful_shutdown(
+ self,
+ addr: impl Into<SocketAddr> + 'static,
+ signal: impl Future<Output = ()> + Send + '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.with_graceful_shutdown(signal).map(|result| {
+ if let Err(err) = result {
+ tracing::error!("server error: {}", err)
+ }
+ });
+
+ Ok((addr, srv))
+ }
+
+ /// 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 the current runtime.
+ pub fn serve_incoming<I>(self, incoming: I) -> impl Future<Output = ()>
+ where
+ I: TryStream + Send,
+ 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)
+ .instrument(tracing::info_span!("Server::serve_incoming"))
+ }
+
+ /// Setup this `Server` with a specific stream of incoming connections and a
+ /// signal to initiate graceful shutdown.
+ ///
+ /// This can be used for Unix Domain Sockets, or TLS, etc.
+ ///
+ /// When the signal completes, the server will start the graceful shutdown
+ /// process.
+ ///
+ /// Returns a `Future` that can be executed on the current runtime.
+ pub fn serve_incoming_with_graceful_shutdown<I>(
+ self,
+ incoming: I,
+ signal: impl Future<Output = ()> + Send + 'static,
+ ) -> impl Future<Output = ()>
+ where
+ I: TryStream + Send,
+ I::Ok: AsyncRead + AsyncWrite + Send + 'static + Unpin,
+ I::Error: Into<Box<dyn StdError + Send + Sync>>,
+ {
+ let incoming = incoming.map_ok(crate::transport::LiftIo);
+ let service = into_service!(self.filter);
+ let pipeline = self.pipeline;
+
+ async move {
+ let srv =
+ HyperServer::builder(hyper::server::accept::from_stream(incoming.into_stream()))
+ .http1_pipeline_flush(pipeline)
+ .serve(service)
+ .with_graceful_shutdown(signal)
+ .await;
+
+ if let Err(err) = srv {
+ tracing::error!("server error: {}", err);
+ }
+ }
+ .instrument(tracing::info_span!(
+ "Server::serve_incoming_with_graceful_shutdown"
+ ))
+ }
+
+ 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 {
+ tracing::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.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ 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.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn cert_path(self, path: impl AsRef<Path>) -> Self {
+ self.with_tls(|tls| tls.cert_path(path))
+ }
+
+ /// Specify the file path to read the trust anchor for optional client authentication.
+ ///
+ /// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
+ /// of the `client_auth_` methods, then client authentication is disabled by default.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn client_auth_optional_path(self, path: impl AsRef<Path>) -> Self {
+ self.with_tls(|tls| tls.client_auth_optional_path(path))
+ }
+
+ /// Specify the file path to read the trust anchor for required client authentication.
+ ///
+ /// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
+ /// `client_auth_` methods, then client authentication is disabled by default.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn client_auth_required_path(self, path: impl AsRef<Path>) -> Self {
+ self.with_tls(|tls| tls.client_auth_required_path(path))
+ }
+
+ /// Specify the in-memory contents of the private key.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ 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.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn cert(self, cert: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.cert(cert.as_ref()))
+ }
+
+ /// Specify the in-memory contents of the trust anchor for optional client authentication.
+ ///
+ /// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
+ /// of the `client_auth_` methods, then client authentication is disabled by default.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn client_auth_optional(self, trust_anchor: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.client_auth_optional(trust_anchor.as_ref()))
+ }
+
+ /// Specify the in-memory contents of the trust anchor for required client authentication.
+ ///
+ /// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
+ /// `client_auth_` methods, then client authentication is disabled by default.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn client_auth_required(self, trust_anchor: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.client_auth_required(trust_anchor.as_ref()))
+ }
+
+ /// Specify the DER-encoded OCSP response.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn ocsp_resp(self, resp: impl AsRef<[u8]>) -> Self {
+ self.with_tls(|tls| tls.ocsp_resp(resp.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>) {
+ let (addr, fut) = self.bind_ephemeral(addr);
+ let span = tracing::info_span!("TlsServer::run", %addr);
+ tracing::info!(parent: &span, "listening on https://{}", addr);
+
+ fut.instrument(span).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>) {
+ 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
+ /// the current runtime.
+ ///
+ /// *This function requires the `"tls"` feature.*
+ pub fn bind_ephemeral(
+ self,
+ addr: impl Into<SocketAddr>,
+ ) -> (SocketAddr, impl Future<Output = ()> + 'static) {
+ let (addr, srv) = bind!(tls: self, addr);
+ let srv = srv.map(|result| {
+ if let Err(err) = result {
+ tracing::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 {
+ tracing::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..3c67c34d84
--- /dev/null
+++ b/third_party/rust/warp/src/test.rs
@@ -0,0 +1,764 @@
+//! 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;
+//! #[tokio::test]
+//! async 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)
+//! .await
+//! .unwrap();
+//! assert_eq!(value, 3);
+//!
+//! // Or simply test if a request matches (doesn't reject).
+//! assert!(
+//! warp::test::request()
+//! .path("/1/-5")
+//! .matches(&filter)
+//! .await
+//! );
+//! }
+//! ```
+//!
+//! 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::Context;
+#[cfg(feature = "websocket")]
+use std::task::{self, Poll};
+
+use bytes::Bytes;
+#[cfg(feature = "websocket")]
+use futures_channel::mpsc;
+#[cfg(feature = "websocket")]
+use futures_util::StreamExt;
+use futures_util::{future, FutureExt, TryFutureExt};
+use http::{
+ header::{HeaderName, HeaderValue},
+ Response,
+};
+use serde::Serialize;
+use serde_json;
+#[cfg(feature = "websocket")]
+use tokio::sync::oneshot;
+
+use crate::filter::Filter;
+#[cfg(feature = "websocket")]
+use crate::filters::ws::Message;
+use crate::reject::IsReject;
+use crate::reply::Reply;
+use crate::route::{self, Route};
+use crate::Request;
+#[cfg(feature = "websocket")]
+use crate::{Sink, Stream};
+
+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
+ }
+
+ /// Set the remote address of this request
+ ///
+ /// Default is no remote address.
+ ///
+ /// # Example
+ /// ```
+ /// use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+ ///
+ /// let req = warp::test::request()
+ /// .remote_addr(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080));
+ /// ```
+ pub fn remote_addr(mut self, addr: SocketAddr) -> Self {
+ self.remote_addr = Some(addr);
+ 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) => {
+ tracing::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))
+ }),
+ );
+
+ 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 the provided filter.
+ ///
+ /// If the handshake succeeds, returns a `WsClient`.
+ ///
+ /// # Example
+ ///
+ /// ```no_run
+ /// use futures_util::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();
+ let (rd_tx, rd_rx) = mpsc::unbounded();
+
+ 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 query_string = match req.uri().query() {
+ Some(q) => format!("?{}", q),
+ None => String::from(""),
+ };
+
+ let uri = format!("http://{}{}{}", addr, req.uri().path(), query_string)
+ .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(hyper::upgrade::on);
+
+ 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.unbounded_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.unbounded_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(|result| 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(())
+ })
+ }
+
+ fn pinned_tx(self: Pin<&mut Self>) -> Pin<&mut mpsc::UnboundedSender<crate::ws::Message>> {
+ let this = Pin::into_inner(self);
+ Pin::new(&mut this.tx)
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl fmt::Debug for WsClient {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("WsClient").finish()
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl Sink<crate::ws::Message> for WsClient {
+ type Error = WsError;
+
+ fn poll_ready(
+ self: Pin<&mut Self>,
+ context: &mut Context<'_>,
+ ) -> Poll<Result<(), Self::Error>> {
+ self.pinned_tx().poll_ready(context).map_err(WsError::new)
+ }
+
+ fn start_send(self: Pin<&mut Self>, message: Message) -> Result<(), Self::Error> {
+ self.pinned_tx().start_send(message).map_err(WsError::new)
+ }
+
+ fn poll_flush(
+ self: Pin<&mut Self>,
+ context: &mut Context<'_>,
+ ) -> Poll<Result<(), Self::Error>> {
+ self.pinned_tx().poll_flush(context).map_err(WsError::new)
+ }
+
+ fn poll_close(
+ self: Pin<&mut Self>,
+ context: &mut Context<'_>,
+ ) -> Poll<Result<(), Self::Error>> {
+ self.pinned_tx().poll_close(context).map_err(WsError::new)
+ }
+}
+
+#[cfg(feature = "websocket")]
+impl Stream for WsClient {
+ type Item = Result<crate::ws::Message, WsError>;
+
+ fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
+ let this = Pin::into_inner(self);
+ let rx = Pin::new(&mut this.rx);
+ match rx.poll_next(context) {
+ Poll::Ready(Some(result)) => Poll::Ready(Some(result.map_err(WsError::new))),
+ Poll::Ready(None) => Poll::Ready(None),
+ Poll::Pending => Poll::Pending,
+ }
+ }
+}
+
+// ===== 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..1f81a6bd21
--- /dev/null
+++ b/third_party/rust/warp/src/tls.rs
@@ -0,0 +1,411 @@
+use std::fmt;
+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, ReadBuf};
+
+use futures_util::ready;
+use hyper::server::accept::Accept;
+use hyper::server::conn::{AddrIncoming, AddrStream};
+
+use crate::transport::Transport;
+use tokio_rustls::rustls::{
+ server::{AllowAnyAnonymousOrAuthenticatedClient, AllowAnyAuthenticatedClient, NoClientAuth},
+ Certificate, Error as TlsError, PrivateKey, RootCertStore, ServerConfig,
+};
+
+/// 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 fmt::Display for TlsConfigError {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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 {}
+
+/// Tls client authentication configuration.
+pub(crate) enum TlsClientAuth {
+ /// No client auth.
+ Off,
+ /// Allow any anonymous or authenticated client.
+ Optional(Box<dyn Read + Send + Sync>),
+ /// Allow any authenticated client.
+ Required(Box<dyn Read + Send + Sync>),
+}
+
+/// 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>,
+ client_auth: TlsClientAuth,
+ ocsp_resp: Vec<u8>,
+}
+
+impl fmt::Debug for TlsConfigBuilder {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> 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()),
+ client_auth: TlsClientAuth::Off,
+ ocsp_resp: Vec::new(),
+ }
+ }
+
+ /// 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
+ }
+
+ /// Sets the trust anchor for optional Tls client authentication via file path.
+ ///
+ /// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
+ /// of the `client_auth_` methods, then client authentication is disabled by default.
+ pub(crate) fn client_auth_optional_path(mut self, path: impl AsRef<Path>) -> Self {
+ let file = Box::new(LazyFile {
+ path: path.as_ref().into(),
+ file: None,
+ });
+ self.client_auth = TlsClientAuth::Optional(file);
+ self
+ }
+
+ /// Sets the trust anchor for optional Tls client authentication via bytes slice.
+ ///
+ /// Anonymous and authenticated clients will be accepted. If no trust anchor is provided by any
+ /// of the `client_auth_` methods, then client authentication is disabled by default.
+ pub(crate) fn client_auth_optional(mut self, trust_anchor: &[u8]) -> Self {
+ let cursor = Box::new(Cursor::new(Vec::from(trust_anchor)));
+ self.client_auth = TlsClientAuth::Optional(cursor);
+ self
+ }
+
+ /// Sets the trust anchor for required Tls client authentication via file path.
+ ///
+ /// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
+ /// `client_auth_` methods, then client authentication is disabled by default.
+ pub(crate) fn client_auth_required_path(mut self, path: impl AsRef<Path>) -> Self {
+ let file = Box::new(LazyFile {
+ path: path.as_ref().into(),
+ file: None,
+ });
+ self.client_auth = TlsClientAuth::Required(file);
+ self
+ }
+
+ /// Sets the trust anchor for required Tls client authentication via bytes slice.
+ ///
+ /// Only authenticated clients will be accepted. If no trust anchor is provided by any of the
+ /// `client_auth_` methods, then client authentication is disabled by default.
+ pub(crate) fn client_auth_required(mut self, trust_anchor: &[u8]) -> Self {
+ let cursor = Box::new(Cursor::new(Vec::from(trust_anchor)));
+ self.client_auth = TlsClientAuth::Required(cursor);
+ self
+ }
+
+ /// sets the DER-encoded OCSP response
+ pub(crate) fn ocsp_resp(mut self, ocsp_resp: &[u8]) -> Self {
+ self.ocsp_resp = Vec::from(ocsp_resp);
+ self
+ }
+
+ pub(crate) fn build(mut self) -> Result<ServerConfig, TlsConfigError> {
+ let mut cert_rdr = BufReader::new(self.cert);
+ let cert = rustls_pemfile::certs(&mut cert_rdr)
+ .map_err(|_e| TlsConfigError::CertParseError)?
+ .into_iter()
+ .map(Certificate)
+ .collect();
+
+ 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 = rustls_pemfile::pkcs8_private_keys(&mut key_vec.as_slice())
+ .map_err(|_e| TlsConfigError::Pkcs8ParseError)?;
+
+ if !pkcs8.is_empty() {
+ PrivateKey(pkcs8.remove(0))
+ } else {
+ let mut rsa = rustls_pemfile::rsa_private_keys(&mut key_vec.as_slice())
+ .map_err(|_e| TlsConfigError::RsaParseError)?;
+
+ if !rsa.is_empty() {
+ PrivateKey(rsa.remove(0))
+ } else {
+ return Err(TlsConfigError::EmptyKey);
+ }
+ }
+ };
+
+ fn read_trust_anchor(
+ trust_anchor: Box<dyn Read + Send + Sync>,
+ ) -> Result<RootCertStore, TlsConfigError> {
+ let trust_anchors = {
+ let mut reader = BufReader::new(trust_anchor);
+ rustls_pemfile::certs(&mut reader).map_err(TlsConfigError::Io)?
+ };
+
+ let mut store = RootCertStore::empty();
+ let (added, _skipped) = store.add_parsable_certificates(&trust_anchors);
+ if added == 0 {
+ return Err(TlsConfigError::CertParseError);
+ }
+
+ Ok(store)
+ }
+
+ let client_auth = match self.client_auth {
+ TlsClientAuth::Off => NoClientAuth::new(),
+ TlsClientAuth::Optional(trust_anchor) => {
+ AllowAnyAnonymousOrAuthenticatedClient::new(read_trust_anchor(trust_anchor)?)
+ }
+ TlsClientAuth::Required(trust_anchor) => {
+ AllowAnyAuthenticatedClient::new(read_trust_anchor(trust_anchor)?)
+ }
+ };
+
+ let mut config = ServerConfig::builder()
+ .with_safe_defaults()
+ .with_client_cert_verifier(client_auth.into())
+ .with_single_cert_with_ocsp_and_sct(cert, key, self.ocsp_resp, Vec::new())
+ .map_err(TlsConfigError::InvalidKey)?;
+ config.alpn_protocols = vec!["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> {
+ Some(self.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,
+ remote_addr: SocketAddr,
+}
+
+impl TlsStream {
+ fn new(stream: AddrStream, config: Arc<ServerConfig>) -> TlsStream {
+ let remote_addr = stream.remote_addr();
+ let accept = tokio_rustls::TlsAcceptor::from(config).accept(stream);
+ TlsStream {
+ state: State::Handshaking(accept),
+ remote_addr,
+ }
+ }
+}
+
+impl AsyncRead for TlsStream {
+ fn poll_read(
+ self: Pin<&mut Self>,
+ cx: &mut Context<'_>,
+ buf: &mut ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ 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..be553e706e
--- /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, ReadBuf};
+
+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 ReadBuf<'_>,
+ ) -> Poll<io::Result<()>> {
+ 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/addr.rs b/third_party/rust/warp/tests/addr.rs
new file mode 100644
index 0000000000..12fc46936f
--- /dev/null
+++ b/third_party/rust/warp/tests/addr.rs
@@ -0,0 +1,24 @@
+#![deny(warnings)]
+
+use std::net::{IpAddr, Ipv4Addr, SocketAddr};
+
+#[tokio::test]
+async fn remote_addr_missing() {
+ let extract_remote_addr = warp::addr::remote();
+
+ let req = warp::test::request();
+ let resp = req.filter(&extract_remote_addr).await.unwrap();
+ assert_eq!(resp, None)
+}
+
+#[tokio::test]
+async fn remote_addr_present() {
+ let extract_remote_addr = warp::addr::remote();
+
+ let req = warp::test::request().remote_addr("1.2.3.4:5678".parse().unwrap());
+ let resp = req.filter(&extract_remote_addr).await.unwrap();
+ assert_eq!(
+ resp,
+ Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 2, 3, 4)), 5678))
+ )
+}
diff --git a/third_party/rust/warp/tests/body.rs b/third_party/rust/warp/tests/body.rs
new file mode 100644
index 0000000000..112eaff141
--- /dev/null
+++ b/third_party/rust/warp/tests/body.rs
@@ -0,0 +1,202 @@
+#![deny(warnings)]
+
+use bytes::Buf;
+use futures_util::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].chunk(), 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..4cf286a3e1
--- /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::<String>("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::<String>("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::<String>("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..977eab06fc
--- /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..4faa933d5e
--- /dev/null
+++ b/third_party/rust/warp/tests/fs.rs
@@ -0,0 +1,271 @@
+#![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);
+
+ let malformed_req = warp::test::request().path("todos.rs");
+ assert_eq!(malformed_req.reply(&file).await.status(), 404);
+}
+
+#[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", "Mon, 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", "Mon, 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", "Mon, 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);
+}
+
+#[tokio::test]
+async fn byte_ranges_with_excluded_file_size() {
+ let _ = pretty_env_logger::try_init();
+
+ let contents = fs::read("README.md").expect("fs::read README.md");
+ let file = warp::fs::file("README.md");
+
+ // range including end of file (non-inclusive result)
+ let res = warp::test::request()
+ .header("range", format!("bytes=100-{}", contents.len()))
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 206);
+ assert_eq!(
+ res.headers()["content-range"],
+ format!("bytes 100-{}/{}", contents.len() - 1, contents.len())
+ );
+ assert_eq!(
+ res.headers()["content-length"],
+ format!("{}", contents.len() - 100)
+ );
+ assert_eq!(res.body(), &contents[100..=contents.len() - 1]);
+
+ // range with 1 byte to end yields same result as above. (inclusive result)
+ let res = warp::test::request()
+ .header("range", format!("bytes=100-{}", contents.len() - 1))
+ .reply(&file)
+ .await;
+ assert_eq!(res.status(), 206);
+ assert_eq!(
+ res.headers()["content-range"],
+ format!("bytes 100-{}/{}", contents.len() - 1, contents.len())
+ );
+ assert_eq!(
+ res.headers()["content-length"],
+ format!("{}", contents.len() - 100)
+ );
+ assert_eq!(res.body(), &contents[100..=contents.len() - 1]);
+}
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/host.rs b/third_party/rust/warp/tests/host.rs
new file mode 100644
index 0000000000..2fb5f42475
--- /dev/null
+++ b/third_party/rust/warp/tests/host.rs
@@ -0,0 +1,87 @@
+#![deny(warnings)]
+use warp::host::Authority;
+
+#[tokio::test]
+async fn exact() {
+ let filter = warp::host::exact("known.com");
+
+ // no authority
+ let req = warp::test::request();
+ assert!(req.filter(&filter).await.unwrap_err().is_not_found());
+
+ // specified in URI
+ let req = warp::test::request().path("http://known.com/about-us");
+ assert!(req.filter(&filter).await.is_ok());
+
+ let req = warp::test::request().path("http://unknown.com/about-us");
+ assert!(req.filter(&filter).await.unwrap_err().is_not_found());
+
+ // specified in Host header
+ let req = warp::test::request()
+ .header("host", "known.com")
+ .path("/about-us");
+ assert!(req.filter(&filter).await.is_ok());
+
+ let req = warp::test::request()
+ .header("host", "unknown.com")
+ .path("/about-us");
+ assert!(req.filter(&filter).await.unwrap_err().is_not_found());
+
+ // specified in both - matching
+ let req = warp::test::request()
+ .header("host", "known.com")
+ .path("http://known.com/about-us");
+ assert!(req.filter(&filter).await.is_ok());
+
+ let req = warp::test::request()
+ .header("host", "unknown.com")
+ .path("http://unknown.com/about-us");
+ assert!(req.filter(&filter).await.unwrap_err().is_not_found());
+
+ // specified in both - mismatch
+ let req = warp::test::request()
+ .header("host", "known.com")
+ .path("http://known2.com/about-us");
+ assert!(req
+ .filter(&filter)
+ .await
+ .unwrap_err()
+ .find::<warp::reject::InvalidHeader>()
+ .is_some());
+
+ // bad host header - invalid chars
+ let req = warp::test::request()
+ .header("host", "😭")
+ .path("http://known.com/about-us");
+ assert!(req
+ .filter(&filter)
+ .await
+ .unwrap_err()
+ .find::<warp::reject::InvalidHeader>()
+ .is_some());
+
+ // bad host header - invalid format
+ let req = warp::test::request()
+ .header("host", "hello space.com")
+ .path("http://known.com//about-us");
+ assert!(req
+ .filter(&filter)
+ .await
+ .unwrap_err()
+ .find::<warp::reject::InvalidHeader>()
+ .is_some());
+}
+
+#[tokio::test]
+async fn optional() {
+ let filter = warp::host::optional();
+
+ let req = warp::test::request().header("host", "example.com");
+ assert_eq!(
+ req.filter(&filter).await.unwrap(),
+ Some(Authority::from_static("example.com"))
+ );
+
+ let req = warp::test::request();
+ assert_eq!(req.filter(&filter).await.unwrap(), None);
+}
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..3172367bcb
--- /dev/null
+++ b/third_party/rust/warp/tests/multipart.rs
@@ -0,0 +1,54 @@
+#![deny(warnings)]
+use bytes::BufMut;
+use futures_util::{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..4f9302036e
--- /dev/null
+++ b/third_party/rust/warp/tests/path.rs
@@ -0,0 +1,411 @@
+#![deny(warnings)]
+#[macro_use]
+extern crate warp;
+
+use futures_util::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);
+
+ let ex = warp::test::request()
+ .path("localhost")
+ .filter(&tail)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/");
+}
+
+#[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);
+
+ // Empty
+
+ let req = warp::test::request().path("/");
+ let p = path!();
+ assert!(req.matches(&p).await);
+
+ let req = warp::test::request().path("/foo");
+ let p = path!();
+ 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);
+
+ // doesn't panic on authority-form
+ let ex = warp::test::request()
+ .path("localhost:1234")
+ .filter(&full_path)
+ .await
+ .unwrap();
+ assert_eq!(ex.as_str(), "/");
+}
+
+#[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..a20f104e67
--- /dev/null
+++ b/third_party/rust/warp/tests/query.rs
@@ -0,0 +1,139 @@
+#![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 query_struct_no_values() {
+ let as_struct = warp::query::<MyArgs>();
+
+ let req = warp::test::request().path("/?foo&baz");
+
+ let extracted = req.filter(&as_struct).await.unwrap();
+ assert_eq!(
+ extracted,
+ MyArgs {
+ foo: Some("".into()),
+ baz: Some("".into())
+ }
+ );
+}
+
+#[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..be7fd33e4a
--- /dev/null
+++ b/third_party/rust/warp/tests/redirect.rs
@@ -0,0 +1,57 @@
+#![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");
+}
+
+#[tokio::test]
+async fn redirect_found_uri() {
+ let over_there = warp::any().map(|| warp::redirect::found(Uri::from_static("/over-there")));
+
+ let req = warp::test::request();
+ let resp = req.reply(&over_there).await;
+
+ assert_eq!(resp.status(), 302);
+ assert_eq!(resp.headers()["location"], "/over-there");
+}
+
+#[tokio::test]
+async fn redirect_see_other_uri() {
+ let over_there = warp::any().map(|| warp::redirect::see_other(Uri::from_static("/over-there")));
+
+ let req = warp::test::request();
+ let resp = req.reply(&over_there).await;
+
+ assert_eq!(resp.status(), 303);
+ assert_eq!(resp.headers()["location"], "/over-there");
+}
+
+#[tokio::test]
+async fn redirect_temporary_uri() {
+ let over_there = warp::any().map(|| warp::redirect::temporary(Uri::from_static("/over-there")));
+
+ let req = warp::test::request();
+ let resp = req.reply(&over_there).await;
+
+ assert_eq!(resp.status(), 307);
+ assert_eq!(resp.headers()["location"], "/over-there");
+}
+
+#[tokio::test]
+async fn redirect_permanent_uri() {
+ let over_there = warp::any().map(|| warp::redirect::permanent(Uri::from_static("/over-there")));
+
+ let req = warp::test::request();
+ let resp = req.reply(&over_there).await;
+
+ assert_eq!(resp.status(), 308);
+ 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/tracing.rs b/third_party/rust/warp/tests/tracing.rs
new file mode 100644
index 0000000000..0ae88fbc38
--- /dev/null
+++ b/third_party/rust/warp/tests/tracing.rs
@@ -0,0 +1,48 @@
+use warp::Filter;
+
+#[tokio::test]
+async fn uses_tracing() {
+ // Setup a log subscriber (responsible to print to output)
+ let subscriber = tracing_subscriber::fmt::Subscriber::builder()
+ .with_env_filter("trace")
+ .without_time()
+ .finish();
+
+ // Set the previously created subscriber as the global subscriber
+ tracing::subscriber::set_global_default(subscriber).unwrap();
+ // Redirect normal log messages to the tracing subscriber
+ tracing_log::LogTracer::init().unwrap();
+
+ // Start a span with some metadata (fields)
+ let span = tracing::info_span!("app", domain = "www.example.org");
+ let _guard = span.enter();
+
+ log::info!("logged using log macro");
+
+ let ok = warp::any()
+ .map(|| {
+ tracing::info!("printed for every request");
+ })
+ .untuple_one()
+ .and(warp::path("aa"))
+ .map(|| {
+ tracing::info!("only printed when path '/aa' matches");
+ })
+ .untuple_one()
+ .map(warp::reply)
+ // Here we add the tracing logger which will ensure that all requests has a span with
+ // useful information about the request (method, url, version, remote_addr, etc.)
+ .with(warp::trace::request());
+
+ tracing::info!("logged using tracing macro");
+
+ // Send a request for /
+ let req = warp::test::request();
+ let resp = req.reply(&ok);
+ assert_eq!(resp.await.status(), 404);
+
+ // Send a request for /aa
+ let req = warp::test::request().path("/aa");
+ let resp = req.reply(&ok);
+ assert_eq!(resp.await.status(), 200);
+}
diff --git a/third_party/rust/warp/tests/ws.rs b/third_party/rust/warp/tests/ws.rs
new file mode 100644
index 0000000000..d5b60356e4
--- /dev/null
+++ b/third_party/rust/warp/tests/ws.rs
@@ -0,0 +1,288 @@
+#![deny(warnings)]
+
+use futures_util::{FutureExt, SinkExt, StreamExt};
+use serde_derive::Deserialize;
+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 wsclient_sink_and_stream() {
+ let _ = pretty_env_logger::try_init();
+
+ let mut client = warp::test::ws()
+ .handshake(ws_echo())
+ .await
+ .expect("handshake");
+
+ let message = warp::ws::Message::text("hello");
+ SinkExt::send(&mut client, message.clone()).await.unwrap();
+ let received_message = client.next().await.unwrap().unwrap();
+ assert_eq!(message, received_message);
+}
+
+#[tokio::test]
+async fn close_frame() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = warp::ws().map(|ws: warp::ws::Ws| {
+ ws.on_upgrade(|mut websocket| async move {
+ let msg = websocket.next().await.expect("item").expect("ok");
+ let _ = msg.close_frame().expect("close frame");
+ })
+ });
+
+ let client = warp::test::ws().handshake(route).await.expect("handshake");
+ drop(client);
+}
+
+#[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 pongs_only() {
+ let _ = pretty_env_logger::try_init();
+
+ let mut client = warp::test::ws()
+ .handshake(ws_echo())
+ .await
+ .expect("handshake");
+
+ // construct a pong message and make sure it is correct
+ let msg = Message::pong("clt");
+ assert!(msg.is_pong());
+ assert_eq!(msg.as_bytes(), &b"clt"[..]);
+
+ // send it to echo and wait for `ws_echo` to send it back
+ client.send(msg).await;
+
+ 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());
+}
+
+#[tokio::test]
+async fn limit_frame_size() {
+ let _ = pretty_env_logger::try_init();
+
+ let echo = warp::ws().map(|ws: warp::ws::Ws| {
+ ws.max_frame_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 length too big: 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());
+}
+
+#[derive(Deserialize)]
+struct MyQuery {
+ hello: String,
+}
+
+#[tokio::test]
+async fn ws_with_query() {
+ let ws_filter = warp::path("my-ws")
+ .and(warp::query::<MyQuery>())
+ .and(warp::ws())
+ .map(|query: MyQuery, ws: warp::ws::Ws| {
+ assert_eq!(query.hello, "world");
+
+ ws.on_upgrade(|websocket| {
+ let (tx, rx) = websocket.split();
+ rx.inspect(|i| log::debug!("ws recv: {:?}", i))
+ .forward(tx)
+ .map(|_| ())
+ })
+ });
+
+ warp::test::ws()
+ .path("/my-ws?hello=world")
+ .handshake(ws_filter)
+ .await
+ .expect("handshake");
+}
+
+// Websocket filter that echoes all messages back.
+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(|_| ())
+ })
+ })
+}