rustrade_data/exchange/databento/
error.rs1use crate::error::DataError;
4use serde::{Deserialize, Serialize};
5use std::error::Error as StdError;
6use std::fmt;
7
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
17pub enum DatabentoErrorKind {
18 Authentication,
20 RateLimit,
22 Network,
24 Decode,
26 Api,
28}
29
30impl fmt::Display for DatabentoErrorKind {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Self::Authentication => write!(f, "authentication"),
34 Self::RateLimit => write!(f, "rate limit"),
35 Self::Network => write!(f, "network"),
36 Self::Decode => write!(f, "decode"),
37 Self::Api => write!(f, "API"),
38 }
39 }
40}
41
42#[derive(Debug)]
44pub(crate) struct DatabentoError {
45 pub(crate) kind: DatabentoErrorKind,
46 context: &'static str,
47 source: Box<dyn StdError + Send + Sync + 'static>,
48}
49
50impl DatabentoError {
51 pub(crate) fn new(
52 context: &'static str,
53 source: impl StdError + Send + Sync + 'static,
54 ) -> Self {
55 let kind = classify_error(&source);
56 Self {
57 kind,
58 context,
59 source: Box::new(source),
60 }
61 }
62}
63
64impl fmt::Display for DatabentoError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 write!(f, "Databento {}: {}", self.context, self.source)
67 }
68}
69
70impl StdError for DatabentoError {
71 fn source(&self) -> Option<&(dyn StdError + 'static)> {
72 Some(self.source.as_ref())
73 }
74}
75
76impl From<DatabentoError> for DataError {
77 fn from(err: DatabentoError) -> Self {
78 DataError::Databento {
79 kind: err.kind,
80 context: err.context.to_string(),
81 message: err.source.to_string(),
82 }
83 }
84}
85
86fn classify_error(err: &(impl StdError + ?Sized)) -> DatabentoErrorKind {
88 let mut msg = err.to_string();
89 msg.make_ascii_lowercase();
90
91 if msg.contains("api key")
92 || msg.contains("apikey")
93 || msg.contains("unauthorized")
94 || msg.contains("authentication")
95 || msg.contains("invalid key")
96 || msg.contains("expired")
97 {
98 return DatabentoErrorKind::Authentication;
99 }
100
101 if msg.contains("rate limit") || msg.contains("too many requests") || msg.contains("throttl") {
102 return DatabentoErrorKind::RateLimit;
103 }
104
105 if msg.contains("connect")
106 || msg.contains("timeout")
107 || msg.contains("network")
108 || msg.contains("dns")
109 || msg.contains("socket")
110 || msg.contains("unexpected end of file")
111 {
112 return DatabentoErrorKind::Network;
113 }
114
115 if msg.contains("decode") || msg.contains("parse") || msg.contains("invalid record") {
116 return DatabentoErrorKind::Decode;
117 }
118
119 DatabentoErrorKind::Api
120}
121
122pub(crate) trait DatabentoResultExt<T> {
125 fn with_context(self, ctx: &'static str) -> Result<T, DatabentoError>;
126}
127
128impl<T, E: StdError + Send + Sync + 'static> DatabentoResultExt<T> for Result<T, E> {
129 fn with_context(self, ctx: &'static str) -> Result<T, DatabentoError> {
130 self.map_err(|e| DatabentoError::new(ctx, e))
131 }
132}
133
134pub(crate) fn decode_error(message: impl Into<String>) -> DataError {
136 DataError::Databento {
137 kind: DatabentoErrorKind::Decode,
138 context: "decoding DBN record".to_string(),
139 message: message.into(),
140 }
141}