use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ErrorCode {
ValidationError,
InvalidDomain,
UnconfiguredDomain,
SendError,
RetrievalError,
TransmissionFailed,
ResourceAlreadyExists,
TemplateNotFound,
NotFound,
QuotaExceeded,
DailyQuotaExceeded,
InsufficientScope,
ScheduleCancellationFailed,
#[serde(untagged)]
Unknown(String),
}
impl fmt::Display for ErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ValidationError => write!(f, "validation_error"),
Self::InvalidDomain => write!(f, "invalid_domain"),
Self::UnconfiguredDomain => write!(f, "unconfigured_domain"),
Self::SendError => write!(f, "send_error"),
Self::RetrievalError => write!(f, "retrieval_error"),
Self::TransmissionFailed => write!(f, "transmission_failed"),
Self::ResourceAlreadyExists => write!(f, "resource_already_exists"),
Self::TemplateNotFound => write!(f, "template_not_found"),
Self::NotFound => write!(f, "not_found"),
Self::QuotaExceeded => write!(f, "quota_exceeded"),
Self::DailyQuotaExceeded => write!(f, "daily_quota_exceeded"),
Self::InsufficientScope => write!(f, "insufficient_scope"),
Self::ScheduleCancellationFailed => write!(f, "schedule_cancellation_failed"),
Self::Unknown(s) => write!(f, "{s}"),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("http error: {0}")]
Http(#[from] reqwest::Error),
#[error("api error: {0}")]
Api(#[from] ApiError),
#[error("validation error: {0}")]
Validation(#[from] ValidationError),
#[error("failed to parse API response: {0}")]
Parse(String),
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ApiError {
pub message: String,
#[serde(default)]
pub error_code: Option<ErrorCode>,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref code) = self.error_code {
write!(f, "[{}] {}", code, self.message)
} else {
write!(f, "{}", self.message)
}
}
}
impl std::error::Error for ApiError {}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ValidationError {
pub message: String,
#[serde(default)]
pub error_code: Option<ErrorCode>,
#[serde(default)]
pub errors: HashMap<String, Vec<String>>,
}
impl fmt::Display for ValidationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.message)?;
for (field, messages) in &self.errors {
for msg in messages {
write!(f, "\n {field}: {msg}")?;
}
}
Ok(())
}
}
impl std::error::Error for ValidationError {}
#[derive(Debug, serde::Deserialize)]
pub(crate) struct RawErrorResponse {
pub message: String,
#[serde(default)]
pub error_code: Option<ErrorCode>,
#[serde(default)]
pub errors: Option<HashMap<String, Vec<String>>>,
}
impl RawErrorResponse {
pub fn into_error(self) -> Error {
if let Some(errors) = self.errors {
Error::Validation(ValidationError {
message: self.message,
error_code: self.error_code,
errors,
})
} else {
Error::Api(ApiError {
message: self.message,
error_code: self.error_code,
})
}
}
}