9 releases (5 breaking)
0.6.0 | Sep 15, 2023 |
---|---|
0.5.0 | Feb 8, 2023 |
0.4.1 | Feb 8, 2023 |
0.4.0 | Sep 20, 2022 |
0.1.2 | Mar 4, 2022 |
#510 in Database interfaces
227 downloads per month
Used in aide
31KB
437 lines
axum-sqlx-tx
Request-bound SQLx transactions for axum.
Summary
axum-sqlx-tx
provides an axum
extractor for obtaining a request-bound transaction.
The transaction begins the first time the extractor is used, and is stored with the request for use by other middleware/handlers.
The transaction is resolved depending on the status code of the response – successful (2XX
) responses will commit the transaction, otherwise it will be rolled back.
See the crate documentation for more information and examples.
lib.rs
:
Request-bound SQLx transactions for axum.
[Tx
] is an axum
extractor for obtaining a transaction that's bound to the
HTTP request. A transaction begins the first time the extractor is used for a request, and is
then stored in request extensions for use by other middleware/handlers. The transaction is
resolved depending on the status code of the eventual response – successful (HTTP 2XX
or
3XX
) responses will cause the transaction to be committed, otherwise it will be rolled back.
This behaviour is often a sensible default, and using the extractor (e.g. rather than directly
using sqlx::Transaction
s) means you can't forget to commit the transactions!
Usage
To use the [Tx
] extractor, you must first add Layer
to your app:
let pool = /* any sqlx::Pool */
let app = axum::Router::new()
// .route(...)s
.layer(axum_sqlx_tx::Layer::new(pool));
You can then simply add [Tx
] as an argument to your handlers:
use axum_sqlx_tx::Tx;
use sqlx::Sqlite;
async fn create_user(mut tx: Tx<Sqlite>, /* ... */) {
// `&mut Tx` implements `sqlx::Executor`
let user = sqlx::query("INSERT INTO users (...) VALUES (...)")
.fetch_one(&mut tx)
.await
.unwrap();
// `Tx` also implements `Deref<Target = sqlx::Transaction>` and `DerefMut`
use sqlx::Acquire;
let inner = tx.begin().await.unwrap();
/* ... */
}
If you forget to add the middleware you'll get Error::MissingExtension
(internal server
error) when using the extractor. You'll also get an error (Error::OverlappingExtractors
) if
you have multiple Tx
arguments in a single handler, or call Tx::from_request
multiple times
in a single middleware.
Error handling
axum
requires that middleware do not return errors, and that the errors returned by extractors
implement IntoResponse
. By default, Error
is used by Layer
and [Tx
] to
convert errors into HTTP 500 responses, with the error's Display
value as the response body,
however it's generally not a good practice to return internal error details to clients!
To make it easier to customise error handling, both Layer
and [Tx
] have a second generic
type parameter, E
, that can be used to override the error type that will be used to convert
the response.
use axum::response::IntoResponse;
use axum_sqlx_tx::Tx;
use sqlx::Sqlite;
struct MyError(axum_sqlx_tx::Error);
// Errors must implement From<axum_sqlx_tx::Error>
impl From<axum_sqlx_tx::Error> for MyError {
fn from(error: axum_sqlx_tx::Error) -> Self {
Self(error)
}
}
// Errors must implement IntoResponse
impl IntoResponse for MyError {
fn into_response(self) -> axum::response::Response {
// note that you would probably want to log the error or something
(http::StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response()
}
}
// Change the layer error type
let app = axum::Router::new()
// .route(...)s
.layer(axum_sqlx_tx::Layer::new_with_error::<MyError>(pool));
// Change the extractor error type
async fn create_user(mut tx: Tx<Sqlite, MyError>, /* ... */) {
/* ... */
}
Examples
See examples/
in the repo for more examples.
Dependencies
~8–24MB
~386K SLoC