1use crate::prelude::{Box, String, ToString, Vec, format};
4use compact_str::CompactString;
5use thiserror::Error;
6
7const MAX_CONTEXT_PARAMS: usize = 32;
8const MAX_CONTEXT_PARAM_CHARS: usize = 128;
9
10#[derive(Debug, Clone)]
12pub struct QueryContext {
13 pub sql: CompactString,
15 pub params: Box<[CompactString]>,
17 pub param_count: usize,
19}
20
21impl QueryContext {
22 pub fn new<V: core::fmt::Debug>(sql: &str, params: &[&V]) -> Self {
24 let rendered = params
25 .iter()
26 .take(MAX_CONTEXT_PARAMS)
27 .map(|param| truncate_param(format!("{param:?}")))
28 .collect::<Vec<_>>()
29 .into_boxed_slice();
30
31 Self {
32 sql: sql.into(),
33 params: rendered,
34 param_count: params.len(),
35 }
36 }
37
38 fn params_display(&self) -> String {
39 if self.param_count == 0 {
40 return "[]".to_string();
41 }
42
43 let mut rendered = String::from("[");
44 for (index, param) in self.params.iter().enumerate() {
45 if index > 0 {
46 rendered.push_str(", ");
47 }
48 rendered.push_str(param.as_str());
49 }
50 if self.param_count > self.params.len() {
51 if !self.params.is_empty() {
52 rendered.push_str(", ");
53 }
54 rendered.push_str("...");
55 rendered.push_str(&format!("(+{} more)", self.param_count - self.params.len()));
56 }
57 rendered.push(']');
58 rendered
59 }
60}
61
62fn truncate_param(mut value: String) -> CompactString {
63 if value.chars().count() <= MAX_CONTEXT_PARAM_CHARS {
64 return value.into();
65 }
66
67 let mut truncated = String::new();
68 for ch in value.drain(..).take(MAX_CONTEXT_PARAM_CHARS) {
69 truncated.push(ch);
70 }
71 truncated.push_str("...");
72 truncated.into()
73}
74
75#[derive(Debug, Error)]
77pub enum DrizzleError {
78 #[error("Execution error: {0}")]
80 ExecutionError(compact_str::CompactString),
81
82 #[error("Prepare error: {0}")]
84 PrepareError(compact_str::CompactString),
85
86 #[error("No rows found")]
88 NotFound,
89
90 #[error("Transaction error: {0}")]
92 TransactionError(compact_str::CompactString),
93
94 #[error("Mapping error: {0}")]
96 Mapping(compact_str::CompactString),
97
98 #[error("Statement error: {0}")]
100 Statement(compact_str::CompactString),
101
102 #[error("Query error: {0}")]
104 Query(CompactString),
105
106 #[error("{source}\n sql: {sql}\n params: {params}", sql = .ctx.sql, params = .ctx.params_display())]
108 QueryFailed {
109 ctx: Box<QueryContext>,
111 #[source]
113 source: Box<DrizzleError>,
114 },
115
116 #[error("Parameter conversion error: {0}")]
118 ParameterError(compact_str::CompactString),
119
120 #[error("Integer conversion error: {0}")]
122 TryFromInt(#[from] core::num::TryFromIntError),
123
124 #[error("Parse int error: {0}")]
126 ParseInt(#[from] core::num::ParseIntError),
127
128 #[error("Parse float error: {0}")]
130 ParseFloat(#[from] core::num::ParseFloatError),
131
132 #[error("Parse bool error: {0}")]
134 ParseBool(#[from] core::str::ParseBoolError),
135
136 #[error("Type conversion error: {0}")]
138 ConversionError(compact_str::CompactString),
139
140 #[error("Schema error: {0}")]
142 Schema(compact_str::CompactString),
143
144 #[error("Migration `{tag}` cannot be repaired safely: {reason}")]
147 UnsafeMigrationRepair {
148 tag: CompactString,
150 reason: CompactString,
152 },
153
154 #[error("{adapter} cannot execute this migration: {requirement}")]
157 UnsupportedMigrationExecution {
158 adapter: CompactString,
160 requirement: CompactString,
162 },
163
164 #[error("Database error: {0}")]
166 Other(compact_str::CompactString),
167
168 #[cfg(feature = "driver-error")]
171 #[error("{driver} error: {source}")]
172 Driver {
173 driver: CompactString,
175 #[source]
177 source: Box<dyn std::error::Error + Send + Sync>,
178 },
179
180 #[cfg(feature = "driver-error")]
182 #[error("{context}: {source}")]
183 External {
184 context: CompactString,
186 #[source]
188 source: Box<dyn std::error::Error + Send + Sync>,
189 },
190
191 #[cfg(feature = "rusqlite")]
193 #[error("Rusqlite error: {0}")]
194 Rusqlite(#[from] rusqlite::Error),
195
196 #[cfg(feature = "turso")]
198 #[error("Turso error: {0}")]
199 Turso(#[from] turso::Error),
200
201 #[cfg(feature = "libsql")]
203 #[error("LibSQL error: {0}")]
204 LibSQL(#[from] libsql::Error),
205
206 #[cfg(feature = "tokio-postgres")]
208 #[error("Postgres error: {0}")]
209 Postgres(#[from] tokio_postgres::Error),
210
211 #[cfg(all(feature = "postgres-sync", not(feature = "tokio-postgres")))]
212 #[error("Postgres error: {0}")]
213 Postgres(#[from] postgres::Error),
214
215 #[cfg(feature = "uuid")]
217 #[error("UUID error: {0}")]
218 UuidError(#[from] uuid::Error),
219
220 #[cfg(feature = "serde")]
222 #[error("JSON error: {0}")]
223 JsonError(#[from] serde_json::Error),
224
225 #[error("Infallible conversion error")]
227 Infallible(#[from] core::convert::Infallible),
228}
229
230impl DrizzleError {
231 #[cfg(feature = "driver-error")]
234 pub fn driver(
235 driver: impl Into<CompactString>,
236 source: impl std::error::Error + Send + Sync + 'static,
237 ) -> Self {
238 Self::Driver {
239 driver: driver.into(),
240 source: Box::new(source),
241 }
242 }
243
244 #[cfg(feature = "driver-error")]
246 pub fn external(
247 context: impl Into<CompactString>,
248 source: impl std::error::Error + Send + Sync + 'static,
249 ) -> Self {
250 Self::External {
251 context: context.into(),
252 source: Box::new(source),
253 }
254 }
255}
256
257pub type Result<T> = core::result::Result<T, DrizzleError>;
259
260pub trait ResultExt<T> {
262 fn with_query<F>(self, ctx: F) -> Result<T>
264 where
265 F: FnOnce() -> QueryContext;
266}
267
268impl<T, E> ResultExt<T> for core::result::Result<T, E>
269where
270 E: Into<DrizzleError>,
271{
272 fn with_query<F>(self, ctx: F) -> Result<T>
273 where
274 F: FnOnce() -> QueryContext,
275 {
276 self.map_err(|error| {
277 let source = error.into();
278 match source {
279 DrizzleError::QueryFailed { .. } => source,
280 other => DrizzleError::QueryFailed {
281 ctx: Box::new(ctx()),
282 source: Box::new(other),
283 },
284 }
285 })
286 }
287}
288
289#[cfg(all(test, feature = "driver-error"))]
290mod tests {
291 use super::*;
292 use std::error::Error as _;
293
294 #[test]
295 fn external_errors_keep_their_source() {
296 let error =
297 DrizzleError::external("schema diff", std::io::Error::other("invalid snapshot"));
298
299 assert_eq!(error.to_string(), "schema diff: invalid snapshot");
300 assert_eq!(
301 error.source().map(ToString::to_string),
302 Some("invalid snapshot".to_string())
303 );
304 }
305}