1#[cfg(feature = "databento")]
2use crate::exchange::databento::DatabentoErrorKind;
3use crate::subscription::{SubKind, candle::CandleInterval};
4use rustrade_instrument::{exchange::ExchangeId, index::error::IndexError};
5use rustrade_integration::{error::SocketError, subscription::SubscriptionId};
6use serde::{Deserialize, Serialize};
7use thiserror::Error;
8
9#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, Error)]
20#[non_exhaustive]
21pub enum DataError {
22 #[error("failed to index market data Subscriptions: {0}")]
23 Index(#[from] IndexError),
24
25 #[error("failed to initialise reconnecting MarketStream due to empty subscriptions")]
26 SubscriptionsEmpty,
27
28 #[error("unsupported DynamicStreams Subscription SubKind: {0}")]
29 UnsupportedSubKind(SubKind),
30
31 #[error("initial snapshot missing for: {0}")]
32 InitialSnapshotMissing(SubscriptionId),
33
34 #[error("initial snapshot invalid: {0}")]
35 InitialSnapshotInvalid(String),
36
37 #[error("SocketError: {0}")]
38 Socket(String),
39
40 #[cfg(feature = "databento")]
42 #[error("Databento {kind} error ({context}): {message}")]
43 Databento {
44 kind: DatabentoErrorKind,
45 context: String,
46 message: String,
47 },
48
49 #[cfg(feature = "lse")]
63 #[error("London Strategic Edge {kind} error: {message}")]
64 Lse {
65 kind: crate::exchange::lse::error::LseErrorKind,
66 message: String,
67 },
68
69 #[error("unsupported dynamic Subscription for exchange: {exchange}, kind: {sub_kind}")]
70 Unsupported {
71 exchange: ExchangeId,
72 sub_kind: SubKind,
73 },
74
75 #[error(
79 "dynamic Subscription for exchange {exchange} needs a subscriber, and none was supplied \
80 through DynamicSubscribers"
81 )]
82 SubscriberRequired { exchange: ExchangeId },
83
84 #[error(
87 "exchange {exchange} needs the `{feature}` cargo feature, which this build does not enable"
88 )]
89 FeatureDisabled {
90 exchange: ExchangeId,
91 feature: String,
92 },
93
94 #[error("exchange {exchange} does not support candle interval: {interval}")]
95 UnsupportedInterval {
96 exchange: ExchangeId,
97 interval: CandleInterval,
98 },
99
100 #[error(
101 "\
102 InvalidSequence: first_update_id {first_update_id} does not follow on from the \
103 prev_last_update_id {prev_last_update_id} \
104 "
105 )]
106 InvalidSequence {
107 prev_last_update_id: u64,
108 first_update_id: u64,
109 },
110}
111
112impl DataError {
113 #[allow(clippy::match_like_matches_macro)]
117 pub fn is_terminal(&self) -> bool {
118 match self {
119 DataError::InvalidSequence { .. } => true,
120 _ => false,
121 }
122 }
123}
124
125impl From<SocketError> for DataError {
126 fn from(value: SocketError) -> Self {
127 Self::Socket(value.to_string())
128 }
129}
130
131#[cfg(feature = "lse")]
132impl From<crate::exchange::lse::error::LseError> for DataError {
133 fn from(value: crate::exchange::lse::error::LseError) -> Self {
134 Self::Lse {
135 kind: value.kind(),
136 message: value.to_string(),
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_data_error_is_terminal() {
147 struct TestCase {
148 input: DataError,
149 expected: bool,
150 }
151
152 let tests = vec![
153 TestCase {
154 input: DataError::InvalidSequence {
156 prev_last_update_id: 0,
157 first_update_id: 0,
158 },
159 expected: true,
160 },
161 TestCase {
162 input: DataError::from(SocketError::Sink),
164 expected: false,
165 },
166 TestCase {
167 input: DataError::UnsupportedInterval {
170 exchange: ExchangeId::HyperliquidPerp,
171 interval: CandleInterval::Sec1,
172 },
173 expected: false,
174 },
175 ];
176
177 for (index, test) in tests.into_iter().enumerate() {
178 let actual = test.input.is_terminal();
179 assert_eq!(actual, test.expected, "TC{} failed", index);
180 }
181 }
182}