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)]
14pub struct QueryContext {
15 pub sql: CompactString,
17 pub params: Box<[CompactString]>,
19 pub param_count: usize,
21}
22
23impl QueryContext {
24 pub fn new<V: core::fmt::Debug>(sql: &str, params: &[&V]) -> Self {
29 let rendered = params
30 .iter()
31 .take(MAX_CONTEXT_PARAMS)
32 .map(|param| truncate_param(format!("{param:?}")))
33 .collect::<Vec<_>>()
34 .into_boxed_slice();
35
36 Self {
37 sql: sql.into(),
38 params: rendered,
39 param_count: params.len(),
40 }
41 }
42
43 fn params_display(&self) -> String {
44 if self.param_count == 0 {
45 return "[]".to_string();
46 }
47
48 let mut rendered = String::from("[");
49 for (index, param) in self.params.iter().enumerate() {
50 if index > 0 {
51 rendered.push_str(", ");
52 }
53 rendered.push_str(param.as_str());
54 }
55 if self.param_count > self.params.len() {
56 if !self.params.is_empty() {
57 rendered.push_str(", ");
58 }
59 rendered.push_str("...");
60 rendered.push_str(&format!("(+{} more)", self.param_count - self.params.len()));
61 }
62 rendered.push(']');
63 rendered
64 }
65}
66
67fn truncate_param(mut value: String) -> CompactString {
68 if value.chars().count() <= MAX_CONTEXT_PARAM_CHARS {
69 return value.into();
70 }
71
72 let mut truncated = String::new();
73 for ch in value.drain(..).take(MAX_CONTEXT_PARAM_CHARS) {
74 truncated.push(ch);
75 }
76 truncated.push_str("...");
77 truncated.into()
78}
79
80#[derive(Debug, Error)]
86pub enum DrizzleError {
87 #[error("Execution error: {0}")]
89 ExecutionError(compact_str::CompactString),
90
91 #[error("Prepare error: {0}")]
93 PrepareError(compact_str::CompactString),
94
95 #[error("No rows found")]
97 NotFound,
98
99 #[error("Transaction error: {0}")]
101 TransactionError(compact_str::CompactString),
102
103 #[error("Mapping error: {0}")]
105 Mapping(compact_str::CompactString),
106
107 #[error("Statement error: {0}")]
109 Statement(compact_str::CompactString),
110
111 #[error("Query error: {0}")]
113 Query(CompactString),
114
115 #[error("{source}\n sql: {sql}\n params: {params}", sql = .ctx.sql, params = .ctx.params_display())]
118 QueryFailed {
119 ctx: Box<QueryContext>,
121 #[source]
123 source: Box<DrizzleError>,
124 },
125
126 #[error("Parameter conversion error: {0}")]
129 ParameterError(compact_str::CompactString),
130
131 #[error("Integer conversion error: {0}")]
133 TryFromInt(#[from] core::num::TryFromIntError),
134
135 #[error("Parse int error: {0}")]
137 ParseInt(#[from] core::num::ParseIntError),
138
139 #[error("Parse float error: {0}")]
141 ParseFloat(#[from] core::num::ParseFloatError),
142
143 #[error("Parse bool error: {0}")]
145 ParseBool(#[from] core::str::ParseBoolError),
146
147 #[error("Type conversion error: {0}")]
149 ConversionError(compact_str::CompactString),
150
151 #[error("Schema error: {0}")]
153 Schema(compact_str::CompactString),
154
155 #[error("Migration `{tag}` cannot be repaired safely: {reason}")]
158 UnsafeMigrationRepair {
159 tag: CompactString,
161 reason: CompactString,
163 },
164
165 #[error("{adapter} cannot execute this migration: {requirement}")]
168 UnsupportedMigrationExecution {
169 adapter: CompactString,
171 requirement: CompactString,
173 },
174
175 #[error("Database error: {0}")]
177 Other(compact_str::CompactString),
178
179 #[cfg(feature = "driver-error")]
182 #[error("{driver} error: {source}")]
183 Driver {
184 driver: CompactString,
186 #[source]
188 source: Box<dyn std::error::Error + Send + Sync>,
189 },
190
191 #[cfg(feature = "driver-error")]
193 #[error("{context}: {source}")]
194 External {
195 context: CompactString,
197 #[source]
199 source: Box<dyn std::error::Error + Send + Sync>,
200 },
201
202 #[cfg(feature = "rusqlite")]
204 #[error("Rusqlite error: {0}")]
205 Rusqlite(#[from] rusqlite::Error),
206
207 #[cfg(feature = "turso")]
209 #[error("Turso error: {0}")]
210 Turso(#[from] turso::Error),
211
212 #[cfg(feature = "libsql")]
214 #[error("LibSQL error: {0}")]
215 LibSQL(#[from] libsql::Error),
216
217 #[cfg(feature = "tokio-postgres")]
219 #[error("Postgres error: {0}")]
220 Postgres(#[from] tokio_postgres::Error),
221
222 #[cfg(all(feature = "postgres-sync", not(feature = "tokio-postgres")))]
224 #[error("Postgres error: {0}")]
225 Postgres(#[from] postgres::Error),
226
227 #[cfg(feature = "uuid")]
229 #[error("UUID error: {0}")]
230 UuidError(#[from] uuid::Error),
231
232 #[cfg(feature = "serde")]
234 #[error("JSON error: {0}")]
235 JsonError(#[from] serde_json::Error),
236
237 #[error("Infallible conversion error")]
239 Infallible(#[from] core::convert::Infallible),
240}
241
242impl DrizzleError {
243 #[cfg(feature = "driver-error")]
246 pub fn driver(
247 driver: impl Into<CompactString>,
248 source: impl std::error::Error + Send + Sync + 'static,
249 ) -> Self {
250 Self::Driver {
251 driver: driver.into(),
252 source: Box::new(source),
253 }
254 }
255
256 #[cfg(feature = "driver-error")]
258 pub fn external(
259 context: impl Into<CompactString>,
260 source: impl std::error::Error + Send + Sync + 'static,
261 ) -> Self {
262 Self::External {
263 context: context.into(),
264 source: Box::new(source),
265 }
266 }
267}
268
269pub type Result<T> = core::result::Result<T, DrizzleError>;
271
272pub trait ResultExt<T> {
290 fn with_query<F>(self, ctx: F) -> Result<T>
294 where
295 F: FnOnce() -> QueryContext;
296}
297
298impl<T, E> ResultExt<T> for core::result::Result<T, E>
299where
300 E: Into<DrizzleError>,
301{
302 fn with_query<F>(self, ctx: F) -> Result<T>
303 where
304 F: FnOnce() -> QueryContext,
305 {
306 self.map_err(|error| {
307 let source = error.into();
308 match source {
309 DrizzleError::QueryFailed { .. } => source,
310 other => DrizzleError::QueryFailed {
311 ctx: Box::new(ctx()),
312 source: Box::new(other),
313 },
314 }
315 })
316 }
317}
318
319#[cfg(all(test, feature = "driver-error"))]
320mod tests {
321 use super::*;
322 use std::error::Error as _;
323
324 #[test]
325 fn external_errors_keep_their_source() {
326 let error =
327 DrizzleError::external("schema diff", std::io::Error::other("invalid snapshot"));
328
329 assert_eq!(error.to_string(), "schema diff: invalid snapshot");
330 assert_eq!(
331 error.source().map(ToString::to_string),
332 Some("invalid snapshot".to_string())
333 );
334 }
335}