use crate::types;
use std::borrow::Cow;
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
#[error("Unauthorized: {0}")]
Unauthorized(Cow<'static, str>),
#[error("{0}")]
MalformedJson(String),
#[error("Internal Server error: {0}")]
InternalServer(Cow<'static, str>),
#[error("{0}")]
Client(#[from] ClientError),
#[error("{0}")]
Server(#[from] ServerError),
#[error("{0}")]
Hub(#[from] HubError),
}
impl Error {
pub fn http_status_code(&self) -> http::StatusCode {
match self {
Self::Unauthorized(_) => http::StatusCode::UNAUTHORIZED,
Self::MalformedJson(_) => http::StatusCode::BAD_REQUEST,
Self::InternalServer(_) => http::StatusCode::INTERNAL_SERVER_ERROR,
_ => http::StatusCode::OK,
}
}
pub fn code(&self) -> u32 {
match self {
Self::Unauthorized(_) => 2001,
Self::MalformedJson(_) => 2001,
Self::InternalServer(_) => 3000,
Self::Client(ClientError::Generic { .. }) => 2000,
Self::Client(ClientError::InvalidParams { .. }) => 2001,
Self::Client(ClientError::NotEnoughInformation { .. }) => 2002,
Self::Client(ClientError::UnknownLocation { .. }) => 2003,
Self::Client(ClientError::UnknownToken { .. }) => 2004,
Self::Server(ServerError::Generic { .. }) => 3000,
Self::Server(ServerError::UnusableApi { .. }) => 3001,
Self::Server(ServerError::UnsupportedVersion { .. }) => 3002,
Self::Server(ServerError::IncompatibleEndpoints { .. }) => 3003,
Self::Hub(HubError::UnknownReceiver { .. }) => 4001,
Self::Hub(HubError::Timeout { .. }) => 4002,
Self::Hub(HubError::Connection { .. }) => 4003,
}
}
pub fn unauthorized(message: impl Into<Cow<'static, str>>) -> Self {
Self::Unauthorized(message.into())
}
pub fn json_input(err: &serde_json::Error) -> Self {
if err.is_data() {
Self::invalid_params(format!("malformed body: {}", err))
} else {
Self::MalformedJson(format!("invalid json in body: {}", err))
}
}
pub fn invalid_params(message: impl Into<Cow<'static, str>>) -> Self {
Self::Client(ClientError::InvalidParams {
message: message.into(),
})
}
pub fn client_generic(message: impl Into<Cow<'static, str>>) -> Self {
Self::Client(ClientError::Generic {
message: message.into(),
})
}
pub fn server_generic(message: impl Into<Cow<'static, str>>) -> Self {
Self::Client(ClientError::Generic {
message: message.into(),
})
}
pub fn internal_server(message: impl Into<Cow<'static, str>>) -> Self {
Self::InternalServer(message.into())
}
}
impl From<serde_json::Error> for Error {
fn from(err: serde_json::Error) -> Self {
Self::json_input(&err)
}
}
impl From<url::ParseError> for Error {
fn from(err: url::ParseError) -> Self {
Self::invalid_params(format!("Invalid URL provided: {}", err))
}
}
impl From<tokio::time::error::Elapsed> for Error {
fn from(_: tokio::time::error::Elapsed) -> Self {
Self::internal_server("Internal timeout handling request")
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ClientError {
#[error("{message}")]
Generic { message: Cow<'static, str> },
#[error("Invalid or missing parameters: {message}")]
InvalidParams {
message: Cow<'static, str>,
},
#[error("Missing information: {message}")]
NotEnoughInformation { message: Cow<'static, str> },
#[error("Unknown location `{id}`")]
UnknownLocation { id: String },
#[error("Unknown token")]
UnknownToken,
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum ServerError {
#[error("{message}")]
Generic { message: Cow<'static, str> },
#[error("{message}")]
UnusableApi { message: Cow<'static, String> },
#[error("Unsupported version: {0}")]
UnsupportedVersion(types::VersionNumber),
#[error("Minimal required implementation not met")]
IncompatibleEndpoints,
}
impl ServerError {
pub fn unusable_api(message: impl Into<String>) -> Self {
Self::UnusableApi {
message: Cow::Owned(message.into()),
}
}
}
#[derive(Clone, Debug, thiserror::Error, PartialEq, Eq)]
pub enum HubError {
#[error("Unknown receiver `{target}`")]
UnknownReceiver { target: types::Url },
#[error("Timeout calling `{target}`. Elapsed after {elapsed}")]
Timeout {
target: types::Url,
elapsed: types::Duration,
},
#[error("Could not connect to `{target}`. Target is down")]
Connection { target: types::Url },
}
#[cfg(feature = "axum")]
mod axum_extensions {
use super::Error;
use axum::{
response::{IntoResponse, Response},
Json,
};
impl IntoResponse for Error {
fn into_response(self) -> Response {
let http_status = self.http_status_code();
let body =
serde_json::to_value(&crate::Response::from_err(self)).expect("Serializing body");
(http_status, Json(body)).into_response()
}
}
}
#[cfg(feature = "warp")]
mod warp_extensions {
impl warp::reject::Reject for super::Error {}
}