use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::moderation::FilingProblem;
use crate::responses::{BanInfoResponse, ErrorResponse};
macro_rules! error_code {
($(#[$meta:meta])* $name:ident = $code:literal) => {
$(#[$meta])*
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct $name;
impl $name {
pub const CODE: &'static str = $code;
}
impl Serialize for $name {
fn serialize<S: serde::Serializer>(
&self,
s: S,
) -> Result<S::Ok, S::Error> {
s.serialize_str($code)
}
}
impl<'de> Deserialize<'de> for $name {
fn deserialize<D: serde::Deserializer<'de>>(
d: D,
) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
match s == $code {
true => Ok(Self),
false => Err(serde::de::Error::custom(format!(
"expected {:?}, got {s:?}",
$code
))),
}
}
}
#[cfg(feature = "schemars")]
impl schemars::JsonSchema for $name {
fn inline_schema() -> bool {
true
}
fn schema_name() -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(stringify!($name))
}
fn json_schema(
_: &mut schemars::SchemaGenerator,
) -> schemars::Schema {
schemars::json_schema!({
"type": "string",
"const": $code,
})
}
}
};
}
pub(crate) use error_code;
error_code!(
AccountSuspended = "account_suspended"
);
error_code!(
RateLimited = "rate_limited"
);
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ExportRateLimited {
pub error: RateLimited,
pub message: String,
pub previous_at: DateTime<Utc>,
pub retry_after_at: DateTime<Utc>,
}
impl ExportRateLimited {
pub fn new(
previous_at: DateTime<Utc>,
retry_after_at: DateTime<Utc>,
) -> Self {
Self {
error: RateLimited,
message: format!(
"An export for this agent was generated at {}. Please retry \
after {}.",
previous_at.format("%Y-%m-%d %H:%M UTC"),
retry_after_at.format("%Y-%m-%d %H:%M UTC"),
),
previous_at,
retry_after_at,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AppealRejected {
pub error: String,
pub problems: Vec<FilingProblem>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AppealCreditsExhausted {
pub error: String,
pub balance: u32,
pub cap: u32,
pub next_accrual_at: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum ApiErrorBody {
Suspended(Box<BanInfoResponse>),
ExportRateLimited(ExportRateLimited),
AppealRejected(AppealRejected),
AppealCreditsExhausted(AppealCreditsExhausted),
Plain(ErrorResponse),
}
impl ApiErrorBody {
pub fn message(&self) -> &str {
match self {
Self::Suspended(b) => &b.message,
Self::ExportRateLimited(b) => &b.message,
Self::AppealRejected(b) => &b.error,
Self::AppealCreditsExhausted(b) => &b.error,
Self::Plain(b) => &b.error,
}
}
}
impl From<ErrorResponse> for ApiErrorBody {
fn from(body: ErrorResponse) -> Self {
Self::Plain(body)
}
}
impl From<BanInfoResponse> for ApiErrorBody {
fn from(body: BanInfoResponse) -> Self {
Self::Suspended(Box::new(body))
}
}
impl From<ExportRateLimited> for ApiErrorBody {
fn from(body: ExportRateLimited) -> Self {
Self::ExportRateLimited(body)
}
}
impl From<AppealRejected> for ApiErrorBody {
fn from(body: AppealRejected) -> Self {
Self::AppealRejected(body)
}
}
impl From<AppealCreditsExhausted> for ApiErrorBody {
fn from(body: AppealCreditsExhausted) -> Self {
Self::AppealCreditsExhausted(body)
}
}
#[cfg(test)]
mod tests;