use crate::{AuthenticatorError, FromErrorDetail};
use reqwest::header::InvalidHeaderValue;
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt;
use thiserror::Error;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ApiErrorWrapper {
pub error: ApiErrorMessage,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum IntegerStringOrObject {
Integer(i64),
String(String),
Object(HashMap<String, Box<IntegerStringOrObject>>),
}
impl IntegerStringOrObject {
pub fn integer(&self) -> Option<i64> {
match self {
Self::Integer(i) => Some(*i),
Self::String(s) => s.parse().ok(),
_ => None,
}
}
pub fn string(&self) -> Option<&String> {
match self {
Self::Integer(_) => None,
Self::String(s) => Some(s),
_ => None,
}
}
pub fn object(&self) -> Option<&HashMap<String, Box<Self>>> {
match self {
Self::Object(o) => Some(o),
_ => None,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ApiErrorDetail(pub Vec<HashMap<String, Box<IntegerStringOrObject>>>);
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ApiErrorMessage {
pub code: u32,
pub message: String,
pub missing: Option<ApiErrorDetail>,
pub duplicated: Option<ApiErrorDetail>,
}
impl ApiErrorDetail {
pub fn get_values<T: FromErrorDetail>(&self) -> impl Iterator<Item = T> + '_ {
self.iter().filter_map(|m| T::from_detail(m))
}
pub fn get_integer(
map: &HashMap<String, Box<IntegerStringOrObject>>,
key: &str,
) -> Option<i64> {
map.get(key).and_then(|f| f.integer())
}
pub fn get_string<'a>(
map: &'a HashMap<String, Box<IntegerStringOrObject>>,
key: &str,
) -> Option<&'a String> {
map.get(key).and_then(|f| f.string())
}
pub fn get_object<'a>(
map: &'a HashMap<String, Box<IntegerStringOrObject>>,
key: &str,
) -> Option<&'a HashMap<String, Box<IntegerStringOrObject>>> {
map.get(key).and_then(|f| f.object())
}
pub fn iter(&self) -> impl Iterator<Item = &HashMap<String, Box<IntegerStringOrObject>>> + '_ {
self.0.iter()
}
}
#[derive(Debug, Default)]
pub struct CdfApiError {
pub code: u32,
pub message: String,
pub missing: Option<ApiErrorDetail>,
pub duplicated: Option<ApiErrorDetail>,
pub request_id: Option<String>,
}
impl fmt::Display for CdfApiError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}: {}. RequestId: {}",
&self.code,
&self.message,
self.request_id.as_deref().unwrap_or("")
)
}
}
impl CdfApiError {
pub(crate) fn new(raw: ApiErrorMessage, request_id: Option<String>) -> Self {
CdfApiError {
code: raw.code,
message: raw.message,
missing: raw.missing,
duplicated: raw.duplicated,
request_id,
}
}
}
impl Error {
fn new_from_code(code: StatusCode, err: CdfApiError) -> Error {
match code {
StatusCode::BAD_REQUEST => Error::BadRequest(err),
StatusCode::UNAUTHORIZED => Error::Unauthorized(err),
StatusCode::FORBIDDEN => Error::Forbidden(err),
StatusCode::NOT_FOUND => Error::NotFound(err),
StatusCode::CONFLICT => Error::Conflict(err),
StatusCode::UNPROCESSABLE_ENTITY => Error::UnprocessableEntity(err),
_ => Error::OtherApiError(err),
}
}
pub(crate) fn new_from_cdf(
code: StatusCode,
err: ApiErrorWrapper,
request_id: Option<String>,
) -> Error {
let cdf_err = CdfApiError::new(err.error, request_id);
Self::new_from_code(code, cdf_err)
}
pub(crate) fn new_without_json(
code: StatusCode,
err: String,
request_id: Option<String>,
) -> Error {
let err = CdfApiError {
code: code.to_owned().as_u16().into(),
message: err,
request_id,
..Default::default()
};
Self::new_from_code(code, err)
}
}
pub type Result<T> = ::std::result::Result<T, Error>;
#[derive(Debug, Error)]
pub enum Error {
#[error("Bad request (400): {0}")]
BadRequest(CdfApiError),
#[error("Unauthorized (401): {0}")]
Unauthorized(CdfApiError),
#[error("Forbidden (403): {0}")]
Forbidden(CdfApiError),
#[error("Not Found (404): {0}")]
NotFound(CdfApiError),
#[error("Conflict (409): {0}")]
Conflict(CdfApiError),
#[error("Unprocessable Entity (422): {0}")]
UnprocessableEntity(CdfApiError),
#[error("Other Api Error: {0}")]
OtherApiError(CdfApiError),
#[error("Environment Variable Missing: {0}")]
EnvironmentVariableMissing(String),
#[error("Error from authenticator: {0}")]
Authenticator(AuthenticatorError),
#[error("Invalid header value: {0}")]
InvalidHeader(#[from] InvalidHeaderValue),
#[error("Error accessing file: {0}")]
IOError(#[from] std::io::Error),
#[error("Error collecting stream: {0:#}")]
StreamError(anyhow::Error),
#[error("Error in middleware: {0:#}")]
Middleware(anyhow::Error),
#[error("Error in configuration: {0}")]
Config(String),
#[error("Unexpected request error: {0}")]
Reqwest(#[from] reqwest::Error),
#[error("Unexpected JSON error: {0}")]
SerdeJson(#[from] ::serde_json::Error),
#[error("Unexpected protobuf error: {0}")]
Prost(#[from] ::prost::DecodeError),
#[error("{0}")]
Other(String),
}
impl From<reqwest_middleware::Error> for Error {
fn from(err: reqwest_middleware::Error) -> Self {
match err {
reqwest_middleware::Error::Middleware(x) => Error::Middleware(x),
reqwest_middleware::Error::Reqwest(x) => Self::from(x),
}
}
}