diff options
| author | Owen Jacobson <owen@grimoire.ca> | 2025-10-27 17:29:36 -0400 |
|---|---|---|
| committer | Owen Jacobson <owen@grimoire.ca> | 2025-10-28 01:27:13 -0400 |
| commit | 398bc79a3f1f4316e6cc33b6e6bce133c1db3e4c (patch) | |
| tree | f34f9ec001d2064b080b302fba175d66b08865ce /src | |
| parent | 2d05e6fb933d8c33078232b3bdfbc2aa05106d80 (diff) | |
Convert the `Conversations` component into a freestanding struct.
Unlike the previous example, this involves cloning an event broadcaster, as well. This is, per the documentation, how the type may be used. From <https://docs.rs/tokio/latest/tokio/sync/broadcast/fn.channel.html>:
> The Sender can be cloned to send to the same channel from multiple points in the process or it can be used concurrently from an `Arc`.
The language is less firm than the language sqlx uses for its pool, but the intent is clear enough, and it works in practice.
Diffstat (limited to 'src')
| -rw-r--r-- | src/app.rs | 10 | ||||
| -rw-r--r-- | src/conversation/app.rs | 10 | ||||
| -rw-r--r-- | src/conversation/handlers/create/mod.rs | 8 | ||||
| -rw-r--r-- | src/conversation/handlers/create/test.rs | 66 | ||||
| -rw-r--r-- | src/conversation/handlers/delete/mod.rs | 9 | ||||
| -rw-r--r-- | src/conversation/handlers/delete/test.rs | 12 | ||||
| -rw-r--r-- | src/test/fixtures/conversation.rs | 11 | ||||
| -rw-r--r-- | src/ui/handlers/conversation.rs | 7 |
8 files changed, 78 insertions, 55 deletions
@@ -38,8 +38,8 @@ impl App { Boot::new(self.db.clone()) } - pub const fn conversations(&self) -> Conversations<'_> { - Conversations::new(&self.db, &self.events) + pub fn conversations(&self) -> Conversations { + Conversations::new(self.db.clone(), self.events.clone()) } pub const fn events(&self) -> Events<'_> { @@ -77,3 +77,9 @@ impl FromRef<App> for Boot { app.boot() } } + +impl FromRef<App> for Conversations { + fn from_ref(app: &App) -> Self { + app.conversations() + } +} diff --git a/src/conversation/app.rs b/src/conversation/app.rs index 26886af..2b62e77 100644 --- a/src/conversation/app.rs +++ b/src/conversation/app.rs @@ -15,13 +15,13 @@ use crate::{ name::{self, Name}, }; -pub struct Conversations<'a> { - db: &'a SqlitePool, - events: &'a Broadcaster, +pub struct Conversations { + db: SqlitePool, + events: Broadcaster, } -impl<'a> Conversations<'a> { - pub const fn new(db: &'a SqlitePool, events: &'a Broadcaster) -> Self { +impl Conversations { + pub const fn new(db: SqlitePool, events: Broadcaster) -> Self { Self { db, events } } diff --git a/src/conversation/handlers/create/mod.rs b/src/conversation/handlers/create/mod.rs index 18eca1f..2b7fa39 100644 --- a/src/conversation/handlers/create/mod.rs +++ b/src/conversation/handlers/create/mod.rs @@ -5,9 +5,8 @@ use axum::{ }; use crate::{ - app::App, clock::RequestedAt, - conversation::{Conversation, app}, + conversation::{Conversation, app, app::Conversations}, error::Internal, name::Name, token::extract::Identity, @@ -17,13 +16,12 @@ use crate::{ mod test; pub async fn handler( - State(app): State<App>, + State(conversations): State<Conversations>, _: Identity, // requires auth, but doesn't actually care who you are RequestedAt(created_at): RequestedAt, Json(request): Json<Request>, ) -> Result<Response, Error> { - let conversation = app - .conversations() + let conversation = conversations .create(&request.name, &created_at) .await .map_err(Error)?; diff --git a/src/conversation/handlers/create/test.rs b/src/conversation/handlers/create/test.rs index bc05b00..380bb13 100644 --- a/src/conversation/handlers/create/test.rs +++ b/src/conversation/handlers/create/test.rs @@ -22,10 +22,14 @@ async fn new_conversation() { let name = fixtures::conversation::propose(); let request = super::Request { name: name.clone() }; - let super::Response(response) = - super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creating a conversation in an empty app succeeds"); + let super::Response(response) = super::handler( + State(app.conversations()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect("creating a conversation in an empty app succeeds"); // Verify the structure of the response @@ -77,10 +81,14 @@ async fn duplicate_name() { let request = super::Request { name: conversation.name.clone(), }; - let super::Error(error) = - super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("duplicate conversation name should fail the request"); + let super::Error(error) = super::handler( + State(app.conversations()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect_err("duplicate conversation name should fail the request"); // Verify the structure of the response @@ -110,10 +118,14 @@ async fn conflicting_canonical_name() { let request = super::Request { name: conflicting_name.clone(), }; - let super::Error(error) = - super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect_err("duplicate conversation name should fail the request"); + let super::Error(error) = super::handler( + State(app.conversations()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect_err("duplicate conversation name should fail the request"); // Verify the structure of the response @@ -135,7 +147,7 @@ async fn invalid_name() { let name = fixtures::conversation::propose_invalid_name(); let request = super::Request { name: name.clone() }; let super::Error(error) = crate::conversation::handlers::create::handler( - State(app.clone()), + State(app.conversations()), creator, fixtures::now(), Json(request), @@ -163,7 +175,7 @@ async fn name_reusable_after_delete() { let request = super::Request { name: name.clone() }; let super::Response(response) = super::handler( - State(app.clone()), + State(app.conversations()), creator.clone(), fixtures::now(), Json(request), @@ -181,10 +193,14 @@ async fn name_reusable_after_delete() { // Call the endpoint (second time) let request = super::Request { name: name.clone() }; - let super::Response(response) = - super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creation succeeds after original conversation deleted"); + let super::Response(response) = super::handler( + State(app.conversations()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect("creation succeeds after original conversation deleted"); // Verify the structure of the response @@ -212,7 +228,7 @@ async fn name_reusable_after_expiry() { let request = super::Request { name: name.clone() }; let super::Response(_) = super::handler( - State(app.clone()), + State(app.conversations()), creator.clone(), fixtures::ancient(), Json(request), @@ -230,10 +246,14 @@ async fn name_reusable_after_expiry() { // Call the endpoint (second time) let request = super::Request { name: name.clone() }; - let super::Response(response) = - super::handler(State(app.clone()), creator, fixtures::now(), Json(request)) - .await - .expect("creation succeeds after original conversation expired"); + let super::Response(response) = super::handler( + State(app.conversations()), + creator, + fixtures::now(), + Json(request), + ) + .await + .expect("creation succeeds after original conversation expired"); // Verify the structure of the response diff --git a/src/conversation/handlers/delete/mod.rs b/src/conversation/handlers/delete/mod.rs index 272165a..231e433 100644 --- a/src/conversation/handlers/delete/mod.rs +++ b/src/conversation/handlers/delete/mod.rs @@ -5,9 +5,8 @@ use axum::{ }; use crate::{ - app::App, clock::RequestedAt, - conversation::{self, app, handlers::PathInfo}, + conversation::{self, app, app::Conversations, handlers::PathInfo}, error::{Internal, NotFound}, token::extract::Identity, }; @@ -16,14 +15,12 @@ use crate::{ mod test; pub async fn handler( - State(app): State<App>, + State(conversations): State<Conversations>, Path(conversation): Path<PathInfo>, RequestedAt(deleted_at): RequestedAt, _: Identity, ) -> Result<Response, Error> { - app.conversations() - .delete(&conversation, &deleted_at) - .await?; + conversations.delete(&conversation, &deleted_at).await?; Ok(Response { id: conversation }) } diff --git a/src/conversation/handlers/delete/test.rs b/src/conversation/handlers/delete/test.rs index 2718d3b..e9e882a 100644 --- a/src/conversation/handlers/delete/test.rs +++ b/src/conversation/handlers/delete/test.rs @@ -14,7 +14,7 @@ pub async fn valid_conversation() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let response = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.id.clone()), fixtures::now(), deleter, @@ -52,7 +52,7 @@ pub async fn invalid_conversation_id() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let conversation = fixtures::conversation::fictitious(); let super::Error(error) = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.clone()), fixtures::now(), deleter, @@ -81,7 +81,7 @@ pub async fn conversation_deleted() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let super::Error(error) = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.id.clone()), fixtures::now(), deleter, @@ -110,7 +110,7 @@ pub async fn conversation_expired() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let super::Error(error) = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.id.clone()), fixtures::now(), deleter, @@ -144,7 +144,7 @@ pub async fn conversation_purged() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let super::Error(error) = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.id.clone()), fixtures::now(), deleter, @@ -170,7 +170,7 @@ pub async fn conversation_not_empty() { let deleter = fixtures::identity::create(&app, &fixtures::now()).await; let super::Error(error) = super::handler( - State(app.clone()), + State(app.conversations()), Path(conversation.id.clone()), fixtures::now(), deleter, diff --git a/src/test/fixtures/conversation.rs b/src/test/fixtures/conversation.rs index fb2f58d..f0d8c8c 100644 --- a/src/test/fixtures/conversation.rs +++ b/src/test/fixtures/conversation.rs @@ -1,3 +1,4 @@ +use axum::extract::FromRef; use faker_rand::{ en_us::{addresses::CityName, names::FullName}, faker_impl_from_templates, @@ -6,15 +7,17 @@ use faker_rand::{ use rand; use crate::{ - app::App, clock::RequestedAt, - conversation::{self, Conversation}, + conversation::{self, Conversation, app::Conversations}, name::Name, }; -pub async fn create(app: &App, created_at: &RequestedAt) -> Conversation { +pub async fn create<App>(app: &App, created_at: &RequestedAt) -> Conversation +where + Conversations: FromRef<App>, +{ let name = propose(); - app.conversations() + Conversations::from_ref(app) .create(&name, created_at) .await .expect("should always succeed if the conversation is actually new") diff --git a/src/ui/handlers/conversation.rs b/src/ui/handlers/conversation.rs index f1bb319..2ff090c 100644 --- a/src/ui/handlers/conversation.rs +++ b/src/ui/handlers/conversation.rs @@ -4,8 +4,7 @@ use axum::{ }; use crate::{ - app::App, - conversation::{self, app}, + conversation::{self, app, app::Conversations}, error::Internal, token::extract::Identity, ui::{ @@ -15,12 +14,12 @@ use crate::{ }; pub async fn handler( - State(app): State<App>, + State(conversations): State<Conversations>, identity: Option<Identity>, Path(conversation): Path<conversation::Id>, ) -> Result<Asset, Error> { let _ = identity.ok_or(Error::NotLoggedIn)?; - app.conversations() + conversations .get(&conversation) .await .map_err(Error::from)?; |
