use std::time::Duration;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct ProblemError {
pub code: String,
pub field: Option<String>,
pub message: String,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ApiErrorKind {
Validation,
Authentication,
NotFound,
Conflict,
Gone,
RateLimit,
Server,
Unavailable,
Unknown,
}
impl ApiErrorKind {
pub(crate) const fn from_status(status: u16) -> Self {
match status {
400 => Self::Validation,
401 | 403 => Self::Authentication,
404 => Self::NotFound,
409 => Self::Conflict,
410 => Self::Gone,
429 => Self::RateLimit,
500 => Self::Server,
503 => Self::Unavailable,
_ => Self::Unknown,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ApiError {
pub status: u16,
pub kind: ApiErrorKind,
pub problem_type: Option<String>,
pub title: Option<String>,
pub detail: Option<String>,
pub code: Option<String>,
pub field: Option<String>,
pub errors: Vec<ProblemError>,
pub retry_after: Option<Duration>,
pub trace_id: Option<String>,
pub raw_body: String,
}
impl std::fmt::Display for ApiError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let message = self
.detail
.as_deref()
.or(self.title.as_deref())
.unwrap_or("Paymos API request failed");
if let Some(code) = self.code.as_deref() {
write!(
formatter,
"Paymos API returned {} ({code}): {message}",
self.status
)
} else {
write!(formatter, "Paymos API returned {}: {message}", self.status)
}
}
}
impl std::error::Error for ApiError {}
#[derive(Debug, Default, Deserialize)]
pub(crate) struct ProblemDetails {
#[serde(rename = "type")]
pub problem_type: String,
pub title: String,
pub status: u16,
pub detail: String,
pub code: String,
pub field: Option<String>,
#[serde(default)]
pub errors: Vec<ProblemError>,
pub trace_id: Option<String>,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("invalid Paymos client configuration: {0}")]
Configuration(String),
#[error("invalid Paymos argument: {0}")]
InvalidArgument(String),
#[error("Paymos transport failed: {0}")]
Transport(#[source] reqwest::Error),
#[error(transparent)]
Api(Box<ApiError>),
#[error("Paymos request could not be serialized: {0}")]
Serialization(#[source] serde_json::Error),
#[error("Paymos API returned an invalid response: {0}")]
InvalidResponse(#[source] serde_json::Error),
#[error("Paymos pagination failed: {0}")]
Pagination(String),
#[error("system clock is before the Unix epoch")]
InvalidClock,
}
impl From<ApiError> for Error {
fn from(value: ApiError) -> Self {
Self::Api(Box::new(value))
}
}