ccxt_core/ws_client/
error.rs1use crate::error::Error;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum WsErrorKind {
12 Transient,
14 Permanent,
16}
17
18impl WsErrorKind {
19 #[inline]
21 #[must_use]
22 pub fn is_transient(self) -> bool {
23 matches!(self, Self::Transient)
24 }
25
26 #[inline]
28 #[must_use]
29 pub fn is_permanent(self) -> bool {
30 matches!(self, Self::Permanent)
31 }
32}
33
34impl std::fmt::Display for WsErrorKind {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::Transient => write!(f, "Transient"),
38 Self::Permanent => write!(f, "Permanent"),
39 }
40 }
41}
42
43#[derive(Debug)]
45pub struct WsError {
46 kind: WsErrorKind,
47 message: String,
48 source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
49}
50
51impl WsError {
52 pub fn new(kind: WsErrorKind, message: impl Into<String>) -> Self {
54 Self {
55 kind,
56 message: message.into(),
57 source: None,
58 }
59 }
60
61 pub fn with_source<E>(kind: WsErrorKind, message: impl Into<String>, source: E) -> Self
63 where
64 E: std::error::Error + Send + Sync + 'static,
65 {
66 Self {
67 kind,
68 message: message.into(),
69 source: Some(Box::new(source)),
70 }
71 }
72
73 pub fn transient(message: impl Into<String>) -> Self {
75 Self::new(WsErrorKind::Transient, message)
76 }
77
78 pub fn transient_with_source<E>(message: impl Into<String>, source: E) -> Self
80 where
81 E: std::error::Error + Send + Sync + 'static,
82 {
83 Self::with_source(WsErrorKind::Transient, message, source)
84 }
85
86 pub fn permanent(message: impl Into<String>) -> Self {
88 Self::new(WsErrorKind::Permanent, message)
89 }
90
91 pub fn permanent_with_source<E>(message: impl Into<String>, source: E) -> Self
93 where
94 E: std::error::Error + Send + Sync + 'static,
95 {
96 Self::with_source(WsErrorKind::Permanent, message, source)
97 }
98
99 #[inline]
101 #[must_use]
102 pub fn kind(&self) -> WsErrorKind {
103 self.kind
104 }
105
106 #[inline]
108 #[must_use]
109 pub fn message(&self) -> &str {
110 &self.message
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn is_transient(&self) -> bool {
117 self.kind.is_transient()
118 }
119
120 #[inline]
122 #[must_use]
123 pub fn is_permanent(&self) -> bool {
124 self.kind.is_permanent()
125 }
126
127 #[must_use]
129 pub fn source(&self) -> Option<&(dyn std::error::Error + Send + Sync + 'static)> {
130 self.source.as_deref()
131 }
132
133 pub fn from_tungstenite(err: &tokio_tungstenite::tungstenite::Error) -> Self {
135 use tokio_tungstenite::tungstenite::Error as TungError;
136
137 match err {
138 TungError::Io(io_err) => {
139 let message = format!("IO error: {io_err}");
140 Self::transient_with_source(
141 message,
142 std::io::Error::new(io_err.kind(), io_err.to_string()),
143 )
144 }
145 TungError::ConnectionClosed => Self::transient("Connection closed by server"),
146 TungError::AlreadyClosed => Self::transient("Connection already closed"),
147 TungError::Protocol(protocol_err) => {
148 Self::permanent(format!("Protocol error: {protocol_err}"))
149 }
150 TungError::Utf8(_) => Self::permanent("UTF-8 encoding error in WebSocket message"),
151 TungError::Http(response) => {
152 let status = response.status();
153 let status_code = status.as_u16();
154 if status_code == 401 || status_code == 403 {
155 Self::permanent(format!("Authentication error: HTTP {status}"))
156 } else if status.is_server_error() {
157 Self::transient(format!("Server error: HTTP {status}"))
158 } else {
159 Self::permanent(format!("HTTP error: {status}"))
160 }
161 }
162 TungError::HttpFormat(http_err) => {
163 Self::permanent(format!("HTTP format error: {http_err}"))
164 }
165 TungError::Url(url_err) => Self::permanent(format!("Invalid URL: {url_err}")),
166 TungError::Tls(tls_err) => Self::transient(format!("TLS error: {tls_err}")),
167 TungError::Capacity(capacity_err) => {
168 Self::permanent(format!("Capacity error: {capacity_err}"))
169 }
170 TungError::WriteBufferFull(msg) => {
171 Self::transient(format!("Write buffer full: {msg:?}"))
172 }
173 TungError::AttackAttempt => Self::permanent("Potential attack detected"),
174 }
175 }
176
177 pub fn from_error(err: &Error) -> Self {
179 if err.as_authentication().is_some() {
180 return Self::permanent(format!("Authentication error: {err}"));
181 }
182 if err.as_cancelled().is_some() {
183 return Self::permanent(format!("Operation cancelled: {err}"));
184 }
185 if err.as_resource_exhausted().is_some() {
186 return Self::permanent(format!("Resource exhausted: {err}"));
187 }
188 Self::transient(format!("Error: {err}"))
189 }
190}
191
192impl std::fmt::Display for WsError {
193 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
194 write!(f, "[{}] {}", self.kind, self.message)
195 }
196}
197
198impl std::error::Error for WsError {
199 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
200 self.source
201 .as_ref()
202 .map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
203 }
204}