use error_chain::Backtrace;
use reqwest;
use reqwest::StatusCode;
use serde_json;
use std::fmt::Display;
use std::{fmt, str};
pub const INPUT_ERROR: &'static str = "InputError";
pub const UNSUPPORTED_ERROR: &'static str = "UnsupportedError";
pub const ALGORITHM_ERROR: &'static str = "AlgorithmError";
fn default_error_type() -> String {
ALGORITHM_ERROR.into()
}
error_chain! {
errors {
Api(err: ApiError) {
display("{}", err)
}
Http(context: String) {
display("HTTP error {}", context)
}
InvalidBaseUrl {
display("unable to parse base URL")
}
InvalidDataUri(uri: String) {
display("invalid data URI '{}'", uri)
}
InvalidAlgoUri(uri: String) {
display("invalid algorithm URI: {}", &uri)
}
DecodeJson(item: &'static str) {
display("failed to decode {} json", item)
}
EncodeJson(item: &'static str) {
display("failed to encode {} as json", item)
}
DecodeBase64(item: &'static str) {
display("failed to decode {} as base64", item)
}
Io(context: String) {
display("I/O error {}", context)
}
InvalidContentType(t: String) {
display("invalid content type: '{}'", t)
}
MismatchedContentType(expected: &'static str) {
display("content did not match content type: '{}'", expected)
}
UnexpectedContentType(expected: &'static str, actual: String) {
display("expected content type '{}', received '{}'", expected, actual)
}
NotFound(url: reqwest::Url) {
display("404 Not Found ({})", url)
}
MissingDataType {
display("API response missing data type")
}
InvalidDataType(t: String) {
display("API responded with invalid data type: '{}'", t)
}
InvalidApiKey {
display("API key is invalid")
}
UnexpectedDataType(expected: &'static str, actual: String) {
display("expected API response with data type '{}', received '{}'", expected, actual)
}
#[doc(hidden)]
__DontMatchMe {}
}
}
#[derive(Debug, Deserialize)]
pub struct ApiError {
pub message: String,
#[serde(default = "default_error_type")]
pub error_type: String,
pub stacktrace: Option<String>,
}
impl Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.stacktrace {
Some(ref trace) => write!(f, "{}\n{}", self.message, trace),
None => write!(f, "{}", self.message),
}
}
}
impl From<ApiError> for Error {
fn from(err: ApiError) -> Self {
Error::from_kind(ErrorKind::Api(err))
}
}
impl std::error::Error for ApiError {}
impl ApiError {
pub fn new<S: Into<String>>(error_type: S, message: S) -> ApiError {
ApiError {
error_type: error_type.into(),
message: message.into(),
stacktrace: Some(format!("{:?}", Backtrace::new())),
}
}
pub(crate) fn from_json_or_status(json: &str, status: StatusCode) -> ApiError {
match serde_json::from_str::<ApiErrorResponse>(json) {
Ok(err_res) => err_res.error,
Err(_) => ApiError::from(status.to_string()),
}
}
}
impl<S> From<S> for ApiError
where
S: Into<String>,
{
fn from(message: S) -> ApiError {
ApiError {
error_type: ALGORITHM_ERROR.into(),
message: message.into(),
stacktrace: Some(format!("{:?}", Backtrace::new())),
}
}
}
#[derive(Debug, Deserialize)]
#[doc(hidden)]
pub struct ApiErrorResponse {
pub error: ApiError,
}
#[doc(hidden)]
impl Error {
pub fn from_json(json: &str) -> Error {
let decoded_error = serde_json::from_str::<ApiErrorResponse>(json);
match decoded_error.chain_err(|| ErrorKind::DecodeJson("api error response")) {
Ok(err_res) => ErrorKind::Api(err_res.error).into(),
Err(err) => err,
}
}
}