Skip to main content

tradingview/
error.rs

1use serde::{Deserialize, Serialize};
2use thiserror::Error;
3use ustr::Ustr;
4
5/// The crate-wide error type.
6///
7/// Wraps all failure modes: network errors, JSON deserialization failures,
8/// WebSocket issues, auth errors, and TradingView-specific protocol errors.
9///
10/// # Conversion
11///
12/// Common external errors (`reqwest::Error`, `serde_json::Error`, `chrono::ParseError`,
13/// etc.) convert automatically via `From` impls.
14#[derive(Debug, Clone, Error, Copy, Serialize, Deserialize)]
15pub enum Error {
16    #[error("Generic: {0}")]
17    Internal(Ustr),
18
19    #[error("Request failed: {0}")]
20    Request(Ustr),
21
22    #[error("JSON parsing failed: {0}")]
23    JsonParse(Ustr),
24
25    #[error("Type conversion failed: {0}")]
26    TypeConversion(Ustr),
27
28    #[error("Invalid header value: {0}")]
29    HeaderValue(Ustr),
30
31    #[error("Login failed: {source}")]
32    Login {
33        #[source]
34        source: LoginError,
35    },
36
37    #[error("Regex error: {0}")]
38    Regex(Ustr),
39
40    #[error("WebSocket connection failed: {0}")]
41    WebSocket(Ustr),
42
43    #[error("No chart token found")]
44    NoChartTokenFound,
45
46    #[error("No scan data found")]
47    NoScanDataFound,
48
49    #[error("Symbols are not in the same exchange")]
50    SymbolsNotInSameExchange,
51
52    #[error("Exchange not specified")]
53    ExchangeNotSpecified,
54
55    #[error("Invalid exchange")]
56    InvalidExchange,
57
58    #[error("Symbols not specified")]
59    SymbolsNotSpecified,
60
61    #[error("No search data found")]
62    NoSearchDataFound,
63
64    #[error("Indicator not found or unsupported: {0}")]
65    IndicatorDataNotFound(Ustr),
66
67    #[error("Task join failed: {0}")]
68    TokioJoin(Ustr),
69
70    #[error("URL parsing failed: {0}")]
71    UrlParse(Ustr),
72
73    #[error("Date/time parsing failed: {0}")]
74    ChronoParse(Ustr),
75
76    #[error("Date/time out of range: {0}")]
77    ChronoOutOfRange(Ustr),
78
79    #[error("Timeout: {0}")]
80    Timeout(Ustr),
81
82    #[error("I/O error: {0}")]
83    Io(Ustr),
84
85    #[error("TradingView error: {source}")]
86    TradingView {
87        #[source]
88        source: TradingViewError,
89    },
90}
91
92// Implement From traits for common error types
93impl From<reqwest::Error> for Error {
94    fn from(err: reqwest::Error) -> Self {
95        Error::Request(err.to_string().into())
96    }
97}
98
99impl From<serde_json::Error> for Error {
100    fn from(err: serde_json::Error) -> Self {
101        Error::JsonParse(err.to_string().into())
102    }
103}
104
105impl From<std::num::ParseIntError> for Error {
106    fn from(err: std::num::ParseIntError) -> Self {
107        Error::TypeConversion(err.to_string().into())
108    }
109}
110
111impl From<reqwest::header::InvalidHeaderValue> for Error {
112    fn from(err: reqwest::header::InvalidHeaderValue) -> Self {
113        Error::HeaderValue(err.to_string().into())
114    }
115}
116
117impl From<LoginError> for Error {
118    fn from(err: LoginError) -> Self {
119        Error::Login { source: err }
120    }
121}
122
123impl From<regex::Error> for Error {
124    fn from(err: regex::Error) -> Self {
125        Error::Regex(err.to_string().into())
126    }
127}
128
129impl From<tokio_tungstenite::tungstenite::Error> for Error {
130    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
131        Error::WebSocket(err.to_string().into())
132    }
133}
134
135impl From<tokio::task::JoinError> for Error {
136    fn from(err: tokio::task::JoinError) -> Self {
137        Error::TokioJoin(err.to_string().into())
138    }
139}
140
141impl From<url::ParseError> for Error {
142    fn from(err: url::ParseError) -> Self {
143        Error::UrlParse(err.to_string().into())
144    }
145}
146
147impl From<chrono::ParseError> for Error {
148    fn from(err: chrono::ParseError) -> Self {
149        Error::ChronoParse(err.to_string().into())
150    }
151}
152
153impl From<chrono::OutOfRangeError> for Error {
154    fn from(err: chrono::OutOfRangeError) -> Self {
155        Error::ChronoOutOfRange(err.to_string().into())
156    }
157}
158
159impl From<std::io::Error> for Error {
160    fn from(err: std::io::Error) -> Self {
161        Error::Io(err.to_string().into())
162    }
163}
164
165impl From<TradingViewError> for Error {
166    fn from(err: TradingViewError) -> Self {
167        Error::TradingView { source: err }
168    }
169}
170
171impl From<String> for Error {
172    fn from(err: String) -> Self {
173        Error::Internal(err.into())
174    }
175}
176
177impl From<&str> for Error {
178    fn from(err: &str) -> Self {
179        Error::Internal(Ustr::from(err))
180    }
181}
182
183impl From<Ustr> for Error {
184    fn from(err: Ustr) -> Self {
185        Error::Internal(err)
186    }
187}
188
189/// Errors returned by TradingView's data server (WebSocket protocol layer).
190///
191/// These correspond to TradingView's own error taxonomy — distinct from
192/// transport-level failures in [`enum@Error`].
193#[derive(Debug, Clone, Error, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
194pub enum TradingViewError {
195    #[error("Series error")]
196    SeriesError,
197    #[error("Symbol error")]
198    SymbolError,
199    #[error("Critical error")]
200    CriticalError,
201    #[error("Study error")]
202    StudyError,
203    #[error("Protocol error")]
204    ProtocolError,
205    #[error("Quote data status error: {0}")]
206    QuoteDataStatusError(Ustr),
207    #[error("Replay error")]
208    ReplayError,
209    #[error("Configuration error: missing exchange")]
210    MissingExchange,
211    #[error("Configuration error: missing symbol")]
212    MissingSymbol,
213    #[error("Invalid session ID or signature")]
214    InvalidSessionId,
215}
216
217/// Errors that can occur during user authentication (login flow).
218#[derive(Debug, Clone, Error, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)]
219pub enum LoginError {
220    #[error("Username or password is empty")]
221    EmptyCredentials,
222    #[error("Username or password is invalid")]
223    InvalidCredentials,
224    #[error("OTP secret is empty")]
225    OTPSecretNotFound,
226    #[error("OTP secret is invalid")]
227    InvalidOTPSecret,
228    #[error("Wrong or expired session ID/signature")]
229    InvalidSession,
230    #[error("Session ID/signature is empty")]
231    SessionNotFound,
232    #[error("Cannot parse user ID")]
233    ParseIDError,
234    #[error("Cannot parse username")]
235    ParseUsernameError,
236    #[error("Cannot parse session hash")]
237    ParseSessionHashError,
238    #[error("Cannot parse private channel")]
239    ParsePrivateChannelError,
240    #[error("Cannot parse auth token")]
241    ParseAuthTokenError,
242    #[error("Missing auth token")]
243    MissingAuthToken,
244}