axum-postgres-tx 0.3.0

Request-scoped PostgreSQL transactions for axum
Documentation
use std::{marker::PhantomData, ops::Deref};

use axum_core::{RequestPartsExt, extract::FromRequestParts, response::IntoResponse};
#[cfg(feature = "bb8")]
use bb8_postgres::tokio_postgres::Transaction;
#[cfg(feature = "deadpool")]
use deadpool_postgres::tokio_postgres::Transaction;
use http::{Method, request::Parts};
use parking_lot::{ArcMutexGuard, RawMutex};

use super::extension::{Extension, LazyTx};

/// An extractor that provides access to a request-scoped PostgreSQL transaction.
///
/// When extracted in an axum handler, `Tx` automatically obtains a connection
/// from the pool and starts a transaction. The transaction is committed when
/// the response is sent (unless an error status code is returned).
///
/// `GET`, `HEAD`, and `OPTIONS` requests start a **read-only** transaction;
/// all other methods start a **read-write** transaction.
///
/// # Examples
///
/// ```rust,no_run
/// use axum_postgres_tx::Tx;
///
/// async fn handler(Tx(tx): Tx) -> String {
///     let row = tx.query_one("SELECT current_database()", &[]).await.unwrap();
///     format!("Connected to: {}", row.get::<_, String>(0))
/// }
/// ```
pub struct Tx<E = super::Error> {
    tx: ArcMutexGuard<RawMutex, LazyTx>,
    _error: PhantomData<E>,
}

impl<E> AsRef<Transaction<'static>> for Tx<E> {
    fn as_ref(&self) -> &Transaction<'static> {
        self.tx.as_ref()
    }
}

impl<E> Deref for Tx<E> {
    type Target = Transaction<'static>;
    fn deref(&self) -> &Self::Target {
        self.tx.deref()
    }
}

impl<S, E> FromRequestParts<S> for Tx<E>
where
    S: Sync,
    E: From<super::Error> + IntoResponse,
{
    type Rejection = E;
    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
        let Ok(method) = parts.extract::<Method>().await;
        let ext: &Extension = parts
            .extensions
            .get()
            .ok_or(super::Error::MissingExtension)?;
        let tx = ext
            .get(method == Method::GET || method == Method::HEAD || method == Method::OPTIONS)
            .await?;
        Ok(Self {
            tx,
            _error: PhantomData,
        })
    }
}