use std::{error::Error as StdError, fmt, io, str};
use crate::data::RoomNameParseError;
use self::ErrorKind::*;
#[derive(Debug)]
pub enum ErrorKind {
Unauthorized,
SerdeJson(serde_json::error::Error),
Url(url::ParseError),
Hyper(hyper::error::Error),
Io(io::Error),
StatusCode(hyper::StatusCode),
Api(ApiError),
RoomNameParse(RoomNameParseError<'static>),
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Debug)]
pub struct Error {
err: ErrorKind,
url: Option<url::Url>,
data: AdditionalData,
}
#[derive(Debug)]
enum AdditionalData {
Json(serde_json::Value),
Body(hyper::Chunk),
None,
}
impl From<Option<serde_json::Value>> for AdditionalData {
fn from(value: Option<serde_json::Value>) -> Self {
match value {
Some(v) => AdditionalData::Json(v),
None => AdditionalData::None,
}
}
}
impl From<Option<hyper::Chunk>> for AdditionalData {
fn from(value: Option<hyper::Chunk>) -> Self {
match value {
Some(v) => AdditionalData::Body(v),
None => AdditionalData::None,
}
}
}
impl AdditionalData {
fn or(self, other: AdditionalData) -> Self {
match self {
AdditionalData::Json(v) => AdditionalData::Json(v),
AdditionalData::Body(v) => AdditionalData::Body(v),
AdditionalData::None => other,
}
}
fn json(&self) -> Option<&serde_json::Value> {
match *self {
AdditionalData::Json(ref v) => Some(v),
_ => None,
}
}
fn body(&self) -> Option<&hyper::Chunk> {
match *self {
AdditionalData::Body(ref v) => Some(v),
_ => None,
}
}
}
impl Error {
pub fn with_url<T: Into<Error>>(err: T, url: Option<url::Url>) -> Error {
Error::with_json(err, url, None)
}
pub fn with_json<T: Into<Error>>(
err: T,
url: Option<url::Url>,
json: Option<serde_json::Value>,
) -> Error {
let err = err.into();
Error {
err: err.err,
url: url.or(err.url),
data: AdditionalData::from(json).or(err.data),
}
}
pub fn with_body<T: Into<Error>>(
err: T,
url: Option<url::Url>,
body: Option<hyper::Chunk>,
) -> Error {
let err = err.into();
Error {
err: err.err,
url: url.or(err.url),
data: AdditionalData::from(body).or(err.data),
}
}
pub fn kind(&self) -> &ErrorKind {
&self.err
}
pub fn url(&self) -> Option<&url::Url> {
self.url.as_ref()
}
pub fn json(&self) -> Option<&serde_json::Value> {
self.data.json()
}
pub fn body(&self) -> Option<&hyper::Chunk> {
self.data.body()
}
}
pub type Result<T> = ::std::result::Result<T, Error>;
impl From<ErrorKind> for Error {
fn from(err: ErrorKind) -> Error {
Error {
err: err,
url: None,
data: AdditionalData::None,
}
}
}
impl From<serde_json::error::Error> for Error {
fn from(err: serde_json::error::Error) -> Error {
ErrorKind::SerdeJson(err).into()
}
}
impl From<hyper::error::Error> for Error {
fn from(err: hyper::error::Error) -> Error {
ErrorKind::Hyper(err).into()
}
}
impl From<url::ParseError> for Error {
fn from(err: url::ParseError) -> Error {
ErrorKind::Url(err).into()
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
ErrorKind::Io(err).into()
}
}
impl From<hyper::StatusCode> for Error {
fn from(code: hyper::StatusCode) -> Error {
if code == hyper::StatusCode::UNAUTHORIZED {
ErrorKind::Unauthorized.into()
} else {
ErrorKind::StatusCode(code).into()
}
}
}
impl From<ApiError> for Error {
fn from(err: ApiError) -> Error {
ErrorKind::Api(err).into()
}
}
impl<'a> From<RoomNameParseError<'a>> for Error {
fn from(err: RoomNameParseError<'a>) -> Error {
ErrorKind::RoomNameParse(err.into_owned()).into()
}
}
impl From<NoToken> for Error {
fn from(_: NoToken) -> Error {
ErrorKind::Unauthorized.into()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.err {
SerdeJson(ref err) => err.fmt(f)?,
Hyper(ref err) => err.fmt(f)?,
Url(ref err) => err.fmt(f)?,
Io(ref err) => err.fmt(f)?,
StatusCode(ref status) => status.fmt(f)?,
Api(ref err) => err.fmt(f)?,
RoomNameParse(ref err) => err.fmt(f)?,
Unauthorized => {
write!(
f,
"access not authorized: token expired, username/password
incorrect or no login provided"
)?;
}
ErrorKind::__Nonexhaustive => unreachable!(),
}
if let Some(ref url) = self.url {
write!(f, " | at url '{}'", url)?;
}
match self.data {
AdditionalData::Json(ref json) => write!(f, " | return json: '{}'", json)?,
AdditionalData::Body(ref body) => match str::from_utf8(body) {
Ok(v) => write!(f, " | return body: '{}'", v)?,
Err(_) => write!(f, " | return body: '{:?}'", &*body)?,
},
AdditionalData::None => (),
}
Ok(())
}
}
impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self.err {
SerdeJson(ref err) => Some(err),
Hyper(ref err) => Some(err),
Url(ref err) => Some(err),
Io(ref err) => Some(err),
Api(ref err) => Some(err),
RoomNameParse(ref err) => Some(err),
StatusCode(_) | Unauthorized => None,
__Nonexhaustive => unreachable!(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoToken;
const NO_TOKEN: &str = "token storage empty when attempting to make authenticated call.";
impl fmt::Display for NoToken {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
NO_TOKEN.fmt(f)
}
}
impl StdError for NoToken {
fn description(&self) -> &str {
NO_TOKEN
}
}
#[derive(Debug, Clone)]
pub enum ApiError {
NotOk(i32),
ServerDown,
InvalidRoom,
InvalidShard,
ResultNotFound,
UserNotFound,
RegistrationNotAllowed,
UsernameAlreadyExists,
InvalidParameters,
GenericError(String),
MissingField(&'static str),
MalformedResponse(String),
#[doc(hidden)]
__Nonexhaustive,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ApiError::NotOk(code) => write!(f, "non-ok result from api result: {}", code),
ApiError::MissingField(field) => write!(f, "missing field from api result: {}", field),
ApiError::MalformedResponse(ref desc) => {
write!(f, "malformed field from api result: {}", desc)
}
ApiError::GenericError(ref err) => write!(f, "api call resulted in error: {}", err),
ApiError::InvalidRoom
| ApiError::InvalidShard
| ApiError::ResultNotFound
| ApiError::UserNotFound
| ApiError::InvalidParameters
| ApiError::ServerDown
| ApiError::RegistrationNotAllowed
| ApiError::UsernameAlreadyExists => write!(f, "{}", self.description()),
ApiError::__Nonexhaustive => unreachable!(),
}
}
}
impl StdError for ApiError {
fn description(&self) -> &str {
match *self {
ApiError::NotOk(_) => "non-ok result from api call",
ApiError::MissingField(_) => "missing field in api result",
ApiError::MalformedResponse(_) => "malformed field in api result",
ApiError::GenericError(_) => "api call resulted in error",
ApiError::InvalidRoom => "malformed api call: invalid room",
ApiError::InvalidShard => "malformed apic all: invalid shard",
ApiError::ResultNotFound => "specific data requested was not found",
ApiError::UserNotFound => "the user requested was not found",
ApiError::RegistrationNotAllowed => {
"registering users via the API is disabled: \
a server password has been set"
}
ApiError::UsernameAlreadyExists => "the username used already exists",
ApiError::InvalidParameters => "one or more parameters to the function were invalid",
ApiError::ServerDown => "the server requested is offline",
ApiError::__Nonexhaustive => unreachable!(),
}
}
fn cause(&self) -> Option<&dyn StdError> {
None
}
}