use std::borrow::Cow;
use std::error::Error as StdError;
use std::fmt;
use std::io;
use dsq_formats;
#[cfg(feature = "io")]
use dsq_io;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)]
pub enum Error {
Io(io::Error),
Polars(polars::error::PolarsError),
Json(serde_json::Error),
Format(FormatError),
Filter(FilterError),
Type(TypeError),
Operation(Cow<'static, str>),
Config(String),
Multiple(Vec<Error>),
}
#[derive(Debug, Clone)]
pub enum FormatError {
Unknown(String),
DetectionFailed(String),
UnsupportedFeature(String),
SchemaMismatch {
expected: String,
actual: String,
},
InvalidOption(String),
}
#[derive(Debug, Clone)]
pub enum FilterError {
Parse(String),
Compile(String),
Runtime(String),
Undefined(String),
TypeMismatch {
expected: String,
actual: String,
},
ArgumentCount {
expected: usize,
actual: usize,
},
}
#[derive(Debug, Clone)]
pub enum TypeError {
InvalidConversion {
from: String,
to: String,
},
UnsupportedOperation {
operation: String,
typ: String,
},
FieldNotFound {
field: String,
typ: String,
},
OutOfRange(String),
UnexpectedNull(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Io(e) => write!(f, "I/O error: {e}"),
Error::Polars(e) => write!(f, "DataFrame error: {e}"),
Error::Json(e) => write!(f, "JSON error: {e}"),
Error::Format(e) => write!(f, "Format error: {e}"),
Error::Filter(e) => write!(f, "Filter error: {e}"),
Error::Type(e) => write!(f, "Type error: {e}"),
Error::Operation(msg) => write!(f, "Operation error: {msg}"),
Error::Config(msg) => write!(f, "Configuration error: {msg}"),
Error::Multiple(errors) => {
write!(f, "Multiple errors occurred:")?;
for (i, e) in errors.iter().enumerate() {
write!(f, "\n {}. {}", i + 1, e)?;
}
Ok(())
}
}
}
}
impl fmt::Display for FormatError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FormatError::Unknown(format) => {
write!(f, "Unknown format: {format}")
}
FormatError::DetectionFailed(path) => {
write!(f, "Failed to detect format for: {path}")
}
FormatError::UnsupportedFeature(feature) => {
write!(f, "Unsupported feature: {feature}")
}
FormatError::SchemaMismatch { expected, actual } => {
write!(f, "Schema mismatch: expected {expected}, got {actual}")
}
FormatError::InvalidOption(option) => {
write!(f, "Invalid format option: {option}")
}
}
}
}
impl fmt::Display for FilterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FilterError::Parse(msg) => write!(f, "Parse error: {msg}"),
FilterError::Compile(msg) => write!(f, "Compilation error: {msg}"),
FilterError::Runtime(msg) => write!(f, "Runtime error: {msg}"),
FilterError::Undefined(name) => write!(f, "Undefined: {name}"),
FilterError::TypeMismatch { expected, actual } => {
write!(f, "Type mismatch: expected {expected}, got {actual}")
}
FilterError::ArgumentCount { expected, actual } => {
write!(f, "Wrong argument count: expected {expected}, got {actual}")
}
}
}
}
impl fmt::Display for TypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeError::InvalidConversion { from, to } => {
write!(f, "Cannot convert from {from} to {to}")
}
TypeError::UnsupportedOperation { operation, typ } => {
write!(f, "Operation '{operation}' not supported for type {typ}")
}
TypeError::FieldNotFound { field, typ } => {
write!(f, "Field '{field}' not found in {typ}")
}
TypeError::OutOfRange(msg) => write!(f, "Value out of range: {msg}"),
TypeError::UnexpectedNull(context) => {
write!(f, "Unexpected null value in: {context}")
}
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Error::Io(e) => Some(e),
Error::Polars(e) => Some(e),
Error::Json(e) => Some(e),
_ => None,
}
}
}
impl StdError for FormatError {}
impl StdError for FilterError {}
impl StdError for TypeError {}
impl From<io::Error> for Error {
fn from(e: io::Error) -> Self {
Error::Io(e)
}
}
impl From<polars::error::PolarsError> for Error {
fn from(e: polars::error::PolarsError) -> Self {
Error::Polars(e)
}
}
impl From<serde_json::Error> for Error {
fn from(e: serde_json::Error) -> Self {
Error::Json(e)
}
}
impl From<FormatError> for Error {
fn from(e: FormatError) -> Self {
Error::Format(e)
}
}
impl From<FilterError> for Error {
fn from(e: FilterError) -> Self {
Error::Filter(e)
}
}
impl From<TypeError> for Error {
fn from(e: TypeError) -> Self {
Error::Type(e)
}
}
impl From<anyhow::Error> for Error {
fn from(e: anyhow::Error) -> Self {
Error::Operation(Cow::Owned(e.to_string()))
}
}
#[cfg(feature = "io")]
impl From<dsq_io::Error> for Error {
fn from(e: dsq_io::Error) -> Self {
Error::Operation(Cow::Owned(e.to_string()))
}
}
impl From<dsq_formats::Error> for Error {
fn from(e: dsq_formats::Error) -> Self {
Error::Operation(Cow::Owned(e.to_string()))
}
}
impl Error {
pub fn operation(msg: impl Into<Cow<'static, str>>) -> Self {
Error::Operation(msg.into())
}
pub fn config(msg: impl Into<String>) -> Self {
Error::Config(msg.into())
}
pub fn from_operation_error(msg: impl Into<Cow<'static, str>>) -> Self {
Error::Operation(msg.into())
}
pub fn from_config_error(msg: impl Into<String>) -> Self {
Error::Config(msg.into())
}
#[must_use]
pub fn combine(errors: Vec<Error>) -> Self {
match errors.len() {
0 => Error::operation(Cow::Borrowed("No errors")),
1 => errors.into_iter().next().unwrap(),
_ => Error::Multiple(errors),
}
}
}
impl FormatError {
pub fn unknown(format: impl Into<String>) -> Self {
FormatError::Unknown(format.into())
}
pub fn detection_failed(path: impl Into<String>) -> Self {
FormatError::DetectionFailed(path.into())
}
}
impl FilterError {
pub fn parse(msg: impl Into<String>) -> Self {
FilterError::Parse(msg.into())
}
pub fn compile(msg: impl Into<String>) -> Self {
FilterError::Compile(msg.into())
}
pub fn runtime(msg: impl Into<String>) -> Self {
FilterError::Runtime(msg.into())
}
}
impl TypeError {
pub fn invalid_conversion(from: impl Into<String>, to: impl Into<String>) -> Self {
TypeError::InvalidConversion {
from: from.into(),
to: to.into(),
}
}
pub fn unsupported_operation(operation: impl Into<String>, typ: impl Into<String>) -> Self {
TypeError::UnsupportedOperation {
operation: operation.into(),
typ: typ.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = Error::operation(Cow::Borrowed("test operation failed"));
assert_eq!(err.to_string(), "Operation error: test operation failed");
let err = FormatError::unknown("xyz");
assert_eq!(err.to_string(), "Unknown format: xyz");
let err = FilterError::parse("unexpected token");
assert_eq!(err.to_string(), "Parse error: unexpected token");
let err = TypeError::invalid_conversion("string", "number");
assert_eq!(err.to_string(), "Cannot convert from string to number");
}
#[test]
fn test_error_conversion() {
let io_err = io::Error::new(io::ErrorKind::NotFound, "file not found");
let err: Error = io_err.into();
assert!(matches!(err, Error::Io(_)));
let format_err = FormatError::unknown("test");
let err: Error = format_err.into();
assert!(matches!(err, Error::Format(_)));
}
#[test]
fn test_multiple_errors() {
let errors = vec![
Error::operation(Cow::Borrowed("error 1")),
Error::operation(Cow::Borrowed("error 2")),
];
let combined = Error::combine(errors);
assert!(matches!(combined, Error::Multiple(_)));
}
}