1use hyper::{
2 body::Bytes, header::InvalidHeaderValue, http::Error as HttpError, Error as HyperError,
3 StatusCode,
4};
5use serde::Deserialize;
6use serde_json::Error as SerdeError;
7use std::fmt;
8
9#[cfg(feature = "local_oauth")]
10#[cfg_attr(docsrs, doc(cfg(feature = "local_oauth")))]
11#[derive(Debug, thiserror::Error)]
12pub enum OAuthError {
13 #[error("failed to accept request")]
14 Accept(#[source] tokio::io::Error),
15 #[error("failed to create tcp listener")]
16 Listener(#[source] tokio::io::Error),
17 #[error("missing code in request")]
18 NoCode { data: Vec<u8> },
19 #[error("failed to read data")]
20 Read(#[source] tokio::io::Error),
21 #[error("redirect uri must contain localhost and a port number")]
22 Url,
23 #[error("failed to write data")]
24 Write(#[source] tokio::io::Error),
25}
26
27#[derive(Debug, Deserialize, thiserror::Error)]
29pub struct ApiError {
30 pub error: Option<String>,
32}
33
34impl fmt::Display for ApiError {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 match self.error {
37 Some(ref msg) => f.write_str(msg),
38 None => f.write_str("empty error message"),
39 }
40 }
41}
42
43#[derive(Debug, thiserror::Error)]
45#[non_exhaustive]
46pub enum OsuError {
47 #[error("failed to create request body")]
49 BodyError {
50 #[from]
51 source: HttpError,
52 },
53 #[error("failed to build osu client, no client id was provided")]
55 BuilderMissingId,
56 #[error("failed to build osu client, no client secret was provided")]
58 BuilderMissingSecret,
59 #[error("failed to chunk the response")]
61 ChunkingResponse {
62 #[source]
63 source: HyperError,
64 },
65 #[error("no usable cipher suites in crypto provider")]
67 ConnectorRoots {
68 #[source]
69 source: std::io::Error,
70 },
71 #[error("failed to parse token for authorization header")]
73 CreatingTokenHeader {
74 #[from]
75 source: InvalidHeaderValue,
76 },
77 #[error("the osu!api returned a 404 implying a missing score, incorrect name, id, etc")]
79 NotFound,
80 #[error(
82 "The previous osu!api token expired and the client \
83 has not yet succeeded in acquiring a new one. \
84 Can not send requests until a new token has been acquired. \
85 This should only occur during an extended downtime of the osu!api."
86 )]
87 NoToken,
88 #[cfg(feature = "local_oauth")]
89 #[cfg_attr(docsrs, doc(cfg(feature = "local_oauth")))]
90 #[error("failed to perform oauth")]
92 OAuth {
93 #[from]
94 source: OAuthError,
95 },
96 #[cfg(feature = "replay")]
97 #[cfg_attr(docsrs, doc(cfg(feature = "replay")))]
98 #[error("osu-db error")]
100 OsuDbError {
101 #[from]
102 source: osu_db::Error,
103 },
104 #[error("failed to deserialize response: {:?}", .bytes)]
106 Parsing {
107 bytes: Bytes,
108 #[source]
109 source: SerdeError,
110 },
111 #[error("failed to parse value")]
113 ParsingValue {
114 #[from]
115 source: ParsingError,
116 },
117 #[error("failed to send request")]
119 Request {
120 #[source]
121 source: hyper_util::client::legacy::Error,
122 },
123 #[error("osu!api did not respond in time")]
125 RequestTimeout,
126 #[error("response error, status {}", .status)]
128 Response {
129 bytes: Bytes,
130 #[source]
131 source: ApiError,
132 status: StatusCode,
133 },
134 #[error("osu!api may be temporarily unavailable (received 503)")]
136 ServiceUnavailable { body: hyper::body::Incoming },
137 #[error("the endpoint is not available for the client's authorization level")]
139 UnavailableEndpoint,
140 #[error("failed to update osu!api token")]
142 UpdateToken {
143 #[source]
144 source: Box<OsuError>,
145 },
146 #[error("failed to parse URL of a request; url: `{}`", .url)]
148 Url {
149 #[source]
150 source: url::ParseError,
151 url: String,
153 },
154}
155
156impl OsuError {
157 pub(crate) fn invalid_mods<E: serde::de::Error>(
158 mods: &serde_json::value::RawValue,
159 err: &SerdeError,
160 ) -> E {
161 E::custom(format!("invalid mods `{mods}`: {err}"))
162 }
163}
164
165#[derive(Debug, thiserror::Error)]
167pub enum ParsingError {
168 #[error("failed to parse `{}` into an Acronym", .0)]
170 Acronym(Box<str>),
171 #[error("failed to parse {} into Genre", .0)]
173 Genre(u8),
174 #[error("failed to parse `{}` into Grade", .0)]
176 Grade(String), #[error("failed to parse {} into Language", .0)]
179 Language(u8),
180 #[error("failed to parse {} into MatchTeam", .0)]
182 MatchTeam(u8),
183 #[error("failed to parse {} into RankStatus", .0)]
185 RankStatus(i8),
186 #[error("failed to parse {} into ScoringType", .0)]
188 ScoringType(u8),
189 #[error("failed to parse {} into TeamType", .0)]
191 TeamType(u8),
192}