use hyper::{
header::InvalidHeaderValue, http::Error as HttpError, Error as HyperError, StatusCode,
};
use serde::Deserialize;
use serde_json::Error as SerdeError;
use std::{error::Error as StdError, fmt};
use url::ParseError;
#[derive(Debug, Deserialize)]
pub struct ApiError {
pub error: Option<String>,
}
impl StdError for ApiError {}
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)]
#[non_exhaustive]
pub enum OsuError {
BodyError { source: HttpError },
BuilderMissingId,
BuilderMissingSecret,
ChunkingResponse { source: HyperError },
CreatingTokenHeader { source: InvalidHeaderValue },
NotFound,
NoToken,
#[cfg(feature = "replay")]
OsuDbError { source: osu_db::Error },
Parsing { body: String, source: SerdeError },
ParsingValue { source: ParsingError },
Request { source: HyperError },
RequestTimeout,
Response {
body: String,
source: ApiError,
status: StatusCode,
},
ServiceUnavailable(String),
UnavailableEndpoint,
UpdateToken { source: Box<OsuError> },
Url {
source: ParseError,
url: String,
},
}
impl StdError for OsuError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
Self::BodyError { source } => Some(source),
Self::BuilderMissingId => None,
Self::BuilderMissingSecret => None,
Self::ChunkingResponse { source } => Some(source),
Self::CreatingTokenHeader { source } => Some(source),
Self::NotFound => None,
Self::NoToken => None,
#[cfg(feature = "replay")]
Self::OsuDbError { source } => Some(source),
Self::Parsing { source, .. } => Some(source),
Self::ParsingValue { source } => Some(source),
Self::Request { source } => Some(source),
Self::RequestTimeout => None,
Self::Response { source, .. } => Some(source),
Self::ServiceUnavailable(_) => None,
Self::UnavailableEndpoint => None,
Self::UpdateToken { source } => Some(source),
Self::Url { source, .. } => Some(source),
}
}
}
impl fmt::Display for OsuError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BodyError { .. } => f.write_str("failed to create request body"),
Self::BuilderMissingId => {
f.write_str("failed to build osu client, no client id was provided")
}
Self::BuilderMissingSecret => {
f.write_str("failed to build osu client, no client secret was provided")
}
Self::ChunkingResponse { .. } => f.write_str("failed to chunk the response"),
Self::CreatingTokenHeader { .. } => {
f.write_str("failed to parse token for authorization header")
}
Self::NotFound => f.write_str(
"the osu!api returned a 404 implying a missing score, incorrect name, id, etc",
),
Self::NoToken => f.write_str(
"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.",
),
#[cfg(feature = "replay")]
Self::OsuDbError { .. } => f.write_str("osu-db error"),
Self::Parsing { body, .. } => write!(f, "failed to deserialize response: {}", body),
Self::ParsingValue { .. } => f.write_str("failed to parse value"),
Self::Request { .. } => f.write_str("failed to send request"),
Self::RequestTimeout => f.write_str("osu!api did not respond in time"),
Self::Response { status, .. } => write!(f, "response error, status {}", status),
Self::ServiceUnavailable(body) => write!(
f,
"osu!api may be temporarily unavailable (received 503): {}",
body
),
Self::UnavailableEndpoint => {
f.write_str("the endpoint is not available for the client's authorization level")
}
Self::UpdateToken { .. } => f.write_str("failed to update osu!api token"),
Self::Url { url, .. } => write!(f, "failed to parse URL of a request; url: `{}`", url),
}
}
}
#[cfg(feature = "replay")]
impl From<osu_db::Error> for OsuError {
fn from(e: osu_db::Error) -> Self {
Self::OsuDbError { source: e }
}
}
impl From<HttpError> for OsuError {
fn from(e: HttpError) -> Self {
Self::BodyError { source: e }
}
}
impl From<ParsingError> for OsuError {
fn from(e: ParsingError) -> Self {
Self::ParsingValue { source: e }
}
}
#[derive(Debug)]
pub enum ParsingError {
Genre(u8),
Grade(String),
Language(u8),
ModsU32(u32),
ModsStr(String),
RankStatus(i8),
ScoringType(u8),
Team(u8),
TeamType(u8),
}
impl StdError for ParsingError {}
impl fmt::Display for ParsingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Genre(n) => write!(f, "failed to parse {} into Genre", n),
Self::Grade(s) => write!(f, "failed to parse `{}` into Grade", s),
Self::Language(n) => write!(f, "failed to parse {} into Language", n),
Self::ModsU32(n) => write!(f, "failed to parse {} into GameMods", n),
Self::ModsStr(s) => write!(f, "failed to parse `{}` into GameMods", s),
Self::RankStatus(n) => write!(f, "failed to parse {} into RankStatus", n),
Self::ScoringType(n) => write!(f, "failed to parse {} into ScoringType", n),
Self::Team(n) => write!(f, "failed to parse {} into Team", n),
Self::TeamType(n) => write!(f, "failed to parse {} into TeamType", n),
}
}
}