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