Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider using route_recognizer instead of path-table #141

Closed
tirr-c opened this issue Feb 26, 2019 · 6 comments
Closed

Consider using route_recognizer instead of path-table #141

tirr-c opened this issue Feb 26, 2019 · 6 comments

Comments

@tirr-c
Copy link
Collaborator

tirr-c commented Feb 26, 2019

Detailed Description

For now Router is using path-table for its route matching. Consider changing the algorithm by replacing it with route_recognizer.

The published version of route_recognizer is unmaintained for about 2 years, and seems to have reversed precedence of path segment matching (http-rs/route-recognizer#21). Maybe we can fork the repository and apply some patches.

Context

Related to #12. route_recognizer seems to have more sophiscated route matching algorithm than path-table. I expect it to fix most of the counterintuitive cases.

@krircc
Copy link

krircc commented Feb 26, 2019

There is actix_router, but it is Independent,and dont dependent Actix* . It is General
this is one test

@aturon
Copy link
Collaborator

aturon commented Feb 26, 2019

Note: this is the algorithm ultimately used by crates.io:

so it's "maintained", just hasn't needed a lot of updating. Definitely a worthy candidate to look at, and I expect the current owners would be interested in moving it into the rust-net-web org.

@bIgBV
Copy link
Contributor

bIgBV commented Feb 28, 2019

One thing to consider for the router API is a way of providing reverse lookups #24. I'll go into more detail about the small survey I did of current implementation of such an API in django and flask, but both of them leverage the router to provide a reverse lookups of endpoints to paths for building URLs.

@tirr-c
Copy link
Collaborator Author

tirr-c commented Mar 7, 2019

So, we need a router that:

  • has intuitive path matching algorithm
  • can be nested
  • has ability to perform reverse lookups

path-table currently used by Tide addresses only the second point, and both route_recognizer and actix-router don't have reverse lookup feature (as far as I understand). I think we need to fork and add needed functionality anyway, so how about experimenting with route_recognizer? It might be challenging with state machines, though.

@fundon
Copy link
Contributor

fundon commented Mar 10, 2019

I wrote a crate https://github.com/trek-rs/path-tree.

Currently, I am thinking about how to generate urls. Maybe the {} is best syntax.

aturon added a commit to aturon/tide that referenced this issue Mar 21, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Mar 21, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Mar 22, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 4, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
secretfader pushed a commit to secretfader/tide that referenced this issue Apr 4, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
secretfader pushed a commit to secretfader/tide that referenced this issue Apr 4, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 9, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 9, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 9, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 9, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 10, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
aturon added a commit to aturon/tide that referenced this issue Apr 10, 2019
This commit reworks Tide, with the goal of reducing the total number of concepts
in the framework. The key new idea is to remove the notion of `Extractor`s, which
in turn allows us to remove or simplify several other concepts in Tide.

We'll first lay out the new design, and then discuss the tradeoffs made in this
simplification.

Here's a full list of the concepts in Tide after this commit:

| Concept  | Description |
| ----- | ----------- |
| `App` | Builder for Tide applications |
| `Route` | Builder for an individual route |
| `Endpoint` | Trait for actual endpoints |
| `Context` | The request context for an endpoint |
| `IntoResponse` | A trait for converting into a `Response` |
| `Middleware` | A trait for Tide middleware |

Previously, the `Endpoint` trait was treated as a somewhat magical internal
abstraction, and we used a macro to provide `Endpoint` implementations for
actual endpoints (with varying numbers of extractor arguments).

In this commit, an `Endpoint` is just an asynchronous function from a `Context`
to a `Response`:

```rust
pub trait Endpoint<AppData>: Send + Sync + 'static {
    /// The async result of `call`.
    type Fut: Future<Output = Response> + Send + 'static;

    /// Invoke the endpoint.
    fn call(&self, cx: Context<AppData>) -> Self::Fut;
}
```

For convenience, this trait is implemented for async functions that return any
value that implements `IntoResponse`:

```rust
impl<AppData, F, Fut> Endpoint<AppData> for F
where
    F: Fn(Context<AppData>) -> Fut,
    Fut: Future
    Fut::Output: IntoResponse,
    // ...
```

This implementation is in contrast to the macro-generated implementations we
previously had, which allowed endpoints with varying numbers of `Extractor`
arguments. The intent is for endpoints to perform their own extraction directly
on the `Context`, as we'll see next.

The `Context` type contains all of the request and middleware context an
endpoint operates on. You can think of it as wrapping an `http_service::Request`
with some additional data.

It's easiest to understand `Context` through the APIs it provides. First, we have
methods for getting basic http request information, mirroring the `http` APIs:

```rust
impl<AppData> Context<AppData> {
    pub fn method(&self) -> &Method;
    pub fn uri(&self) -> &Uri;
    pub fn version(&self) -> Version;
    pub fn headers(&self) -> &HeaderMap;
}
```

The context also has a handle to application data, which typically would store
database connection pools and other "application-global" state. This API
replaces the old `AppData` extractor:

```rust
impl<AppData> Context<AppData> {
    pub fn app_data(&self) -> &AppData {
        &self.app_data
    }
}
```

Similarly, we provide a *direct* API for extracting any "route parameters"
(i.e. placeholders in the route URL), replacing the need for `NamedSegment` and
the like:

```rust
impl<AppData> Context<AppData> {
    pub fn route_param(&self, key: &str) -> Option<&str>;
}
```

Basic body extraction is likewise built in via `Context` methods, replacing the
`Str`, `Bytes`, and `Json` extractors:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_bytes(&mut self) -> std::io::Result<Vec<u8>>;
    pub async fn body_string(&mut self) -> std::io::Result<String>;
    pub async fn body_json<T: serde::de::DeserializeOwned>(&mut self) -> std::io::Result<T>;
}
```

Looking at the [message database example](https://github.com/rustasync/tide/blob/master/examples/messages.rs#L44),
we previously had endpoints like this:

```rust
async fn new_message(mut db: AppData<Database>, msg: body::Json<Message>) -> String {
    db.insert(msg.clone()).to_string()
}

async fn set_message(
    mut db: AppData<Database>,
    id: head::Path<usize>,
    msg: body::Json<Message>,
) -> Result<(), StatusCode> {
    if db.set(*id, msg.clone()) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

These endpoints would now be written something like this (where `Error` is
intended as a general error type, convertible into a response):

```rust
async fn new_message(cx: Context<Database>) -> Result<String, Error> {
    let msg = await!(cx.body_json())?;

    cx.app_data().insert(msg).to_string()
}

async fn set_message(cx: Context<Database>) -> Result<(), Error> {
    let msg = await!(cx.body_json())?;

    if cx.app_data().set(cx.route_param("id"), msg) {
        Ok(())
    } else {
        Err(StatusCode::NOT_FOUND)
    }
}
```

The endpoint code is a bit more verbose, but also arguably easier to follow,
since the extraction (and error handling) is more clear.

In addition, the basic extraction approach is *more discoverable*, since it
operates via normal methods on `Context`.

Part of the idea of the old `Extractor` trait was that Tide would provide an
*extensible* system of extractors; you could always introduce new types that
implement `Extractor`. But now most of the existing extractors are built-in
`Context` methods. How do we recover extensibility?

Easy: we use Rust's ability to extend existing types with new methods, via
traits! (Note: this is directly inspired by the Gotham framework).

Let's say we want to provide cookie extraction. Previously, we'd have a `Cookies`
type that you could use as an endpoint argument for extraction. Now, instead, we
can introduce a `Cookies` *trait* that's used to extend `Context` with new APIs:

```rust
trait Cookies {
    fn cookies(&self) -> CookieJar;
}

impl<AppData> Cookies for Context<AppData> { ... }
```

This pattern is called an "extension trait" -- a trait whose sole purpose is to
extend an existing type with new methods. There are several nice properties of
this approach:

- The resulting extraction API is just a direct and natural as the built-in
  ones: just a method call on the `Context` object.

- The methods that are available on `Context` are controlled by what traits are
  in scope. In other words, if you want to use a custom extractor from the
  ecosystem, you just bring its trait into scope, and then the method is
  available. That makes it easy to build a robust ecosystem around a small set
  of core Tide APIs.

One of the major benefits of moving extraction into the endpoint body, rather
than via `Extractor` arguments, is that it's much simpler to provide
configuration. For example, we could easily provide a customized json body
extractor that limited the maximum size of the body or other such options:

```rust
impl<AppData> Context<AppData> {
    pub async fn body_json_cfg<T: serde::de::DeserializeOwned>(&mut self, cfg: JsonConfig) -> std::io::Result<T>;
}
```

As a result, we can drop much of the complexity in `App` around configuration.

Following the spirit of the changes to extractors, response generation for
non-standard Rust types is now just done via a free function:

```rust
mod response {
    pub fn json<T: serde::Serialize>(t: T) -> Response { ... }
}
```

As before, there's a top-level `App` type for building up a Tide application.
However, the API has been drastically simplified:

- It no longer provides a configuration system, since extractors can now be
  configured directly.
- It no longer allows for the middleware list to be customized per route;
  instead, middleware is set up only at the top level.

These simplifications make the programming model much easier to understand;
previously, there were inconsistencies between the way that middleware nesting
and configuration nesting worked. The hope is that we can get away with this
much simpler, top-level model.

When actually adding routes via `at`, you get a `Route` object (which used to be
`Resource`). This object now provides a *builder-style* API for adding
endpoints, allowing you to chain several endpoints. Altogether, this means we
can drop nested routing as well.

The middleware trait is more or less as it was before, adjusted to use `Context`
objects and otherwise slightly cleaned up.

This commit also switches to using the route-recognizer crate, rather than the
path-table crate, as the underlying routing mechanism. In addition to being more
efficient, route-recognizer provides a more intuitive semantics for "ambiguous"
routing situations. See issue http-rs#12 and issue http-rs#141 for more details.
@aturon
Copy link
Collaborator

aturon commented Apr 10, 2019

The #156 PR moved to route-recognizer. And in general it should now be much easier to swap out the underlying router. I'm open to moving to path-tree if that seems like a better option -- though offhand the two seem pretty comparable in terms of functionality (and route-recognizer is used by crates.io, so there's some impetus for sharing).

I'm going to close this for now, but if @fundon or others want to explore moving from route-recognizer to path-tree or something else, please feel free to open a new issue and make a case there!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants