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}