Skip to content

Commit

Permalink
Improve guide section on mutations and invocation order for construct…
Browse files Browse the repository at this point in the history
…ors.
  • Loading branch information
LukeMathWalker committed Mar 30, 2024
1 parent 52d89f8 commit ab45cc3
Show file tree
Hide file tree
Showing 18 changed files with 159 additions and 25 deletions.
5 changes: 4 additions & 1 deletion doc_examples/.cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
pavex = { path = "../libs/pavex" }
pavex_cli_client = { path = "../libs/pavex_cli_client" }
pavex_tracing = { path = "../libs/pavex_tracing" }
pavex_bp_schema = { path = "../libs/pavex_bp_schema" }
pavex_bp_schema = { path = "../libs/pavex_bp_schema" }

[net]
offline = true
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
```rust title="src/base/handler.rs"
use super::{A, B};
use pavex::response::Response;
pub fn handler(a: A, b: B) -> Response {
// Handler logic
// [...]
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/target
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "di_core_concepts"
version = "0.1.0"
edition = "2021"

[dependencies]
pavex = { path = "../../../../../libs/pavex" }
pavex_cli_client = { path = "../../../../../libs/pavex_cli_client" }
cargo_px_env = "0.1"

[workspace]
members = [".", "server_sdk"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "di_core_concepts_server_sdk"
version = "0.1.0"
edition = "2021"

[package.metadata.px.generate]
generator_type = "cargo_workspace_binary"
generator_name = "di_core_concepts"

[dependencies]
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
use pavex::blueprint::Blueprint;
use pavex::blueprint::router::GET;
use pavex::f;

pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.request_scoped(f!(super::a));
bp.request_scoped(f!(super::b));
bp.route(GET, "/", f!(super::handler));
bp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use super::{A, B};
use pavex::response::Response;

pub fn handler(a: A, b: B) -> Response {
// Handler logic
todo!()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pub use blueprint::blueprint;
pub use handler::handler;

mod blueprint;
mod handler;

pub struct A;
pub struct B;

pub fn a() -> A {
A
}
pub fn b() -> B {
B
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use pavex::blueprint::Blueprint;

pub fn blueprint() -> Blueprint {
let mut bp = Blueprint::new();
bp.nest(crate::base::blueprint());
bp
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#![allow(dead_code)]
#![allow(unused_variables)]

pub use blueprint::blueprint;

pub mod base;
mod blueprint;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::error::Error;

use cargo_px_env::generated_pkg_manifest_path;
use pavex_cli_client::Client;

use di_core_concepts::blueprint;

fn main() -> Result<(), Box<dyn Error>> {
let generated_dir = generated_pkg_manifest_path()?.parent().unwrap().into();
Client::new()
.generate(blueprint(), generated_dir)
.execute()?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use pavex::http::StatusCode;

pub fn handler() -> StatusCode {
todo!()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
starter_project_folder: "project"
commands:
- command: "cargo px c"
expected_outcome: "success"
snippets:
- name: "handler"
source_path: "src/base/handler.rs"
ranges: [ "0..5", "6..8" ]
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,3 @@ generator_type = "cargo_workspace_binary"
generator_name = "kit"

[dependencies]
kit = { version = "0.1.0", path = "..", package = "kit" }
http = { version = "1.0.0", package = "http" }
hyper = { version = "1.1.0", package = "hyper" }
matchit = { version = "0.7.3", git = "https://github.com/ibraheemdev/matchit", branch = "master", package = "matchit" }
pavex = { version = "0.1.0", path = "../../../../../../libs/pavex", package = "pavex" }
thiserror = { version = "1.0.51", package = "thiserror" }
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@ index 051b47b..9aad912 100644
let mut bp = Blueprint::new();
- bp.constructor(f!(crate::User::extract), Lifecycle::RequestScoped);
+ bp.request_scoped(f!(crate::User::extract));
bp.wrap(f!(crate::authentication::reject_anonymous));
bp.pre_process(f!(crate::authentication::reject_anonymous));
bp.route(GET, "/greet", f!(crate::routes::greet));
bp
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
```rust title="src/authentication.rs"
use std::future::IntoFuture;
use pavex::middleware::Next;
use pavex::middleware::Processing;
use pavex::response::Response;
use crate::user::User;
pub async fn reject_anonymous<C>(user: &User, next: Next<C>) -> Response
where
C: IntoFuture<Output=Response>,
pub fn reject_anonymous(user: &User) -> Processing
{
if let User::Anonymous = user {
return Response::unauthorized();
let r = Response::unauthorized();
Processing::EarlyReturn(r)
} else {
Processing::Continue
}
next.into_future().await
}
```
51 changes: 42 additions & 9 deletions docs/guide/dependency_injection/core_concepts/constructors.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,22 +97,55 @@ a type that doesn't need to be constructed.
If a type needs to be constructed, but Pavex can't find a constructor for it,
[it will report an error](../../../getting_started/quickstart/dependency_injection.md#missing-constructor).

## No mutations

Constructors are not allowed to take mutable references (i.e. `&mut T`) as inputs.

The order in which constructors are called would suddenly matter if they were allowed to mutate
their dependencies.
This would in turn require a way to specify that order, therefore increasing the overall complexity of the
framework.

## Constructors can fail

Constructors can be fallible: they can return a `Result<T, E>`, where `E` is an error type.
If a constructor is fallible, you must specify an [**error handler**](../../errors/error_handlers.md) when registering
it with the application [`Blueprint`][Blueprint].
Check out the [error handling guide](../../errors/error_handlers.md) for more details.

## Invocation order

Pavex provides no guarantees on the _relative_ invocation order of constructors.

Consider the following request handler:

--8<-- "doc_examples/guide/dependency_injection/core_concepts/project-handler.snap"

It injects two different types as input parameters, `A` and `B`.
The way input parameters are ordered in `handler`'s definition does not influence the invocation order
of the respective constructors. Pavex may invoke `A`'s constructor before `B`'s constructor,
or vice versa.

The final invocation order will be primarily determined based on:

- **Dependency constraints**.
If `A`'s constructor takes `C` as input and `C`'s constructor takes `&B` as input,
`B`'s constructor will certainly be invoked before `A`'s. There's no other way!
- **Borrow-checking constraints**.
If `A`'s constructor takes a reference to `C` as input, while `B`'s constructor takes `C` by value,
Pavex will invoke `A`'s constructor first to avoid a `.clone()`.

## No mutations

Constructors are not allowed to take mutable references (i.e. `&mut T`) as inputs.
It'd be quite difficult to reason about mutations since you can't control the
[invocation order of constructors](#invocation-order).

On the other hand, invocation order is well-defined for other types of components:
[request handlers](../../routing/request_handlers.md),
[pre-processing middlewares](../../middleware/pre_processing.md) and
[post-processing middlewares](../../middleware/post_processing.md).
That's why Pavex allows them to inject mutable references as input parameters.

!!! note "Wrapping middlewares"

Invocation order is well-defined for wrapping middlewares, but Pavex
doesn't let them manipulate mutable references.
Check [their guide](../../middleware/wrapping.md#use-with-caution)
to learn more about the rationale for this exception.


[Blueprint]: ../../../api_reference/pavex/blueprint/struct.Blueprint.html
[Blueprint::constructor]: ../../../api_reference/pavex/blueprint/struct.Blueprint.html#method.constructor
[Blueprint::singleton]: ../../../api_reference/pavex/blueprint/struct.Blueprint.html#method.singleton
Expand Down

0 comments on commit ab45cc3

Please sign in to comment.