sqlx_error/
lib.rs

1//! # sqlx-error
2//!
3//! A wrapper around `sqlx::Error` to provide error path and additional context.
4//!
5//! ## Usage
6//!
7//! ```rust
8//! use sqlx_error::{sqlx_error, SqlxError};
9//!
10//! #[derive(Debug, thiserror::Error)]
11//! pub enum MyError {
12//!     #[error(transparent)]
13//!     Sqlx(#[from] SqlxError),
14//! }
15//!
16//! /// If you have a single sqlx query per function, the function path by itself could provide
17//! /// enough context
18//! fn foo() -> Result<(), MyError> {
19//!     Err(sqlx::Error::RowNotFound).map_err(sqlx_error!())?;
20//!     Ok(())
21//! }
22//!
23//! /// Or you can add more context
24//! fn bar() -> Result<(), MyError> {
25//!     Err(sqlx::Error::RowNotFound).map_err(sqlx_error!("more context"))?;
26//!     Ok(())
27//! }
28//!
29//! # fn main() {
30//! assert_eq!(foo().unwrap_err().to_string(), "sqlx: rust_out::foo at src/lib.rs:15");
31//! assert_eq!(bar().unwrap_err().to_string(), "sqlx: more context in rust_out::bar at src/lib.rs:21");
32//! # }
33//! ```
34
35#![warn(clippy::all, missing_docs, nonstandard_style, future_incompatible)]
36
37use std::{error::Error, fmt, option::Option};
38
39/// Sqlx error wrapper to hold additional info
40#[derive(Debug)]
41pub struct SqlxError(::sqlx_core::error::Error, String);
42
43/// A `Result` based on `SqlxError`
44pub type SqlxResult<T> = Result<T, SqlxError>;
45
46/// The macro adds error path and optional description to`sqlx::Error`.
47///
48/// If you have a single sqlx query per function and the function path by itself provides enough
49/// context you can just use `sqlx_error!()`. If it's not enough you can provide an additional
50/// message with `sqlx_error!("more context")`.
51#[macro_export]
52macro_rules! sqlx_error {
53    () => {
54        |e| $crate::SqlxError::new(e, $crate::__private::code_path::code_path!().into())
55    };
56    ($desc:expr) => {
57        |e| {
58            $crate::SqlxError::new(
59                e,
60                format!(
61                    "{} in {}",
62                    $desc,
63                    $crate::__private::code_path::code_path!()
64                ),
65            )
66        }
67    };
68}
69
70#[doc(hidden)]
71pub mod __private {
72    pub use code_path;
73}
74
75impl SqlxError {
76    /// Creates an `SqlxError` instance
77    pub fn new(err: sqlx_core::error::Error, msg: String) -> Self {
78        Self(err, msg)
79    }
80}
81
82impl fmt::Display for SqlxError {
83    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84        write!(f, "sqlx: {}", self.1)
85    }
86}
87
88impl Error for SqlxError {
89    fn source(&self) -> Option<&(dyn Error + 'static)> {
90        Option::Some(&self.0)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[derive(Debug, thiserror::Error)]
99    pub enum MyError {
100        #[error(transparent)]
101        Sqlx(#[from] SqlxError),
102    }
103
104    fn bare() -> Result<(), MyError> {
105        Err(sqlx::Error::RowNotFound).map_err(sqlx_error!())?;
106        Ok(())
107    }
108
109    fn with_context() -> Result<(), MyError> {
110        Err(sqlx::Error::RowNotFound).map_err(sqlx_error!("my context"))?;
111        Ok(())
112    }
113
114    #[test]
115    fn works() {
116        assert!(bare()
117            .unwrap_err()
118            .to_string()
119            .starts_with("sqlx: sqlx_error::tests::bare at src/lib.rs:"));
120
121        assert!(with_context()
122            .unwrap_err()
123            .to_string()
124            .starts_with("sqlx: my context in sqlx_error::tests::with_context at src/lib.rs:"));
125    }
126}