diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml
index 2e6fd566..17d57711 100644
--- a/.github/workflows/CI.yml
+++ b/.github/workflows/CI.yml
@@ -74,7 +74,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: test
- args: --all --all-features
+ args: --all --all-features --all-targets
deny-check:
name: cargo-deny check
diff --git a/examples/testing.rs b/examples/testing.rs
new file mode 100644
index 00000000..cd4a7f63
--- /dev/null
+++ b/examples/testing.rs
@@ -0,0 +1,101 @@
+use awebframework::{prelude::*, routing::BoxRoute};
+use tower_http::trace::TraceLayer;
+
+#[tokio::main]
+async fn main() {
+ tracing_subscriber::fmt::init();
+
+ let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000));
+
+ tracing::debug!("listening on {}", addr);
+
+ hyper::Server::bind(&addr)
+ .serve(app().into_make_service())
+ .await
+ .unwrap();
+}
+
+/// Having a function that produces our app makes it easy to call it from tests
+/// without having to create an HTTP server.
+#[allow(dead_code)]
+fn app() -> BoxRoute
{
+ route("/", get(|| async { "Hello, World!" }))
+ .route(
+ "/json",
+ post(|payload: extract::Json| async move {
+ response::Json(serde_json::json!({ "data": payload.0 }))
+ }),
+ )
+ // We can still add middleware
+ .layer(TraceLayer::new_for_http())
+ .boxed()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use http::StatusCode;
+ use serde_json::{json, Value};
+ use tower::ServiceExt; // for `app.oneshot()`
+
+ #[tokio::test]
+ async fn hello_world() {
+ let app = app();
+
+ // `BoxRoute` implements `tower::Service>` so we can
+ // call it like any tower service, no need to run an HTTP server.
+ let response = app
+ .oneshot(Request::builder().uri("/").body(Body::empty()).unwrap())
+ .await
+ .unwrap();
+
+ assert_eq!(response.status(), StatusCode::OK);
+
+ let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
+ assert_eq!(&body[..], b"Hello, World!");
+ }
+
+ #[tokio::test]
+ async fn json() {
+ let app = app();
+
+ let response = app
+ .oneshot(
+ Request::builder()
+ .method(http::Method::POST)
+ .uri("/json")
+ .header(http::header::CONTENT_TYPE, "application/json")
+ .body(Body::from(
+ serde_json::to_vec(&json!([1, 2, 3, 4])).unwrap(),
+ ))
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+
+ assert_eq!(response.status(), StatusCode::OK);
+
+ let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
+ let body: Value = serde_json::from_slice(&body).unwrap();
+ assert_eq!(body, json!({ "data": [1, 2, 3, 4] }));
+ }
+
+ #[tokio::test]
+ async fn not_found() {
+ let app = app();
+
+ let response = app
+ .oneshot(
+ Request::builder()
+ .uri("/does-not-exist")
+ .body(Body::empty())
+ .unwrap(),
+ )
+ .await
+ .unwrap();
+
+ assert_eq!(response.status(), StatusCode::NOT_FOUND);
+ let body = hyper::body::to_bytes(response.into_body()).await.unwrap();
+ assert!(body.is_empty());
+ }
+}
diff --git a/src/extract/rejection.rs b/src/extract/rejection.rs
index d900c9ba..1c462967 100644
--- a/src/extract/rejection.rs
+++ b/src/extract/rejection.rs
@@ -1,10 +1,8 @@
//! Rejection response types.
-use http::StatusCode;
-use tower::BoxError;
-
use super::IntoResponse;
use crate::body::Body;
+use tower::BoxError;
macro_rules! define_rejection {
(
@@ -355,9 +353,9 @@ pub struct TypedHeaderRejection {
#[cfg(feature = "headers")]
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
impl IntoResponse for TypedHeaderRejection {
- fn into_response(self) -> http::Response {
+ fn into_response(self) -> http::Response {
let mut res = format!("{} ({})", self.err, self.name).into_response();
- *res.status_mut() = StatusCode::BAD_REQUEST;
+ *res.status_mut() = http::StatusCode::BAD_REQUEST;
res
}
}