use crate::error::DataError;
use serde::{Deserialize, Serialize};
use std::error::Error as StdError;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub enum DatabentoErrorKind {
Authentication,
RateLimit,
Network,
Decode,
Api,
}
impl fmt::Display for DatabentoErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Authentication => write!(f, "authentication"),
Self::RateLimit => write!(f, "rate limit"),
Self::Network => write!(f, "network"),
Self::Decode => write!(f, "decode"),
Self::Api => write!(f, "API"),
}
}
}
#[derive(Debug)]
pub(crate) struct DatabentoError {
pub(crate) kind: DatabentoErrorKind,
context: &'static str,
source: Box<dyn StdError + Send + Sync + 'static>,
}
impl DatabentoError {
pub(crate) fn new(
context: &'static str,
source: impl StdError + Send + Sync + 'static,
) -> Self {
let kind = classify_error(&source);
Self {
kind,
context,
source: Box::new(source),
}
}
}
impl fmt::Display for DatabentoError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Databento {}: {}", self.context, self.source)
}
}
impl StdError for DatabentoError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(self.source.as_ref())
}
}
impl From<DatabentoError> for DataError {
fn from(err: DatabentoError) -> Self {
DataError::Databento {
kind: err.kind,
context: err.context.to_string(),
message: err.source.to_string(),
}
}
}
fn classify_error(err: &(impl StdError + ?Sized)) -> DatabentoErrorKind {
let mut msg = err.to_string();
msg.make_ascii_lowercase();
if msg.contains("api key")
|| msg.contains("apikey")
|| msg.contains("unauthorized")
|| msg.contains("authentication")
|| msg.contains("invalid key")
|| msg.contains("expired")
{
return DatabentoErrorKind::Authentication;
}
if msg.contains("rate limit") || msg.contains("too many requests") || msg.contains("throttl") {
return DatabentoErrorKind::RateLimit;
}
if msg.contains("connect")
|| msg.contains("timeout")
|| msg.contains("network")
|| msg.contains("dns")
|| msg.contains("socket")
|| msg.contains("unexpected end of file")
{
return DatabentoErrorKind::Network;
}
if msg.contains("decode") || msg.contains("parse") || msg.contains("invalid record") {
return DatabentoErrorKind::Decode;
}
DatabentoErrorKind::Api
}
pub(crate) trait DatabentoResultExt<T> {
fn with_context(self, ctx: &'static str) -> Result<T, DatabentoError>;
}
impl<T, E: StdError + Send + Sync + 'static> DatabentoResultExt<T> for Result<T, E> {
fn with_context(self, ctx: &'static str) -> Result<T, DatabentoError> {
self.map_err(|e| DatabentoError::new(ctx, e))
}
}
pub(crate) fn decode_error(message: impl Into<String>) -> DataError {
DataError::Databento {
kind: DatabentoErrorKind::Decode,
context: "decoding DBN record".to_string(),
message: message.into(),
}
}