* Improve extractor docs
- Moves things from the `extract` module docs to the root module docs to
make them more discoverable
- Adds section showing commonly used extractors
- More clarity around multiple extractors that mutate the request
* english...
Boxing a service normally means using `tower::util::BoxService`. That
doesn't implement `Clone` however so normally I had been combining it
with `Buffer` to get that.
But recently I discovered https://github.com/dtolnay/dyn-clone which
makes it possible to clone trait objects. So this adds a new internal
utility called `CloneBoxService` which replaces the previous
`BoxService` + `Buffer` combo in `BoxRoute`.
I'll investigate upstreaming that to tower. I think it makes sense there
since box + clone is quite a common need.
I haven't been able to find a proper solution for #89 so for now I think
we should document the issue and move on with shipping 0.2.
Part of https://github.com/tokio-rs/axum/issues/89
This removes a small foot gun from the routing.
This means matching different HTTP methods for the same route that
aren't defined together now works.
So `Router::new().route("/", get(...)).route("/", post(...))` now
accepts both `GET` and `POST`. Previously only `POST` would be accepted.
This way there is now only one way to create a router:
```rust
use axum::{Router, handler::get};
let app = Router::new()
.route("/foo", get(handler))
.route("/foo", get(handler));
```
`nest` was changed in the same way:
```rust
use axum::Router;
let app = Router::new().nest("/foo", service);
```
Previously, on `main`, this wouldn't compile:
```rust
let app = route("/", get(handler))
.layer(
ServiceBuilder::new()
.timeout(Duration::from_secs(10))
.into_inner(),
)
.handle_error(...)
.route(...); // <-- doesn't work
```
That is because `handle_error` would be
`axum::service::ServiceExt::handle_error` which returns `HandleError<_,
_, _, HandleErrorFromService>` which does _not_ implement `RoutingDsl`.
So you couldn't call `route`. This was caused by
https://github.com/tokio-rs/axum/pull/120.
Basically `handle_error` when called on a `RoutingDsl`, the resulting
service should also implement `RoutingDsl`, but if called on another
random service it should _not_ implement `RoutingDsl`.
I don't think thats possible by having `handle_error` on `ServiceExt`
which is implemented for any service, since all axum routers are also
services by design.
This resolves the issue by removing `ServiceExt` and moving its methods
to `RoutingDsl`. Then we have more tight control over what has a
`handle_error` method.
`service::OnMethod` now also has a `handle_error` so you can still
handle errors from random services, by doing
`service::any(svc).handle_error(...)`.
Adds associated `Body` and `BodyError` types to `IntoResponse`. This is required for returning responses with bodies other than `hyper::Body` from handlers. That wasn't previously possible.
This is a breaking change so should be shipped in 0.2.
With this you'll be able to do:
```rust
let one = route("/foo", get(|| async { "foo" }))
.route("/bar", get(|| async { "bar" }));
let two = route("/baz", get(|| async { "baz" }));
let app = one.or(two);
```
Fixes https://github.com/tokio-rs/axum/issues/101
* Feature-gate test that depends on non-default features
Makes `cargo check` work without extra flags.
* Don't set doc(html_root_url)
It is no longer recommended:
https://github.com/rust-lang/api-guidelines/pull/230
* Remove documentation URL from Cargo.toml
crates.io will link to the right version on docs.rs automatically.
* Ensure toolchains installed by actions-rs/toolchain are actually used
* Fix missing rustup component in check job
* Raise MSRV to 1.51
Older versions weren't actually working before.
* Only run clippy & rustfmt on stable toolchain
MSRV is checked in test-versions.
* Allow cargo doc to succeed without headers and multipart features
CI will still ensure that intra-doc links that rely on these are not broken.
Example usage:
```rust
use axum::{prelude::*, sse::{sse, Event, KeepAlive}};
use tokio_stream::StreamExt as _;
use futures::stream::{self, Stream};
use std::{
time::Duration,
convert::Infallible,
};
let app = route("/sse", sse(make_stream).keep_alive(KeepAlive::default()));
async fn make_stream(
// you can also put extractors here
) -> Result<impl Stream<Item = Result<Event, Infallible>>, Infallible> {
// A `Stream` that repeats an event every second
let stream = stream::repeat_with(|| Event::default().data("hi!"))
.map(Ok)
.throttle(Duration::from_secs(1));
Ok(stream)
}
```
Implementation is based on [warp's](https://github.com/seanmonstar/warp/blob/master/src/filters/sse.rs)