1use std::time::Duration;
2use thiserror::Error;
3
4#[macro_export]
8macro_rules! define_exchange_error {
9 ($ErrorType:ident { $($unique_variant:tt)* }) => {
10 #[derive(Debug, thiserror::Error)]
11 pub enum $ErrorType {
12 #[error("http error: {0}")]
13 Http(#[from] reqwest::Error),
14 #[error("api error: {0}")]
15 Api(String),
16 #[error("rate limited")]
17 RateLimited,
18 #[error("authentication required")]
19 AuthRequired,
20 #[error("market not found: {0}")]
21 MarketNotFound(String),
22 $($unique_variant)*
23 }
24 };
25}
26
27#[derive(Debug, Error)]
28pub enum OpenPxError {
29 #[error("network error: {0}")]
30 Network(#[from] NetworkError),
31
32 #[error("exchange error: {0}")]
33 Exchange(#[from] ExchangeError),
34
35 #[error("websocket error: {0}")]
36 WebSocket(#[from] WebSocketError),
37
38 #[error("signing error: {0}")]
39 Signing(#[from] SigningError),
40
41 #[error("rate limit exceeded")]
42 RateLimitExceeded,
43
44 #[error("serialization error: {0}")]
45 Serialization(#[from] serde_json::Error),
46
47 #[error("configuration error: {0}")]
48 Config(String),
49
50 #[error("invalid input: {0}")]
51 InvalidInput(String),
52
53 #[error("{0}")]
54 Other(String),
55}
56
57impl OpenPxError {
58 pub fn is_retryable(&self) -> bool {
60 match self {
61 Self::Network(_) => true,
62 Self::RateLimitExceeded => true,
63 Self::Exchange(e) => e.is_retryable(),
64 Self::WebSocket(e) => e.is_retryable(),
65 Self::Signing(_) | Self::Config(_) | Self::InvalidInput(_) => false,
66 Self::Serialization(_) => false,
67 Self::Other(_) => false,
68 }
69 }
70
71 pub fn retry_after(&self) -> Option<Duration> {
73 match self {
74 Self::RateLimitExceeded => Some(Duration::from_secs(1)),
75 Self::Network(NetworkError::Timeout(_)) => Some(Duration::from_millis(500)),
76 Self::Network(_) => Some(Duration::from_millis(100)),
77 Self::WebSocket(e) => e.retry_after(),
78 _ => None,
79 }
80 }
81}
82
83#[derive(Debug, Error)]
84pub enum NetworkError {
85 #[error("http request failed: {0}")]
86 Http(String),
87
88 #[error("timeout after {0}ms")]
89 Timeout(u64),
90
91 #[error("connection failed: {0}")]
92 Connection(String),
93}
94
95#[derive(Debug, Error)]
96pub enum ExchangeError {
97 #[error("market not found: {0}")]
98 MarketNotFound(String),
99
100 #[error("invalid order: {0}")]
101 InvalidOrder(String),
102
103 #[error("order rejected: {0}")]
104 OrderRejected(String),
105
106 #[error("insufficient funds: {0}")]
107 InsufficientFunds(String),
108
109 #[error("authentication failed: {0}")]
110 Authentication(String),
111
112 #[error("not supported: {0}")]
113 NotSupported(String),
114
115 #[error("api error: {0}")]
116 Api(String),
117}
118
119#[derive(Debug, Clone, Error)]
120pub enum WebSocketError {
121 #[error("connection error: {0}")]
122 Connection(String),
123
124 #[error("connection closed")]
125 Closed,
126
127 #[error("protocol error: {0}")]
128 Protocol(String),
129
130 #[error("subscription failed: {0}")]
131 Subscription(String),
132}
133
134impl WebSocketError {
135 pub fn is_retryable(&self) -> bool {
136 matches!(self, Self::Connection(_) | Self::Closed)
137 }
138
139 pub fn retry_after(&self) -> Option<Duration> {
140 match self {
141 Self::Connection(_) | Self::Closed => Some(Duration::from_millis(500)),
142 _ => None,
143 }
144 }
145}
146
147impl ExchangeError {
148 pub fn is_retryable(&self) -> bool {
149 matches!(self, Self::Api(_))
150 }
151}
152
153#[derive(Debug, Error)]
154pub enum SigningError {
155 #[error("invalid private key")]
156 InvalidKey,
157
158 #[error("signing failed: {0}")]
159 SigningFailed(String),
160
161 #[error("unsupported operation: {0}")]
162 Unsupported(String),
163}