axum_sqlx_tx/error.rs
1/// Possible errors when extracting [`Tx`] from a request.
2///
3/// Errors can occur at two points during the request lifecycle:
4///
5/// 1. The [`Tx`] extractor might fail to obtain a connection from the pool and `BEGIN` a
6/// transaction. This could be due to:
7///
8/// - Forgetting to add the middleware: [`Error::MissingExtension`].
9/// - Calling the extractor multiple times in the same request: [`Error::OverlappingExtractors`].
10/// - A problem communicating with the database: [`Error::Database`].
11///
12/// 2. The middleware [`Layer`] might fail to commit the transaction. This could be due to a problem
13/// communicating with the database, or else a logic error (e.g. unsatisfied deferred
14/// constraint): [`Error::Database`].
15///
16/// `axum` requires that errors can be turned into responses. The [`Error`] type converts into a
17/// HTTP 500 response with the error message as the response body. This may be suitable for
18/// development or internal services but it's generally not advisable to return internal error
19/// details to clients.
20///
21/// You can override the error types for both the [`Tx`] extractor and [`Layer`]:
22///
23/// - Override the [`Tx`]`<DB, E>` error type using the `E` generic type parameter. `E` must be
24/// convertible from [`Error`] (e.g. [`Error`]`: Into<E>`).
25///
26/// - Override the [`Layer`] error type using [`Config::layer_error`](crate::Config::layer_error).
27/// The layer error type must be convertible from `sqlx::Error` (e.g.
28/// `sqlx::Error: Into<LayerError>`).
29///
30/// In both cases, the error type must implement `axum::response::IntoResponse`.
31///
32/// ```
33/// use axum::{response::IntoResponse, routing::post};
34///
35/// enum MyError{
36/// Extractor(axum_sqlx_tx::Error),
37/// Layer(sqlx::Error),
38/// }
39///
40/// impl From<axum_sqlx_tx::Error> for MyError {
41/// fn from(error: axum_sqlx_tx::Error) -> Self {
42/// Self::Extractor(error)
43/// }
44/// }
45///
46/// impl From<sqlx::Error> for MyError {
47/// fn from(error: sqlx::Error) -> Self {
48/// Self::Layer(error)
49/// }
50/// }
51///
52/// impl IntoResponse for MyError {
53/// fn into_response(self) -> axum::response::Response {
54/// // note that you would probably want to log the error as well
55/// (http::StatusCode::INTERNAL_SERVER_ERROR, "internal server error").into_response()
56/// }
57/// }
58///
59/// // Override the `Tx` error type using the second generic type parameter
60/// type Tx = axum_sqlx_tx::Tx<sqlx::Sqlite, MyError>;
61///
62/// # async fn foo() {
63/// let pool = sqlx::SqlitePool::connect("...").await.unwrap();
64///
65/// let (state, layer) = Tx::config(pool)
66/// // Override the `Layer` error type using the `Config` API
67/// .layer_error::<MyError>()
68/// .setup();
69/// # let app = axum::Router::new()
70/// # .route("/", post(create_user))
71/// # .layer(layer)
72/// # .with_state(state);
73/// # let listener: tokio::net::TcpListener = todo!();
74/// # axum::serve(listener, app);
75/// # }
76/// # async fn create_user(mut tx: Tx, /* ... */) {
77/// # /* ... */
78/// # }
79/// ```
80///
81/// [`Tx`]: crate::Tx
82/// [`Layer`]: crate::Layer
83#[derive(Debug, thiserror::Error)]
84pub enum Error {
85 /// Indicates that the [`Layer`](crate::Layer) middleware was not installed.
86 #[error("required extension not registered; did you add the axum_sqlx_tx::Layer middleware?")]
87 MissingExtension,
88
89 /// Indicates that [`Tx`](crate::Tx) was extracted multiple times in a single
90 /// handler/middleware.
91 #[error("axum_sqlx_tx::Tx extractor used multiple times in the same handler/middleware")]
92 OverlappingExtractors,
93
94 /// A database error occurred when starting or committing the transaction.
95 #[error(transparent)]
96 Database {
97 #[from]
98 error: sqlx::Error,
99 },
100}
101
102impl axum_core::response::IntoResponse for Error {
103 fn into_response(self) -> axum_core::response::Response {
104 (http::StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response()
105 }
106}