axum_sqlx_tx/
lib.rs

1//! Request-bound [SQLx] transactions for [axum].
2//!
3//! [SQLx]: https://github.com/launchbadge/sqlx#readme
4//! [axum]: https://github.com/tokio-rs/axum#readme
5//!
6//! [`Tx`] is an `axum` [extractor][axum extractors] for obtaining a transaction that's bound to the
7//! HTTP request. A transaction begins the first time the extractor is used for a request, and is
8//! then stored in [request extensions] for use by other middleware/handlers. The transaction is
9//! resolved depending on the status code of the eventual response – successful (HTTP `2XX` or
10//! `3XX`) responses will cause the transaction to be committed, otherwise it will be rolled back.
11//!
12//! This behaviour is often a sensible default, and using the extractor (e.g. rather than directly
13//! using [`sqlx::Transaction`]s) means you can't forget to commit the transactions!
14//!
15//! [axum extractors]: https://docs.rs/axum/latest/axum/#extractors
16//! [request extensions]: https://docs.rs/http/latest/http/struct.Extensions.html
17//!
18//! # Usage
19//!
20//! To use the [`Tx`] extractor, you must first add [`State`] and [`Layer`] to your app. [`State`]
21//! holds the configuration for the extractor, and the [`Layer`] middleware manages the
22//! request-bound transaction.
23//!
24//! ```
25//! # async fn foo() {
26//! // It's recommended to create aliases specialised for your extractor(s)
27//! type Tx = axum_sqlx_tx::Tx<sqlx::Sqlite>;
28//!
29//! let pool = sqlx::SqlitePool::connect("...").await.unwrap();
30//!
31//! let (state, layer) = Tx::setup(pool);
32//!
33//! let app = axum::Router::new()
34//!     // .route(...)s
35//! #   .route("/", axum::routing::get(|tx: Tx| async move {}))
36//!     .layer(layer)
37//!     .with_state(state);
38//! # let listener: tokio::net::TcpListener = todo!();
39//! # axum::serve(listener, app);
40//! # }
41//! ```
42//!
43//! You can then simply add [`Tx`] as an argument to your handlers:
44//!
45//! ```
46//! type Tx = axum_sqlx_tx::Tx<sqlx::Sqlite>;
47//!
48//! async fn create_user(mut tx: Tx, /* ... */) {
49//!     // `&mut Tx` implements `sqlx::Executor`
50//!     let user = sqlx::query("INSERT INTO users (...) VALUES (...)")
51//!         .fetch_one(&mut tx)
52//!         .await
53//!         .unwrap();
54//!
55//!     // `Tx` also implements `Deref<Target = sqlx::Transaction>` and `DerefMut`
56//!     use sqlx::Acquire;
57//!     let inner = tx.begin().await.unwrap();
58//!     /* ... */
59//! }
60//! ```
61//!
62//! ## Error handling
63//!
64//! `axum` requires that errors can be turned into responses. The [`Error`] type converts into a
65//! HTTP 500 response with the error message as the response body. This may be suitable for
66//! development or internal services but it's generally not advisable to return internal error
67//! details to clients.
68//!
69//! See [`Error`] for how to customise error handling.
70//!
71//! ## Multiple databases
72//!
73//! If you need to work with multiple databases, you can define marker structs for each. See
74//! [`Marker`] for an example.
75//!
76//! It's not currently possible to use `Tx` for a dynamic number of databases. Feel free to open an
77//! issue if you have a requirement for this.
78//!
79//! ## Accessing the pool
80//!
81//! Note that [`State`] implements [`FromRef`](axum_core::extract::FromRef) into the inner SQLx pool. Therefore,
82//! if you still need to access the database pool at some handler, you can use axum's `State`
83//! extractor normally.
84//!
85//! ```
86//! use axum::extract::State;
87//!
88//! async fn this_still_works(State(pool): State<sqlx::SqlitePool>) {
89//!     /* ... */
90//! }
91//! ```
92//!
93//! # Examples
94//!
95//! See [`examples/`][examples] in the repo for more examples.
96//!
97//! [examples]: https://github.com/digital-society-coop/axum-sqlx-tx/tree/master/examples
98
99#![cfg_attr(doc, deny(warnings))]
100
101mod config;
102mod error;
103mod extension;
104mod layer;
105mod marker;
106mod state;
107mod tx;
108
109pub use crate::{
110    config::Config,
111    error::Error,
112    layer::{Layer, Service},
113    marker::Marker,
114    state::State,
115    tx::Tx,
116};