ytmapi_rs/
error.rs

1//! Module to contain code related to errors that could be produced by the API.
2use core::fmt::{Debug, Display};
3pub use json_crawler::CrawlerError as JsonError;
4use std::hash::{Hash, Hasher};
5use std::io;
6use std::time::SystemTimeError;
7
8/// Alias for a Result with the error type ytmapi-rs::Error.
9pub type Result<T> = core::result::Result<T, Error>;
10
11/// This type represents all errors this API could produce.
12pub struct Error {
13    // This is boxed to avoid passing around very large errors - in the case of an Api error we
14    // want to provide the source file to the caller.
15    inner: Box<ErrorKind>,
16}
17
18/// The kind of the error.
19/// This list may grow over time, and it's not recommended to exhaustively match
20/// on it.
21#[non_exhaustive]
22pub enum ErrorKind {
23    /// Error parsing Json response from InnerTube.
24    JsonParsing(JsonError),
25    /// Error from HTTP client.
26    Web {
27        message: String,
28    },
29    /// General io error.
30    // TODO: improve
31    Io(io::Error),
32    /// Received a response from InnerTube that was not in the expected (JSON)
33    /// format.
34    InvalidResponse {
35        response: String,
36    },
37    /// InnerTube credential header not in expected format.
38    Header,
39    UnableToSerializeGoogleOAuthToken {
40        response: String,
41        err: serde_json::Error,
42    },
43    /// ytcfg not in expected format.
44    UnableToParseYtCfg {
45        ytcfg: String,
46    },
47    /// ytcfg didn't include visitor data.
48    NoVisitorData,
49    /// InnerTube rejected the User Agent we are using.
50    InvalidUserAgent(String),
51    /// OAuthToken has expired.
52    /// Returns a hash of the expired token generated using the default hasher.
53    OAuthTokenExpired {
54        token_hash: u64,
55    },
56    // This is a u64 not a usize as that is what serde_json will deserialize to.
57    // TODO: Could use a library to handle these.
58    /// Recieved an error code in the Json reply from InnerTube.
59    OtherErrorCodeInResponse {
60        code: u64,
61        message: String,
62    },
63    /// Innertube returned a STATUS_FAILED for the query.
64    ApiStatusFailed,
65    /// Unable to obtain system time for the query to Innertube.
66    SystemTimeError {
67        message: String,
68    },
69    /// Tried to upload a song with an invalid upload filename.
70    InvalidUploadFilename {
71        filename: String,
72        message: String,
73    },
74    MissingUploadUrl,
75}
76/// The type we were attempting to pass from the Json.
77#[derive(Debug, Clone)]
78pub enum ParseTarget {
79    Array,
80    Other(String),
81}
82impl Error {
83    /// Extract the inner kind from the error for pattern matching.
84    pub fn into_kind(self) -> ErrorKind {
85        *self.inner
86    }
87    pub(crate) fn invalid_user_agent<S: Into<String>>(user_agent: S) -> Self {
88        Self {
89            inner: Box::new(ErrorKind::InvalidUserAgent(user_agent.into())),
90        }
91    }
92    pub(crate) fn oauth_token_expired(token: &crate::auth::OAuthToken) -> Self {
93        let mut h = std::hash::DefaultHasher::new();
94        token.hash(&mut h);
95        let token_hash = h.finish();
96        Self {
97            inner: Box::new(ErrorKind::OAuthTokenExpired { token_hash }),
98        }
99    }
100    pub(crate) fn header() -> Self {
101        Self {
102            inner: Box::new(ErrorKind::Header),
103        }
104    }
105    pub(crate) fn ytcfg(ytcfg: impl Into<String>) -> Self {
106        Self {
107            inner: Box::new(ErrorKind::UnableToParseYtCfg {
108                ytcfg: ytcfg.into(),
109            }),
110        }
111    }
112    pub(crate) fn no_visitor_data() -> Self {
113        Self {
114            inner: Box::new(ErrorKind::NoVisitorData),
115        }
116    }
117    pub(crate) fn response<S: Into<String>>(response: S) -> Self {
118        let response = response.into();
119        Self {
120            inner: Box::new(ErrorKind::InvalidResponse { response }),
121        }
122    }
123    pub(crate) fn unable_to_serialize_oauth<S: Into<String>>(
124        response: S,
125        err: serde_json::Error,
126    ) -> Self {
127        let response = response.into();
128        Self {
129            inner: Box::new(ErrorKind::UnableToSerializeGoogleOAuthToken { response, err }),
130        }
131    }
132    pub(crate) fn other_code(code: u64, message: String) -> Self {
133        Self {
134            inner: Box::new(ErrorKind::OtherErrorCodeInResponse { code, message }),
135        }
136    }
137    pub(crate) fn status_failed() -> Self {
138        Self {
139            inner: Box::new(ErrorKind::ApiStatusFailed),
140        }
141    }
142    pub(crate) fn invalid_upload_filename(filename: String, message: String) -> Self {
143        Self {
144            inner: Box::new(ErrorKind::InvalidUploadFilename { filename, message }),
145        }
146    }
147    pub(crate) fn missing_upload_url() -> Self {
148        Self {
149            inner: Box::new(ErrorKind::MissingUploadUrl),
150        }
151    }
152    pub(crate) fn web(message: impl Into<String>) -> Self {
153        Self {
154            inner: Box::new(ErrorKind::Web {
155                message: message.into(),
156            }),
157        }
158    }
159}
160
161impl std::error::Error for Error {}
162impl Display for ErrorKind {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            ErrorKind::Web { message } => write!(f, "Web error <{message}> received."),
166            ErrorKind::Io(e) => write!(f, "IO error {e} recieved."),
167            ErrorKind::Header => write!(f, "Error parsing header."),
168            ErrorKind::InvalidResponse { response } => {
169                write!(
170                    f,
171                    "Response is invalid json - unable to deserialize. <{response}>"
172                )
173            }
174            ErrorKind::OtherErrorCodeInResponse { code, message } => {
175                write!(
176                    f,
177                    "Http error code {code} recieved in response. Message: <{message}>."
178                )
179            }
180            ErrorKind::ApiStatusFailed => write!(f, "Api returned STATUS_FAILED for the query"),
181            ErrorKind::OAuthTokenExpired { token_hash: _ } => write!(f, "OAuth token has expired"),
182            ErrorKind::InvalidUserAgent(u) => write!(f, "InnerTube rejected User Agent {u}"),
183            ErrorKind::UnableToSerializeGoogleOAuthToken { response, err } => write!(
184                f,
185                "Unable to serialize Google auth token {response}, received error {err}"
186            ),
187            ErrorKind::SystemTimeError { message } => write!(
188                f,
189                "Error obtaining system time to use in API query. <{message}>"
190            ),
191            ErrorKind::JsonParsing(e) => write!(f, "{e}"),
192            ErrorKind::UnableToParseYtCfg { ytcfg } => write!(
193                f,
194                "Unable to parse ytcfg - expected the function to exist and contain json. Received: {ytcfg}"
195            ),
196            ErrorKind::NoVisitorData => write!(f, "ytcfg didn't include VISITOR_DATA"),
197            ErrorKind::InvalidUploadFilename {
198                filename,
199                message: msg,
200            } => write!(
201                f,
202                "Invalid upload filename {filename}. Error message: {msg}"
203            ),
204            ErrorKind::MissingUploadUrl => {
205                write!(f, "expected an x-goog-upload-url but didn't get one")
206            }
207        }
208    }
209}
210// As this is displayed when unwrapping, we don't want to end up including the
211// entire format of this struct (potentially including entire source json file).
212impl Debug for Error {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        // TODO: Improve implementation
215        Display::fmt(&*self.inner, f)
216    }
217}
218impl Display for Error {
219    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
220        Display::fmt(&*self.inner, f)
221    }
222}
223impl From<reqwest::Error> for Error {
224    fn from(err: reqwest::Error) -> Self {
225        let message = err.to_string();
226        Self {
227            inner: Box::new(ErrorKind::Web { message }),
228        }
229    }
230}
231impl From<io::Error> for Error {
232    fn from(err: io::Error) -> Self {
233        Self {
234            inner: Box::new(ErrorKind::Io(err)),
235        }
236    }
237}
238impl From<SystemTimeError> for Error {
239    fn from(err: SystemTimeError) -> Self {
240        let message = err.to_string();
241        Self {
242            inner: Box::new(ErrorKind::SystemTimeError { message }),
243        }
244    }
245}
246impl From<ErrorKind> for Error {
247    fn from(value: ErrorKind) -> Self {
248        Self {
249            inner: Box::new(value),
250        }
251    }
252}
253impl From<JsonError> for Error {
254    fn from(value: JsonError) -> Self {
255        let e = ErrorKind::JsonParsing(value);
256        Self { inner: Box::new(e) }
257    }
258}