use hyper::{
body::Bytes, header::InvalidHeaderValue, http::Error as HttpError, Error as HyperError,
StatusCode,
};
use serde::Deserialize;
use serde_json::Error as SerdeError;
use std::fmt;
#[cfg(feature = "local_oauth")]
#[cfg_attr(docsrs, doc(cfg(feature = "local_oauth")))]
#[derive(Debug, thiserror::Error)]
pub enum OAuthError {
#[error("failed to accept request")]
Accept(#[source] tokio::io::Error),
#[error("failed to create tcp listener")]
Listener(#[source] tokio::io::Error),
#[error("missing code in request")]
NoCode { data: Vec<u8> },
#[error("failed to read data")]
Read(#[source] tokio::io::Error),
#[error("redirect uri must contain localhost and a port number")]
Url,
#[error("failed to write data")]
Write(#[source] tokio::io::Error),
}
#[derive(Debug, Deserialize, thiserror::Error)]
pub struct ApiError {
pub error: Option<String>,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.error {
Some(ref msg) => f.write_str(msg),
None => f.write_str("empty error message"),
}
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum OsuError {
#[error("failed to create request body")]
BodyError {
#[from]
source: HttpError,
},
#[error("failed to build osu client, no client id was provided")]
BuilderMissingId,
#[error("failed to build osu client, no client secret was provided")]
BuilderMissingSecret,
#[error("failed to chunk the response")]
ChunkingResponse {
#[source]
source: HyperError,
},
#[error("no usable cipher suites in crypto provider")]
ConnectorRoots {
#[source]
source: std::io::Error,
},
#[error("failed to parse token for authorization header")]
CreatingTokenHeader {
#[from]
source: InvalidHeaderValue,
},
#[error("the osu!api returned a 404 implying a missing score, incorrect name, id, etc")]
NotFound,
#[error(
"The previous osu!api token expired and the client \
has not yet succeeded in acquiring a new one. \
Can not send requests until a new token has been acquired. \
This should only occur during an extended downtime of the osu!api."
)]
NoToken,
#[cfg(feature = "local_oauth")]
#[cfg_attr(docsrs, doc(cfg(feature = "local_oauth")))]
#[error("failed to perform oauth")]
OAuth {
#[from]
source: OAuthError,
},
#[cfg(feature = "replay")]
#[cfg_attr(docsrs, doc(cfg(feature = "replay")))]
#[error("osu-db error")]
OsuDbError {
#[from]
source: osu_db::Error,
},
#[error("failed to deserialize response: {:?}", .bytes)]
Parsing {
bytes: Bytes,
#[source]
source: SerdeError,
},
#[error("failed to parse value")]
ParsingValue {
#[from]
source: ParsingError,
},
#[error("failed to send request")]
Request {
#[source]
source: hyper_util::client::legacy::Error,
},
#[error("osu!api did not respond in time")]
RequestTimeout,
#[error("response error, status {}", .status)]
Response {
bytes: Bytes,
#[source]
source: ApiError,
status: StatusCode,
},
#[error("osu!api may be temporarily unavailable (received 503)")]
ServiceUnavailable { body: hyper::body::Incoming },
#[error("the endpoint is not available for the client's authorization level")]
UnavailableEndpoint,
#[error("failed to update osu!api token")]
UpdateToken {
#[source]
source: Box<OsuError>,
},
#[error("failed to parse URL of a request; url: `{}`", .url)]
Url {
#[source]
source: url::ParseError,
url: String,
},
}
impl OsuError {
pub(crate) fn invalid_mods<E: serde::de::Error>(
mods: &serde_json::value::RawValue,
err: &SerdeError,
) -> E {
E::custom(format!("invalid mods `{mods}`: {err}"))
}
}
#[derive(Debug, thiserror::Error)]
pub enum ParsingError {
#[error("failed to parse `{}` into an Acronym", .0)]
Acronym(Box<str>),
#[error("failed to parse {} into Genre", .0)]
Genre(u8),
#[error("failed to parse `{}` into Grade", .0)]
Grade(String), #[error("failed to parse {} into Language", .0)]
Language(u8),
#[error("failed to parse {} into MatchTeam", .0)]
MatchTeam(u8),
#[error("failed to parse {} into RankStatus", .0)]
RankStatus(i8),
#[error("failed to parse {} into ScoringType", .0)]
ScoringType(u8),
#[error("failed to parse {} into TeamType", .0)]
TeamType(u8),
}