use std::fmt;
use hyper::StatusCode;
use failure_derive::Fail;
use serde_derive::Deserialize;
use crate::api;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Deserialize)]
pub(super) struct GdaxRestError<'a> {
message: &'a str,
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, Fail)]
pub struct RestError {
pub kind: RestErrorKind,
pub error_msg: Option<String>,
}
impl api::errors::ErrorKinded<!> for RestError {
fn kind(&self) -> api::errors::RestErrorKind<!> {
if self.kind == RestErrorKind::TooManyRequests {
return api::errors::RestErrorKind::TooManyRequests;
}
if self.kind == RestErrorKind::Timeout {
return api::errors::RestErrorKind::UnknownStatus;
}
if self.kind == RestErrorKind::InternalError {
return api::errors::RestErrorKind::OtherSide;
}
if self.error_msg
.as_ref()
.map(|msg| msg.starts_with("request timestamp expired"))
.unwrap_or(false)
{
return api::errors::RestErrorKind::OutsideTimeWindow;
}
api::errors::RestErrorKind::InvalidRequest
}
}
impl api::errors::ErrorKinded<api::errors::CancelErrorKind> for RestError {
fn kind(&self) -> api::errors::RestErrorKind<api::errors::CancelErrorKind> {
if self.kind == RestErrorKind::NotFound {
return api::errors::RestErrorKind::Specific(
api::errors::CancelErrorKind::UnknownOrder
);
}
if self.error_msg
.as_ref()
.map(|msg| msg.starts_with("Order already done"))
.unwrap_or(false)
{
return api::errors::RestErrorKind::Specific(
api::errors::CancelErrorKind::UnknownOrder
);
}
if self.error_msg
.as_ref()
.map(|msg| msg.starts_with("order not found"))
.unwrap_or(false)
{
return api::errors::RestErrorKind::Specific(
api::errors::CancelErrorKind::UnknownOrder
);
}
<Self as api::errors::ErrorKinded<!>>::kind(self).into()
}
}
impl api::errors::ErrorKinded<api::errors::OrderErrorKind> for RestError {
fn kind(&self) -> api::errors::RestErrorKind<api::errors::OrderErrorKind> {
if self.error_msg
.as_ref()
.map(|msg| msg.starts_with("Insufficient funds"))
.unwrap_or(false)
{
return api::errors::RestErrorKind::Specific(
api::errors::OrderErrorKind::InsufficientBalance
);
}
<Self as api::errors::ErrorKinded<!>>::kind(self).into()
}
}
impl RestError {
pub(super) fn from_gdax_error(status: StatusCode, gdax_error: Option<GdaxRestError>)
-> Self
{
RestError {
kind: RestErrorKind::from_status_code(status),
error_msg: gdax_error.map(|error| error.message.to_owned()),
}
}
}
impl fmt::Display for RestError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.kind)?;
if let Some(error_msg) = &self.error_msg {
write!(f, ": `{}`", error_msg)?;
}
Ok(())
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, Fail)]
pub enum RestErrorKind {
#[fail(display = "bad request")]
BadRequest,
#[fail(display = "unauthorized - invalid API key")]
Unauthorized,
#[fail(display = "forbidden")]
Forbidden,
#[fail(display = "not found")]
NotFound,
#[fail(display = "too many requests")]
TooManyRequests,
#[fail(display = "internal server error")]
InternalError,
#[fail(display = "timeout")]
Timeout,
#[fail(display = "unknown error, HTTP status code = {}", _0)]
Unknown(StatusCode),
}
impl RestErrorKind {
fn from_status_code(code: StatusCode) -> Self {
use self::RestErrorKind::*;
match code {
StatusCode::OK => panic!("`RestErrorKind::from_status_code` with `StatusCode::Ok`"),
StatusCode::BAD_REQUEST => BadRequest,
StatusCode::UNAUTHORIZED => Unauthorized,
StatusCode::FORBIDDEN => Forbidden,
StatusCode::NOT_FOUND => NotFound,
StatusCode::TOO_MANY_REQUESTS => TooManyRequests,
StatusCode::INTERNAL_SERVER_ERROR => InternalError,
StatusCode::GATEWAY_TIMEOUT => Timeout,
other => Unknown(other),
}
}
}