use crate::prelude::{Box, String, ToString, Vec, format};
use compact_str::CompactString;
use thiserror::Error;
const MAX_CONTEXT_PARAMS: usize = 32;
const MAX_CONTEXT_PARAM_CHARS: usize = 128;
#[derive(Debug, Clone)]
pub struct QueryContext {
pub sql: CompactString,
pub params: Box<[CompactString]>,
pub param_count: usize,
}
impl QueryContext {
pub fn new<V: core::fmt::Debug>(sql: &str, params: &[&V]) -> Self {
let rendered = params
.iter()
.take(MAX_CONTEXT_PARAMS)
.map(|param| truncate_param(format!("{param:?}")))
.collect::<Vec<_>>()
.into_boxed_slice();
Self {
sql: sql.into(),
params: rendered,
param_count: params.len(),
}
}
fn params_display(&self) -> String {
if self.param_count == 0 {
return "[]".to_string();
}
let mut rendered = String::from("[");
for (index, param) in self.params.iter().enumerate() {
if index > 0 {
rendered.push_str(", ");
}
rendered.push_str(param.as_str());
}
if self.param_count > self.params.len() {
if !self.params.is_empty() {
rendered.push_str(", ");
}
rendered.push_str("...");
rendered.push_str(&format!("(+{} more)", self.param_count - self.params.len()));
}
rendered.push(']');
rendered
}
}
fn truncate_param(mut value: String) -> CompactString {
if value.chars().count() <= MAX_CONTEXT_PARAM_CHARS {
return value.into();
}
let mut truncated = String::new();
for ch in value.drain(..).take(MAX_CONTEXT_PARAM_CHARS) {
truncated.push(ch);
}
truncated.push_str("...");
truncated.into()
}
#[derive(Debug, Error)]
pub enum DrizzleError {
#[error("Execution error: {0}")]
ExecutionError(compact_str::CompactString),
#[error("Prepare error: {0}")]
PrepareError(compact_str::CompactString),
#[error("No rows found")]
NotFound,
#[error("Transaction error: {0}")]
TransactionError(compact_str::CompactString),
#[error("Mapping error: {0}")]
Mapping(compact_str::CompactString),
#[error("Statement error: {0}")]
Statement(compact_str::CompactString),
#[error("Query error: {0}")]
Query(CompactString),
#[error("{source}\n sql: {sql}\n params: {params}", sql = .ctx.sql, params = .ctx.params_display())]
QueryFailed {
ctx: Box<QueryContext>,
#[source]
source: Box<DrizzleError>,
},
#[error("Parameter conversion error: {0}")]
ParameterError(compact_str::CompactString),
#[error("Integer conversion error: {0}")]
TryFromInt(#[from] core::num::TryFromIntError),
#[error("Parse int error: {0}")]
ParseInt(#[from] core::num::ParseIntError),
#[error("Parse float error: {0}")]
ParseFloat(#[from] core::num::ParseFloatError),
#[error("Parse bool error: {0}")]
ParseBool(#[from] core::str::ParseBoolError),
#[error("Type conversion error: {0}")]
ConversionError(compact_str::CompactString),
#[error("Schema error: {0}")]
Schema(compact_str::CompactString),
#[error("Database error: {0}")]
Other(compact_str::CompactString),
#[cfg(feature = "rusqlite")]
#[error("Rusqlite error: {0}")]
Rusqlite(#[from] rusqlite::Error),
#[cfg(feature = "turso")]
#[error("Turso error: {0}")]
Turso(#[from] turso::Error),
#[cfg(feature = "libsql")]
#[error("LibSQL error: {0}")]
LibSQL(#[from] libsql::Error),
#[cfg(feature = "tokio-postgres")]
#[error("Postgres error: {0}")]
Postgres(#[from] tokio_postgres::Error),
#[cfg(all(feature = "postgres-sync", not(feature = "tokio-postgres")))]
#[error("Postgres error: {0}")]
Postgres(#[from] postgres::Error),
#[cfg(feature = "uuid")]
#[error("UUID error: {0}")]
UuidError(#[from] uuid::Error),
#[cfg(feature = "serde")]
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("Infallible conversion error")]
Infallible(#[from] core::convert::Infallible),
}
pub type Result<T> = core::result::Result<T, DrizzleError>;
pub trait ResultExt<T> {
fn with_query<F>(self, ctx: F) -> Result<T>
where
F: FnOnce() -> QueryContext;
}
impl<T, E> ResultExt<T> for core::result::Result<T, E>
where
E: Into<DrizzleError>,
{
fn with_query<F>(self, ctx: F) -> Result<T>
where
F: FnOnce() -> QueryContext,
{
self.map_err(|error| {
let source = error.into();
match source {
DrizzleError::QueryFailed { .. } => source,
other => DrizzleError::QueryFailed {
ctx: Box::new(ctx()),
source: Box::new(other),
},
}
})
}
}