Skip to main content

sova_db/
tx.rs

1use crate::handle::{DbHandle, DbPool};
2use crate::DbError;
3use sea_orm::TransactionTrait;
4use sova_core::extend::named;
5use sova_core::{Next, Request, Response};
6use std::sync::Arc;
7
8/// Open a request-scoped transaction; commit on 2xx, otherwise rollback.
9pub fn transaction() -> impl sova_core::extend::IntoMwEntry {
10    named("db_tx", |mut req: Request, next: Next| async move {
11        let Some(DbHandle::Conn(conn)) = req.get::<DbHandle>().cloned() else {
12            return Response::text("database connection missing for transaction").status(500);
13        };
14        let tx = match conn.begin().await {
15            Ok(tx) => tx,
16            Err(err) => return DbError(err).into_response_via_error(),
17        };
18        let arc = Arc::new(tx);
19        req.set(DbHandle::Tx(Arc::clone(&arc)));
20        let res = next(req).await;
21        match Arc::try_unwrap(arc) {
22            Ok(tx) => {
23                if res.status_code().is_success() {
24                    if let Err(err) = tx.commit().await {
25                        tracing::error!(error = %err, "db commit failed");
26                        return Response::text("Internal Server Error").status(500);
27                    }
28                } else if let Err(err) = tx.rollback().await {
29                    tracing::error!(error = %err, "db rollback failed");
30                }
31            }
32            Err(_) => tracing::error!("db transaction still held after request"),
33        }
34        res
35    })
36}
37
38trait IntoResponseViaError {
39    fn into_response_via_error(self) -> Response;
40}
41
42impl IntoResponseViaError for DbError {
43    fn into_response_via_error(self) -> Response {
44        sova_core::IntoResponse::into_response(self)
45    }
46}
47
48/// Helper used by plugin install to inject Conn handle.
49pub(crate) fn inject_conn(pool: DbPool) -> impl sova_core::extend::IntoMwEntry {
50    named(
51        "db",
52        sova_core::with_state(pool, |pool, mut req, next| async move {
53            match pool.get() {
54                Ok(conn) => {
55                    req.set(DbHandle::Conn(conn));
56                    next(req).await
57                }
58                Err(err) => sova_core::IntoResponse::into_response(err),
59            }
60        }),
61    )
62}