summaryrefslogtreecommitdiff
path: root/src/boot/handlers
diff options
context:
space:
mode:
authorOwen Jacobson <owen@grimoire.ca>2025-10-27 17:07:53 -0400
committerOwen Jacobson <owen@grimoire.ca>2025-10-28 01:27:13 -0400
commit2d05e6fb933d8c33078232b3bdfbc2aa05106d80 (patch)
tree7f5b0c3d4272edeabeb52538fa7ed3090d6bccd1 /src/boot/handlers
parent58e6496558a01052537c5272169aea3e79ccbc8e (diff)
Make `Boot` a freestanding app type, rather than a view of `crate::app::App`'s internals.
In the course of working on web push, I determined that we probably need to make `App` generic over the web push client we're using, so that tests can use a dummy client while the real app uses a client created at startup and maintained over the life of the program's execution. The most direct implementation of that is to render App as `App<P>`, where the parameter is occupied by the specific web push client type in use. However, doing this requires refactoring at _every_ site that mentions `App`, including every handler, even though the vast majority of those sites will not be concerned with web push. I reviewed a few options with @wlonk: * Accept the type parameter and apply it everywhere, as the cost of supporting web push. * Hard-code the use of a specific web push client. * Insulate handlers &c from `App` via provider traits, mimicing what we do for repository provider traits today. * Treat each app type as a freestanding state in its own right, so that only push-related components need to consider push clients (as far as is feasible). This is a prototype towards that last point, using a simple app component (boot) as a testbed. `FromRef` allows handlers that take a `Boot` to be used in routes that provide an `App`, so this is a contained change. However, the structure of `FromRef` prevents `Boot` from carrying any lifetime narrower than `'static`, so it now holds clones of the state fields it acquires from App, instead of references. This is fine - that's just a database pool, and sqlx's pool type is designed to be shared via cloning. From <https://docs.rs/sqlx/latest/sqlx/struct.Pool.html>: > Cloning Pool is cheap as it is simply a reference-counted handle to the inner pool state.
Diffstat (limited to 'src/boot/handlers')
-rw-r--r--src/boot/handlers/boot/mod.rs9
-rw-r--r--src/boot/handlers/boot/test.rs16
2 files changed, 14 insertions, 11 deletions
diff --git a/src/boot/handlers/boot/mod.rs b/src/boot/handlers/boot/mod.rs
index 3e022b1..5ff7802 100644
--- a/src/boot/handlers/boot/mod.rs
+++ b/src/boot/handlers/boot/mod.rs
@@ -7,15 +7,18 @@ use axum::{
use serde::Serialize;
use crate::{
- app::App, boot::Snapshot, error::Internal, event::Heartbeat, login::Login,
+ boot::{Snapshot, app::Boot},
+ error::Internal,
+ event::Heartbeat,
+ login::Login,
token::extract::Identity,
};
#[cfg(test)]
mod test;
-pub async fn handler(State(app): State<App>, identity: Identity) -> Result<Response, Internal> {
- let snapshot = app.boot().snapshot().await?;
+pub async fn handler(State(boot): State<Boot>, identity: Identity) -> Result<Response, Internal> {
+ let snapshot = boot.snapshot().await?;
let heartbeat = Heartbeat::TIMEOUT;
Ok(Response {
diff --git a/src/boot/handlers/boot/test.rs b/src/boot/handlers/boot/test.rs
index cb50442..a9891eb 100644
--- a/src/boot/handlers/boot/test.rs
+++ b/src/boot/handlers/boot/test.rs
@@ -8,7 +8,7 @@ async fn returns_identity() {
let app = fixtures::scratch_app().await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer.clone())
+ let response = super::handler(State(app.boot()), viewer.clone())
.await
.expect("boot always succeeds");
@@ -21,7 +21,7 @@ async fn includes_users() {
let spectator = fixtures::user::create(&app, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -42,7 +42,7 @@ async fn includes_conversations() {
let conversation = fixtures::conversation::create(&app, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -65,7 +65,7 @@ async fn includes_messages() {
let message = fixtures::message::send(&app, &conversation, &sender, &fixtures::now()).await;
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -94,7 +94,7 @@ async fn includes_expired_messages() {
.expect("expiry never fails");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -136,7 +136,7 @@ async fn includes_deleted_messages() {
.expect("deleting valid message succeeds");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -175,7 +175,7 @@ async fn includes_expired_conversations() {
.expect("expiry never fails");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");
@@ -214,7 +214,7 @@ async fn includes_deleted_conversations() {
.expect("deleting a valid conversation succeeds");
let viewer = fixtures::identity::fictitious();
- let response = super::handler(State(app), viewer)
+ let response = super::handler(State(app.boot()), viewer)
.await
.expect("boot always succeeds");