use std::any;
use std::error::Error;
use thiserror::Error;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum BodyError {
#[error("failed to serialize to JSON: {}", source)]
SerdeJson {
#[from]
source: serde_json::Error,
},
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ApiError<E>
where
E: Error + Send + Sync + 'static,
{
#[error("client error: {}", source)]
Client {
source: E,
},
#[error("failed to parse url: {}", source)]
UrlParse {
#[from]
source: url::ParseError,
},
#[error("failed to create request body: {}", source)]
Body {
#[from]
source: BodyError,
},
#[error("could not parse JSON response: {}", source)]
Json {
#[from]
source: serde_json::Error,
},
#[error("traduora server error: {}", msg)]
Traduora {
msg: String,
},
#[error("traduora internal server error {}", status)]
TraduoraService {
status: http::StatusCode,
data: Vec<u8>,
},
#[error("traduora server error: {:?}", obj)]
TraduoraObject {
obj: serde_json::Value,
},
#[error("traduora server error: {:?}", obj)]
TraduoraUnrecognized {
obj: serde_json::Value,
},
#[error("could not parse {} data from JSON: {}", typename, source)]
DataType {
source: serde_json::Error,
typename: &'static str,
},
}
impl<E> ApiError<E>
where
E: Error + Send + Sync + 'static,
{
pub fn client(source: E) -> Self {
Self::Client { source }
}
pub(crate) fn server_error(status: http::StatusCode, body: &bytes::Bytes) -> Self {
Self::TraduoraService {
status,
data: body.into_iter().copied().collect(),
}
}
pub(crate) fn from_traduora(value: serde_json::Value) -> Self {
let error_value = value
.pointer("/message")
.or_else(|| value.pointer("/error"));
match error_value {
Some(error_value) => match error_value.as_str() {
Some(msg) => Self::Traduora { msg: msg.into() },
None => Self::TraduoraObject {
obj: error_value.clone(),
},
},
None => Self::TraduoraUnrecognized { obj: value },
}
}
pub(crate) fn data_type<T>(source: serde_json::Error) -> Self {
Self::DataType {
source,
typename: any::type_name::<T>(),
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use thiserror::Error;
use crate::ApiError;
#[derive(Debug, Error)]
#[error("my error")]
enum MyError {}
#[test]
fn traduora_error_error() {
let obj = json!({
"error": "error contents",
});
let err: ApiError<MyError> = ApiError::from_traduora(obj);
if let ApiError::Traduora { msg } = err {
assert_eq!(msg, "error contents");
} else {
panic!("unexpected error: {}", err);
}
}
#[test]
fn traduora_error_message_string() {
let obj = json!({
"message": "error contents",
});
let err: ApiError<MyError> = ApiError::from_traduora(obj);
if let ApiError::Traduora { msg } = err {
assert_eq!(msg, "error contents");
} else {
panic!("unexpected error: {}", err);
}
}
#[test]
fn traduora_error_message_object() {
let err_obj = json!({
"blah": "foo",
});
let obj = json!({
"message": err_obj,
});
let err: ApiError<MyError> = ApiError::from_traduora(obj);
if let ApiError::TraduoraObject { obj } = err {
assert_eq!(obj, err_obj);
} else {
panic!("unexpected error: {}", err);
}
}
#[test]
fn traduora_error_message_unrecognized() {
let err_obj = json!({
"some_weird_key": "an even weirder value",
});
let err: ApiError<MyError> = ApiError::from_traduora(err_obj.clone());
if let ApiError::TraduoraUnrecognized { obj } = err {
assert_eq!(obj, err_obj);
} else {
panic!("unexpected error: {}", err);
}
}
}