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

Dependency injections docs, #3 #129

Merged
merged 2 commits into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/getting_started/quickstart/dependency_injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ If you check out its definition, you'll see that it registers a constructor for

It specifies:

- The fully qualified path to the constructor method, wrapped in a macro (`f!`)
- The [fully qualified path](../../guide/dependency_injection/cookbook.md) to the constructor method, wrapped in a macro (`f!`)
- The constructor's lifecycle ([`Lifecycle::RequestScoped`](Lifecycle::RequestScoped)): the framework will invoke this
constructor at most once per
request
Expand Down
2 changes: 1 addition & 1 deletion docs/getting_started/quickstart/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ It specifies:

- The HTTP method (`GET`)
- The path (`/api/ping`)
- The fully qualified path to the handler function (`crate::routes::status::ping`), wrapped in a macro (`f!`)
- The [fully qualified path](../../guide/dependency_injection/cookbook.md) to the handler function (`crate::routes::status::ping`), wrapped in a macro (`f!`)

## Request handlers

Expand Down
3 changes: 2 additions & 1 deletion docs/guide/dependency_injection/cookbook.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Cookbook

This cookbook contains a collection of reference examples for Pavex's dependency injection framework.
It covers common use cases (free functions, methods) as well as more advanced ones (trait methods, generics, etc.).
It covers the registration syntax for common use cases (free functions, methods) as well as more advanced ones
(trait methods, generics, etc.).

Use it a reference in your day-to-day Pavex development if you're not sure of the syntax for a particular use case.

Expand Down
6 changes: 3 additions & 3 deletions docs/guide/dependency_injection/core_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ using its [`constructor`][Blueprint::constructor] method:

[`constructor`][Blueprint::constructor] takes two arguments:

- The fully qualified path to the constructor, wrapped in a macro ([`f!`][f])
- The [fully qualified path](cookbook.md) to the constructor, wrapped in a macro ([`f!`][f])
- The **constructor's lifecycle**.

### Lifecycles
Expand All @@ -59,7 +59,7 @@ Let's look at a few common scenarios to build some intuition around lifecycles:
| Scenario | Lifecycle | Why? |
|--------------------------|-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Database connection pool | [Singleton][Lifecycle::Singleton] | The entire application should use the same pool. <br/>Each request will fetch a connection from the pool when needed. |
| HTTP client | [Singleton][Lifecycle::Singleton] | Most HTTP clients keep, under the hood, an HTTP connection pool. <br/>You want to reuse those connections across requests to minimise latency and the number of open file descriptors. |
| HTTP client | [Singleton][Lifecycle::Singleton] | Most HTTP clients keep, under the hood, a connection pool. <br/>You want to reuse those connections across requests to minimise latency and the number of open file descriptors. |
| Route parameters | [RequestScoped][Lifecycle::RequestScoped] | Route parameters are extracted from the incoming request. <br/> They must not be shared across requests, therefore they can't be a [`Singleton`][Lifecycle::Singleton].<br/> They could be [`Transient`][Lifecycle::Transient], but re-parsing the parameters before every use would be expensive.<br/>[`RequestScoped`][Lifecycle::RequestScoped] is the optimal choice. |
| Database connection | [Transient][Lifecycle::Transient] | The connection is retrieved from a shared pool.<br/>It could be [`RequestScoped`][Lifecycle::RequestScoped], but you might end up keeping the connection booked (i.e. outside of the pool) for longer than it's strictly necessary.<br/>[`Transient`][Lifecycle::Transient] is the optimal choice: you only remove the connection from the pool when it's needed, put it back when idle. | |

Expand Down Expand Up @@ -114,7 +114,7 @@ The framework primitives are:
- [`RouteParams`][RouteParams]. The route parameters extracted from the incoming request.
- [`AllowedMethods`][AllowedMethods]. The HTTP methods allowed for the current request path.

They represent raw data from the incoming request ([`RequestHead`][RequestHead], [`IncomingBody`][IncomingBody])
They represent raw data from the incoming request ([`RequestHead`][RequestHead], [`RawIncomingBody`][RawIncomingBody])
or information coming from the routing system ([`AllowedMethods`][AllowedMethods], [`RouteParams`][RouteParams]).

### Convenient, but inflexible
Expand Down
67 changes: 67 additions & 0 deletions docs/guide/dependency_injection/limitations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Limitations

Pavex's dependency injection system is powerful, but it's not perfect.
Due to the [technology we're using under the hood](https://youtu.be/OxQYyg_v3rw?feature=shared),
there are some limitations you need to be aware of.

## Trait bounds are ignored

Pavex ignores trait bounds on generic parameters.

```rust
/// A generic parameter with a trait bound.
pub fn with_bound<T>(input: T) -> Output<T>
where T: Debug
{
// [...]
}

/// A generic parameter without a trait bound.
pub fn without_bound<T>(input: T) -> Output<T> {
// [...]
}
```

From Pavex's perspective, `with_bound` and `without_bound` are equivalent: they take `T`
as input parameter and return `Output<T>`.

As a consequence, Pavex won't detect any errors related to trait bounds in the code-generation phase.
Those errors will be picked up by the Rust compiler when it tries to compile the generated code.

## Naked generics

A generic parameter is **naked** if it appears, as is, in the function signature of a constructor.

```rust
/// A naked generic output parameter.
pub fn naked_output<T>(/* ... */) -> T {
// [...]
}
```

From Pavex's perspective, `naked_output` is a universal constructor: it can build any type.
It will therefore reject the constructor with an error message at compile time.

You can have a naked generic input parameter,
but only if it's also an [output-driven generic parameter](cookbook.md#output-driven-generics).
There is no ambiguity in that case:
Pavex determines the concrete type of the input parameter from the output type of the constructor.

```rust
/// A naked output-driven parameter.
pub fn wrapper<T>(t: T) -> Custom<T> {
// [...]
}
```

You can't have a naked generic input parameter that is input-driven.

```rust
/// A naked input-driven parameter.
pub fn naked_input<T: DatabaseService>(t: T) -> QueryResult {
// [...]
}
```

Pavex [can't reason about trait bounds](#trait-bounds-are-ignored), therefore it isn't smart enough
to determine the concrete type of the input parameter based on the bounds you've specified.
8 changes: 7 additions & 1 deletion docs/guide/routing/request_handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ When registering a route, you must provide the **fully qualified path** to the r
--8<-- "doc_examples/guide/routing/request_handlers/intro/src/blueprint.rs"
```

The path must be wrapped in the [`f!` macro][f!].
The path must be wrapped in the [`f!` macro][f!].

!!! note "Registration syntax"

You can use free functions, static methods, non-static methods, and trait methods as request handlers.
Check out the [dependency injection cookbook](../dependency_injection/cookbook.md) for more details on
the syntax for each case.

## `IntoResponse`

Expand Down
Loading