#![allow(
missing_docs,
clippy::unwrap_used,
clippy::expect_used,
clippy::uninlined_format_args,
clippy::items_after_statements,
clippy::use_self,
clippy::semicolon_if_nothing_returned,
clippy::similar_names
)]
use bytes::Bytes;
use http_body_util::Full;
use hyper::{Request, Response, StatusCode};
use std::convert::Infallible;
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::task::{Context, Poll};
use tachyon_web::Router;
use tachyon_web::http::response::Body;
use tower::Service;
#[derive(Clone)]
struct EchoUriService {
last_uri: Arc<Mutex<String>>,
}
impl Service<Request<Bytes>> for EchoUriService {
type Response = Response<Full<Bytes>>;
type Error = Infallible;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request<Bytes>) -> Self::Future {
let uri = req.uri().to_string();
self.last_uri.lock().unwrap().clone_from(&uri);
Box::pin(async move { Ok(Response::new(Full::new(Bytes::from(uri)))) })
}
}
async fn body_to_string(body: Body) -> String {
use http_body_util::BodyExt;
let bytes = body.collect().await.unwrap().to_bytes();
String::from_utf8_lossy(&bytes).into_owned()
}
#[tokio::test]
async fn test_nest_service_preserves_query_string() {
let last_uri = Arc::new(Mutex::new(String::new()));
let svc = EchoUriService {
last_uri: last_uri.clone(),
};
let router = Router::new()
.nest_service("/api", svc)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder()
.method("GET")
.uri("/api/users/42?page=2&sort=asc")
.body(Body::empty())
.unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let seen = last_uri.lock().unwrap().clone();
assert_eq!(seen, "/users/42?page=2&sort=asc");
}
#[tokio::test]
async fn test_nest_service_no_query_string() {
let last_uri = Arc::new(Mutex::new(String::new()));
let svc = EchoUriService {
last_uri: last_uri.clone(),
};
let router = Router::new()
.nest_service("/api", svc)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder()
.method("GET")
.uri("/api/users/42")
.body(Body::empty())
.unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let seen = last_uri.lock().unwrap().clone();
assert_eq!(seen, "/users/42");
}
#[tokio::test]
async fn test_nest_service_does_not_synthesize_query_from_encoded_path() {
let last_uri = Arc::new(Mutex::new(String::new()));
let svc = EchoUriService {
last_uri: last_uri.clone(),
};
let router = Router::new()
.nest_service("/api", svc)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder()
.method("GET")
.uri("/api/foo%3Fadmin=1")
.body(Body::empty())
.unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let seen = last_uri.lock().unwrap().clone();
assert_eq!(seen, "/foo%3Fadmin=1");
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "/foo%3Fadmin=1");
}
#[tokio::test]
async fn test_compiled_router_oneshot() {
use tachyon_web::get;
use tower::ServiceExt;
async fn hello() -> &'static str {
"hello from oneshot"
}
let router = Router::new()
.route("/", get(hello))
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder()
.method("GET")
.uri("/")
.body(Body::empty())
.unwrap();
let resp = router.oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "hello from oneshot");
}
#[derive(Clone)]
struct AlwaysFailsReady;
impl Service<Request<Bytes>> for AlwaysFailsReady {
type Response = Response<Full<Bytes>>;
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 Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Err(std::io::Error::other("never ready")))
}
fn call(&mut self, _req: Request<Bytes>) -> Self::Future {
unreachable!("poll_ready always errors, so ready() never lets call() run")
}
}
#[derive(Clone)]
struct AlwaysFailsCall;
impl Service<Request<Bytes>> for AlwaysFailsCall {
type Response = Response<Full<Bytes>>;
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 Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _req: Request<Bytes>) -> Self::Future {
Box::pin(async { Err(std::io::Error::other("call always fails")) })
}
}
#[derive(Clone)]
struct PassThroughLayer;
impl<S> tower::Layer<S> for PassThroughLayer {
type Service = S;
fn layer(&self, inner: S) -> S {
inner
}
}
#[derive(Clone)]
struct AlwaysFailsReadyLayer;
impl<S> tower::Layer<S> for AlwaysFailsReadyLayer {
type Service = AlwaysFailsReady;
fn layer(&self, _inner: S) -> AlwaysFailsReady {
AlwaysFailsReady
}
}
#[derive(Clone)]
struct AlwaysFailsCallLayer;
impl<S> tower::Layer<S> for AlwaysFailsCallLayer {
type Service = AlwaysFailsCall;
fn layer(&self, _inner: S) -> AlwaysFailsCall {
AlwaysFailsCall
}
}
#[tokio::test]
async fn test_layer_wraps_every_route_via_tower_layer() {
use tachyon_web::get;
async fn hello() -> &'static str {
"hello from layer"
}
let router = Router::new()
.route("/", get(hello))
.layer(PassThroughLayer)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "hello from layer");
}
#[tokio::test]
async fn test_route_layer_does_not_wrap_the_fallback() {
use tachyon_web::get;
async fn hello() -> &'static str {
"hello from route_layer"
}
async fn fallback() -> &'static str {
"fallback"
}
let router = Router::new()
.route("/", get(hello))
.fallback(fallback)
.route_layer(PassThroughLayer)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "hello from route_layer");
let miss_req = Request::builder()
.uri("/nowhere")
.body(Body::empty())
.unwrap();
let miss_resp = router.handle_request(miss_req).await;
assert_eq!(miss_resp.status(), StatusCode::OK);
let miss_body = body_to_string(miss_resp.into_body()).await;
assert_eq!(miss_body, "fallback");
}
#[tokio::test]
async fn test_layer_surfaces_service_not_ready_as_500() {
use tachyon_web::get;
async fn hello() -> &'static str {
"unreachable"
}
let router = Router::new()
.route("/", get(hello))
.layer(AlwaysFailsReadyLayer)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_layer_surfaces_service_call_failure_as_500() {
use tachyon_web::get;
async fn hello() -> &'static str {
"unreachable"
}
let router = Router::new()
.route("/", get(hello))
.layer(AlwaysFailsCallLayer)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_route_service_rejects_oversized_body() {
let last_uri = Arc::new(Mutex::new(String::new()));
let svc = EchoUriService { last_uri };
let router = Router::new()
.route_service("/echo", svc)
.with_state::<()>(())
.compile()
.expect("compile");
let oversized = vec![0u8; 2 * 1024 * 1024 + 1];
let req = Request::builder()
.method("POST")
.uri("/echo")
.body(Body::full(Bytes::from(oversized)))
.unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
}
#[tokio::test]
async fn test_route_service_surfaces_service_not_ready_as_500() {
let router = Router::new()
.route_service("/svc", AlwaysFailsReady)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/svc").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_route_service_surfaces_service_call_failure_as_500() {
let router = Router::new()
.route_service("/svc", AlwaysFailsCall)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/svc").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_fallback_service_routes_unmatched_requests() {
use tachyon_web::get;
async fn hello() -> &'static str {
"hello"
}
let last_uri = Arc::new(Mutex::new(String::new()));
let svc = EchoUriService {
last_uri: last_uri.clone(),
};
let router = Router::new()
.route("/", get(hello))
.fallback_service(svc)
.with_state::<()>(())
.compile()
.expect("compile");
let req = Request::builder().uri("/").body(Body::empty()).unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "hello");
let req = Request::builder()
.uri("/no/such/route?x=1")
.body(Body::empty())
.unwrap();
let resp = router.handle_request(req).await;
assert_eq!(resp.status(), StatusCode::OK);
let body = body_to_string(resp.into_body()).await;
assert_eq!(body, "/no/such/route?x=1");
let seen = last_uri.lock().unwrap().clone();
assert_eq!(seen, "/no/such/route?x=1");
}