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("exchange {exchange} does not support candle interval: {interval}")]
76 UnsupportedInterval {
77 exchange: ExchangeId,
78 interval: CandleInterval,
79 },
80
81 #[error(
82 "\
83 InvalidSequence: first_update_id {first_update_id} does not follow on from the \
84 prev_last_update_id {prev_last_update_id} \
85 "
86 )]
87 InvalidSequence {
88 prev_last_update_id: u64,
89 first_update_id: u64,
90 },
91}
92
93impl DataError {
94 #[allow(clippy::match_like_matches_macro)]
98 pub fn is_terminal(&self) -> bool {
99 match self {
100 DataError::InvalidSequence { .. } => true,
101 _ => false,
102 }
103 }
104}
105
106impl From<SocketError> for DataError {
107 fn from(value: SocketError) -> Self {
108 Self::Socket(value.to_string())
109 }
110}
111
112#[cfg(feature = "lse")]
113impl From<crate::exchange::lse::error::LseError> for DataError {
114 fn from(value: crate::exchange::lse::error::LseError) -> Self {
115 Self::Lse {
116 kind: value.kind(),
117 message: value.to_string(),
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn test_data_error_is_terminal() {
128 struct TestCase {
129 input: DataError,
130 expected: bool,
131 }
132
133 let tests = vec![
134 TestCase {
135 input: DataError::InvalidSequence {
137 prev_last_update_id: 0,
138 first_update_id: 0,
139 },
140 expected: true,
141 },
142 TestCase {
143 input: DataError::from(SocketError::Sink),
145 expected: false,
146 },
147 TestCase {
148 input: DataError::UnsupportedInterval {
151 exchange: ExchangeId::HyperliquidPerp,
152 interval: CandleInterval::Sec1,
153 },
154 expected: false,
155 },
156 ];
157
158 for (index, test) in tests.into_iter().enumerate() {
159 let actual = test.input.is_terminal();
160 assert_eq!(actual, test.expected, "TC{} failed", index);
161 }
162 }
163}