use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Span {
pub start: u32,
pub end: u32,
}
impl Span {
#[must_use]
pub const fn new(start: u32, end: u32) -> Self {
Self { start, end }
}
#[must_use]
pub const fn len(self) -> u32 {
self.end.saturating_sub(self.start)
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.len() == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorCode {
Parser,
Binder,
Catalog,
Conversion,
OutOfRange,
InvalidInput,
OutOfMemory,
Io,
NotImplemented,
Constraint,
Transaction,
Interrupt,
Internal,
}
impl ErrorCode {
#[must_use]
pub const fn duckdb_name(self) -> &'static str {
match self {
Self::Parser => "Parser Error",
Self::Binder => "Binder Error",
Self::Catalog => "Catalog Error",
Self::Conversion => "Conversion Error",
Self::OutOfRange => "Out of Range Error",
Self::InvalidInput => "Invalid Input Error",
Self::OutOfMemory => "Out of Memory Error",
Self::Io => "IO Error",
Self::NotImplemented => "Not implemented Error",
Self::Constraint => "Constraint Error",
Self::Transaction => "TransactionContext Error",
Self::Interrupt => "Interrupt Error",
Self::Internal => "INTERNAL Error",
}
}
#[must_use]
pub const fn is_user_error(self) -> bool {
matches!(
self,
Self::Parser
| Self::Binder
| Self::Catalog
| Self::Conversion
| Self::OutOfRange
| Self::InvalidInput
| Self::Constraint
| Self::Transaction
)
}
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.duckdb_name())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error(Box<Payload>);
#[derive(Debug, Clone, PartialEq, Eq)]
struct Payload {
code: ErrorCode,
message: String,
span: Option<Span>,
}
impl Error {
pub fn new(code: ErrorCode, message: impl Into<String>) -> Self {
Self(Box::new(Payload { code, message: message.into(), span: None }))
}
#[must_use]
pub fn with_span(mut self, span: Span) -> Self {
self.0.span = Some(span);
self
}
#[must_use]
pub fn code(&self) -> ErrorCode {
self.0.code
}
#[must_use]
pub fn message(&self) -> &str {
&self.0.message
}
#[must_use]
pub fn span(&self) -> Option<Span> {
self.0.span
}
pub fn parser(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Parser, message)
}
pub fn binder(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Binder, message)
}
pub fn catalog(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Catalog, message)
}
pub fn conversion(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Conversion, message)
}
pub fn out_of_range(message: impl Into<String>) -> Self {
Self::new(ErrorCode::OutOfRange, message)
}
pub fn invalid_input(message: impl Into<String>) -> Self {
Self::new(ErrorCode::InvalidInput, message)
}
pub fn out_of_memory(message: impl Into<String>) -> Self {
Self::new(ErrorCode::OutOfMemory, message)
}
pub fn io(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Io, message)
}
pub fn not_implemented(message: impl Into<String>) -> Self {
Self::new(ErrorCode::NotImplemented, message)
}
pub fn constraint(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Constraint, message)
}
pub fn transaction(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Transaction, message)
}
pub fn interrupt(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Interrupt, message)
}
pub fn internal(message: impl Into<String>) -> Self {
Self::new(ErrorCode::Internal, message)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.0.code, self.0.message)
}
}
impl std::error::Error for Error {}
impl From<std::io::Error> for Error {
fn from(error: std::io::Error) -> Self {
Self::io(error.to_string())
}
}
#[cfg(test)]
mod tests {
use super::{Error, ErrorCode, Span};
#[test]
fn an_error_prints_the_way_duckdb_prints_it() {
let error = Error::binder("Referenced column \"nope\" not found in FROM clause!");
assert_eq!(
error.to_string(),
"Binder Error: Referenced column \"nope\" not found in FROM clause!"
);
}
#[test]
fn a_result_is_no_wider_than_the_value_in_it() {
assert_eq!(size_of::<Error>(), size_of::<usize>());
assert_eq!(size_of::<Result<String, Error>>(), size_of::<String>());
}
#[test]
fn a_span_survives_being_attached() {
let error = Error::parser("syntax error at or near \"FROM\"").with_span(Span::new(7, 11));
assert_eq!(error.span(), Some(Span::new(7, 11)));
assert_eq!(error.span().map(Span::len), Some(4));
assert_eq!(error.code(), ErrorCode::Parser);
}
#[test]
fn the_fuzzer_can_tell_our_bugs_from_the_query_s_bugs() {
assert!(ErrorCode::Binder.is_user_error());
assert!(ErrorCode::Conversion.is_user_error());
assert!(!ErrorCode::Internal.is_user_error());
assert!(!ErrorCode::OutOfMemory.is_user_error());
assert!(!ErrorCode::NotImplemented.is_user_error());
}
}