diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f3cf36093..99b78ddac 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -71,7 +71,7 @@ jobs: run: cargo fmt --all -- --check - name: Docs - run: cargo doc --features docs + run: cargo doc clippy_check: name: Clippy check diff --git a/CHANGELOG.md b/CHANGELOG.md index 309e8a8d6..eca4cf96e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://book.async.rs/overview ## [Unreleased] +### Added + +- Added `logger::RequestLogger` based on `log` (replaces `logger:RootLogger`) + +### Changed + +- Resolved an `#[allow(unused_mut)]` workaround. +- Renamed `ExtractForms` to `ContextExt`. + +### Removed + +- Removed `logger::RootLogger` (replaced by `logger:RequestLogger`) +- Removed internal use of the `box_async` macro + ## [0.3.0] - 2019-10-31 This is the first release in almost 6 months; introducing a snapshot of where we diff --git a/Cargo.toml b/Cargo.toml index 1b28d5818..11e2b7088 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,11 +29,9 @@ route-recognizer = "0.1.13" serde = "1.0.102" serde_derive = "1.0.102" serde_json = "1.0.41" -slog = "2.5.2" -slog-async = "2.3.0" -slog-term = "2.4.2" typemap = "0.3.3" serde_urlencoded = "0.6.1" +log = "0.4.8" [dependencies.http-service-hyper] optional = true diff --git a/README.md b/README.md index 604bd44a1..758271e0b 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ API Docs | - + Contributing | @@ -60,23 +60,23 @@ fn main() -> Result<(), std::io::Error> { **More Examples** -- [Hello World](https://github.com/rustasync/tide/tree/master/examples/hello.rs) -- [Messages](https://github.com/rustasync/tide/blob/master/examples/messages.rs) -- [Body Types](https://github.com/rustasync/tide/blob/master/examples/body_types.rs) -- [Multipart Form](https://github.com/rustasync/tide/tree/master/examples/multipart-form/main.rs) -- [Catch All](https://github.com/rustasync/tide/tree/master/examples/catch_all.rs) -- [Cookies](https://github.com/rustasync/tide/tree/master/examples/cookies.rs) -- [Default Headers](https://github.com/rustasync/tide/tree/master/examples/default_headers.rs) -- [GraphQL](https://github.com/rustasync/tide/tree/master/examples/graphql.rs) +- [Hello World](https://github.com/http-rs/tide/tree/master/examples/hello.rs) +- [Messages](https://github.com/http-rs/tide/blob/master/examples/messages.rs) +- [Body Types](https://github.com/http-rs/tide/blob/master/examples/body_types.rs) +- [Multipart Form](https://github.com/http-rs/tide/tree/master/examples/multipart-form/main.rs) +- [Catch All](https://github.com/http-rs/tide/tree/master/examples/catch_all.rs) +- [Cookies](https://github.com/http-rs/tide/tree/master/examples/cookies.rs) +- [Default Headers](https://github.com/http-rs/tide/tree/master/examples/default_headers.rs) +- [GraphQL](https://github.com/http-rs/tide/tree/master/examples/graphql.rs) ## Resources Read about the design here: -- [Rising Tide: building a modular web framework in the open](https://rustasync.github.io/team/2018/09/11/tide.html) -- [Routing and extraction in Tide: a first sketch](https://rustasync.github.io/team/2018/10/16/tide-routing.html) -- [Middleware in Tide](https://rustasync.github.io/team/2018/11/07/tide-middleware.html) -- [Tide's evolving middleware approach](https://rustasync.github.io/team/2018/11/27/tide-middleware-evolution.html) +- [Rising Tide: building a modular web framework in the open](https://http-rs.github.io/team/2018/09/11/tide.html) +- [Routing and extraction in Tide: a first sketch](https://http-rs.github.io/team/2018/10/16/tide-routing.html) +- [Middleware in Tide](https://http-rs.github.io/team/2018/11/07/tide-middleware.html) +- [Tide's evolving middleware approach](https://http-rs.github.io/team/2018/11/27/tide-middleware-evolution.html) ## Contributing @@ -89,7 +89,7 @@ guide][contributing] and take a look at some of these issues: #### Conduct The Tide project adheres to the [Contributor Covenant Code of -Conduct](https://github.com/rustasync/tide/blob/master/.github/CODE_OF_CONDUCT.md). This +Conduct](https://github.com/http-rs/tide/blob/master/.github/CODE_OF_CONDUCT.md). This describes the minimum behavior expected from all contributors. ## License @@ -107,7 +107,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. -[releases]: https://github.com/rustasync/tide/releases -[contributing]: https://github.com/rustasync/tide/blob/master/.github/CONTRIBUTING.md -[good-first-issue]: https://github.com/rustasync/tide/labels/good%20first%20issue -[help-wanted]: https://github.com/rustasync/tide/labels/help%20wanted +[releases]: https://github.com/http-rs/tide/releases +[contributing]: https://github.com/http-rs/tide/blob/master/.github/CONTRIBUTING.md +[good-first-issue]: https://github.com/http-rs/tide/labels/good%20first%20issue +[help-wanted]: https://github.com/http-rs/tide/labels/help%20wanted diff --git a/examples/body_types.rs b/examples/body_types.rs index fb85fbe1e..deed774ae 100644 --- a/examples/body_types.rs +++ b/examples/body_types.rs @@ -1,7 +1,7 @@ use serde::{Deserialize, Serialize}; use tide::{ error::ResultExt, - forms::{self, ExtractForms}, + forms::{self, ContextExt}, response, App, Context, EndpointResult, }; @@ -11,28 +11,24 @@ struct Message { contents: String, } -#[allow(unused_mut)] // Workaround clippy bug async fn echo_string(mut cx: Context<()>) -> String { let msg = cx.body_string().await.unwrap(); println!("String: {}", msg); msg } -#[allow(unused_mut)] // Workaround clippy bug async fn echo_bytes(mut cx: Context<()>) -> Vec { let msg = cx.body_bytes().await.unwrap(); println!("Bytes: {:?}", msg); msg } -#[allow(unused_mut)] // Workaround clippy bug async fn echo_json(mut cx: Context<()>) -> EndpointResult { let msg = cx.body_json().await.client_err()?; println!("JSON: {:?}", msg); Ok(response::json(msg)) } -#[allow(unused_mut)] // Workaround clippy bug async fn echo_form(mut cx: Context<()>) -> EndpointResult { let msg = cx.body_form().await?; println!("Form: {:?}", msg); diff --git a/examples/cookies.rs b/examples/cookies.rs index 1f8fee097..751efe8c3 100644 --- a/examples/cookies.rs +++ b/examples/cookies.rs @@ -7,12 +7,10 @@ async fn retrieve_cookie(mut cx: Context<()>) -> String { format!("hello cookies: {:?}", cx.get_cookie("hello").unwrap()) } -#[allow(unused_mut)] // Workaround clippy bug async fn set_cookie(mut cx: Context<()>) { cx.set_cookie(Cookie::new("hello", "world")).unwrap(); } -#[allow(unused_mut)] // Workaround clippy bug async fn remove_cookie(mut cx: Context<()>) { cx.remove_cookie(Cookie::named("hello")).unwrap(); } diff --git a/examples/messages.rs b/examples/messages.rs index 3cbb75d7f..ffb84f160 100644 --- a/examples/messages.rs +++ b/examples/messages.rs @@ -37,13 +37,11 @@ impl Database { } } -#[allow(unused_mut)] // Workaround clippy bug async fn new_message(mut cx: Context) -> EndpointResult { let msg = cx.body_json().await.client_err()?; Ok(cx.state().insert(msg).to_string()) } -#[allow(unused_mut)] // Workaround clippy bug async fn set_message(mut cx: Context) -> EndpointResult<()> { let msg = cx.body_json().await.client_err()?; let id = cx.param("id").client_err()?; diff --git a/examples/multipart-form/main.rs b/examples/multipart-form/main.rs index 6ed732ab0..1bf6e7495 100644 --- a/examples/multipart-form/main.rs +++ b/examples/multipart-form/main.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use std::io::Read; -use tide::{forms::ExtractForms, response, App, Context, EndpointResult}; +use tide::{forms::ContextExt, response, App, Context, EndpointResult}; #[derive(Serialize, Deserialize, Clone)] struct Message { @@ -9,7 +9,6 @@ struct Message { file: Option, } -#[allow(unused_mut)] // Workaround clippy bug async fn upload_file(mut cx: Context<()>) -> EndpointResult { // https://stackoverflow.com/questions/43424982/how-to-parse-multipart-forms-using-abonander-multipart-with-rocket let mut message = Message { diff --git a/src/app.rs b/src/app.rs index a3c9ba8cf..b76ce47d2 100644 --- a/src/app.rs +++ b/src/app.rs @@ -33,7 +33,7 @@ use crate::{ /// /// let mut app = tide::App::new(); /// app.at("/hello").get(|_| async move {"Hello, world!"}); -/// app.serve("127.0.0.1:8000"); +/// app.serve("127.0.0.1:8000").unwrap(); /// ``` /// /// # Routing and parameters @@ -44,7 +44,6 @@ use crate::{ /// segments as parameters to endpoints: /// /// ```rust, no_run -/// /// use tide::error::ResultExt; /// /// async fn hello(cx: tide::Context<()>) -> tide::EndpointResult { @@ -65,7 +64,7 @@ use crate::{ /// "Use /hello/{your name} or /goodbye/{your name}" /// }); /// -/// app.serve("127.0.0.1:8000"); +/// app.serve("127.0.0.1:8000").unwrap(); /// ``` /// /// You can learn more about routing in the [`App::at`] documentation. @@ -272,7 +271,7 @@ impl HttpService for Server { let middleware = self.middleware.clone(); let data = self.data.clone(); - box_async! { + Box::pin(async move { let fut = { let Selection { endpoint, params } = router.route(&path, method); let cx = Context::new(data, req, params); @@ -286,7 +285,7 @@ impl HttpService for Server { }; Ok(fut.await) - } + }) } } diff --git a/src/endpoint.rs b/src/endpoint.rs index dc02d5e88..176e1e15f 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -66,8 +66,6 @@ where type Fut = BoxFuture<'static, Response>; fn call(&self, cx: Context) -> Self::Fut { let fut = (self)(cx); - box_async! { - fut.await.into_response() - } + Box::pin(async move { fut.await.into_response() }) } } diff --git a/src/forms.rs b/src/forms.rs index b7338dab5..697027973 100644 --- a/src/forms.rs +++ b/src/forms.rs @@ -8,7 +8,7 @@ use crate::{ }; /// An extension trait for `Context`, providing form extraction. -pub trait ExtractForms { +pub trait ContextExt { /// Asynchronously extract the entire body as a single form. fn body_form(&mut self) -> BoxTryFuture; @@ -16,13 +16,15 @@ pub trait ExtractForms { fn body_multipart(&mut self) -> BoxTryFuture>>>; } -impl ExtractForms for Context { +impl ContextExt for Context { fn body_form(&mut self) -> BoxTryFuture { let body = self.take_body(); - box_async! { + Box::pin(async move { let body = body.into_vec().await.client_err()?; - Ok(serde_urlencoded::from_bytes(&body).map_err(|e| err_fmt!("could not decode form: {}", e)).client_err()?) - } + Ok(serde_urlencoded::from_bytes(&body) + .map_err(|e| err_fmt!("could not decode form: {}", e)) + .client_err()?) + }) } fn body_multipart(&mut self) -> BoxTryFuture>>> { @@ -35,11 +37,13 @@ impl ExtractForms for Context { let body = self.take_body(); - box_async! { + Box::pin(async move { let body = body.into_vec().await.client_err()?; - let boundary = boundary.ok_or_else(|| err_fmt!("no boundary found")).client_err()?; + let boundary = boundary + .ok_or_else(|| err_fmt!("no boundary found")) + .client_err()?; Ok(Multipart::with_body(Cursor::new(body), boundary)) - } + }) } } diff --git a/src/lib.rs b/src/lib.rs index 6ddac489a..af4d64f84 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,31 +1,15 @@ -#![cfg_attr(feature = "nightly", deny(missing_docs))] -#![cfg_attr(feature = "nightly", feature(external_doc))] -#![cfg_attr(feature = "nightly", doc(include = "../README.md"))] -#![cfg_attr(test, deny(warnings))] -#![allow(unused_variables)] -#![deny( - nonstandard_style, - rust_2018_idioms, - future_incompatible, - missing_debug_implementations -)] -// TODO: Remove this after clippy bug due to async await is resolved. -// ISSUE: https://github.com/rust-lang/rust-clippy/issues/3988 -#![allow(clippy::needless_lifetimes)] +// #![warn(missing_docs)] +#![warn(missing_debug_implementations, rust_2018_idioms)] +#![allow(clippy::mutex_atomic, clippy::module_inception)] +#![doc(test(attr(deny(rust_2018_idioms, warnings))))] +#![doc(test(attr(allow(unused_extern_crates, unused_variables))))] -//! //! Welcome to Tide. //! //! The [`App`](struct.App.html) docs are a good place to get started. //! //! -macro_rules! box_async { - {$($t:tt)*} => { - ::futures::future::FutureExt::boxed(async move { $($t)* }) - }; -} - #[macro_use] pub mod error; diff --git a/src/middleware/cookies.rs b/src/middleware/cookies.rs index b33443b51..639ea4032 100644 --- a/src/middleware/cookies.rs +++ b/src/middleware/cookies.rs @@ -31,7 +31,7 @@ impl Middleware for CookiesMiddleware { mut cx: Context, next: Next<'a, Data>, ) -> BoxFuture<'a, Response> { - box_async! { + Box::pin(async move { let cookie_data = cx .extensions_mut() .remove() @@ -56,7 +56,7 @@ impl Middleware for CookiesMiddleware { } } res - } + }) } } @@ -72,23 +72,19 @@ mod tests { static COOKIE_NAME: &str = "testCookie"; /// Tide will use the the `Cookies`'s `Extract` implementation to build this parameter. - #[allow(unused_mut)] // Workaround clippy bug async fn retrieve_cookie(mut cx: Context<()>) -> String { format!("{}", cx.get_cookie(COOKIE_NAME).unwrap().unwrap().value()) } - #[allow(unused_mut)] // Workaround clippy bug async fn set_cookie(mut cx: Context<()>) { cx.set_cookie(Cookie::new(COOKIE_NAME, "NewCookieValue")) .unwrap(); } - #[allow(unused_mut)] // Workaround clippy bug async fn remove_cookie(mut cx: Context<()>) { cx.remove_cookie(Cookie::named(COOKIE_NAME)).unwrap(); } - #[allow(unused_mut)] // Workaround clippy bug async fn set_multiple_cookie(mut cx: Context<()>) { cx.set_cookie(Cookie::new("C1", "V1")).unwrap(); cx.set_cookie(Cookie::new("C2", "V2")).unwrap(); diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index d7b5bffe4..e6627f82b 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -41,7 +41,7 @@ impl DefaultHeaders { impl Middleware for DefaultHeaders { fn handle<'a>(&'a self, cx: Context, next: Next<'a, Data>) -> BoxFuture<'a, Response> { - box_async! { + Box::pin(async move { let mut res = next.run(cx).await; let headers = res.headers_mut(); @@ -49,6 +49,6 @@ impl Middleware for DefaultHeaders { headers.entry(key).unwrap().or_insert_with(|| value.clone()); } res - } + }) } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 3becf7928..defaed905 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -1,50 +1,50 @@ -use slog::{info, o, Drain}; -use slog_async; -use slog_term; - -use futures::future::BoxFuture; - use crate::{ middleware::{Middleware, Next}, Context, Response, }; +use futures::future::BoxFuture; -/// Root logger for Tide. Wraps over logger provided by slog.SimpleLogger -#[derive(Debug)] -pub struct RootLogger { - // drain: dyn slog::Drain, - inner_logger: slog::Logger, -} - -impl RootLogger { - pub fn new() -> RootLogger { - let decorator = slog_term::TermDecorator::new().build(); - let drain = slog_term::CompactFormat::new(decorator).build().fuse(); - let drain = slog_async::Async::new(drain).build().fuse(); - - let log = slog::Logger::root(drain, o!()); - RootLogger { inner_logger: log } +/// A simple requests logger +/// +/// # Examples +/// +/// ```rust +/// +/// let mut app = tide::App::new(); +/// app.middleware(tide::middleware::RequestLogger::new()); +/// ``` +#[derive(Debug, Clone, Default)] +pub struct RequestLogger; + +impl RequestLogger { + pub fn new() -> Self { + Self::default() } -} -impl Default for RootLogger { - fn default() -> Self { - Self::new() + async fn log_basic<'a, Data: Send + Sync + 'static>( + &'a self, + ctx: Context, + next: Next<'a, Data>, + ) -> Response { + let path = ctx.uri().path().to_owned(); + let method = ctx.method().as_str().to_owned(); + log::trace!("IN => {} {}", method, path); + let start = std::time::Instant::now(); + let res = next.run(ctx).await; + let status = res.status(); + log::info!( + "{} {} {} {}ms", + method, + path, + status.as_str(), + start.elapsed().as_millis() + ); + res } } -/// Stores information during request phase and logs information once the response -/// is generated. -impl Middleware for RootLogger { - fn handle<'a>(&'a self, cx: Context, next: Next<'a, Data>) -> BoxFuture<'a, Response> { - box_async! { - let path = cx.uri().path().to_owned(); - let method = cx.method().as_str().to_owned(); - - let res = next.run(cx).await; - let status = res.status(); - info!(self.inner_logger, "{} {} {}", method, path, status.as_str()); - res - } +impl Middleware for RequestLogger { + fn handle<'a>(&'a self, ctx: Context, next: Next<'a, Data>) -> BoxFuture<'a, Response> { + Box::pin(async move { self.log_basic(ctx, next).await }) } } diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 64c6c5692..343a21b17 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -7,7 +7,9 @@ mod cookies; mod default_headers; mod logger; -pub use self::{cookies::CookiesMiddleware, default_headers::DefaultHeaders, logger::RootLogger}; +pub use self::{ + cookies::CookiesMiddleware, default_headers::DefaultHeaders, logger::RequestLogger, +}; /// Middleware that wraps around remaining middleware chain. pub trait Middleware: 'static + Send + Sync { diff --git a/src/router.rs b/src/router.rs index 34710110d..d3dfc1723 100644 --- a/src/router.rs +++ b/src/router.rs @@ -62,7 +62,10 @@ impl Router { } fn not_found_endpoint(_cx: Context) -> BoxFuture<'static, Response> { - box_async! { - http::Response::builder().status(http::StatusCode::NOT_FOUND).body(Body::empty()).unwrap() - } + Box::pin(async move { + http::Response::builder() + .status(http::StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap() + }) }