summaryrefslogtreecommitdiffstats
path: root/third_party/rust/warp/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/rust/warp/tests
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/warp/tests')
-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.rs102
-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
17 files changed, 2236 insertions, 0 deletions
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..ed98232be3
--- /dev/null
+++ b/third_party/rust/warp/tests/multipart.rs
@@ -0,0 +1,102 @@
+#![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");
+}
+
+#[tokio::test]
+async fn max_length_is_enforced() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = multipart::form()
+ .and_then(|_: multipart::FormData| async { Ok::<(), warp::Rejection>(()) });
+
+ let boundary = "--abcdef1234--";
+
+ let req = warp::test::request()
+ .method("POST")
+ // Note no content-length header
+ .header("transfer-encoding", "chunked")
+ .header(
+ "content-type",
+ format!("multipart/form-data; boundary={}", boundary),
+ );
+
+ // Intentionally don't add body, as it automatically also adds
+ // content-length header
+ let resp = req.filter(&route).await;
+ assert!(resp.is_err());
+}
+
+#[tokio::test]
+async fn max_length_can_be_disabled() {
+ let _ = pretty_env_logger::try_init();
+
+ let route = multipart::form()
+ .max_length(None)
+ .and_then(|_: multipart::FormData| async { Ok::<(), warp::Rejection>(()) });
+
+ let boundary = "--abcdef1234--";
+
+ let req = warp::test::request()
+ .method("POST")
+ .header("transfer-encoding", "chunked")
+ .header(
+ "content-type",
+ format!("multipart/form-data; boundary={}", boundary),
+ );
+
+ // Intentionally don't add body, as it automatically also adds
+ // content-length header
+ let resp = req.filter(&route).await;
+ assert!(resp.is_ok());
+}
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..cf87feda33
--- /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()
+ .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..4e57e7f70e
--- /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(|_| ())
+ })
+ })
+}