1use chrono::{DateTime, Utc};
17use serde::{Deserialize, Serialize};
18
19use crate::moderation::FilingProblem;
20use crate::responses::{BanInfoResponse, ErrorResponse};
21
22macro_rules! error_code {
25 ($(#[$meta:meta])* $name:ident = $code:literal) => {
26 $(#[$meta])*
27 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
28 pub struct $name;
29
30 impl $name {
31 pub const CODE: &'static str = $code;
33 }
34
35 impl Serialize for $name {
36 fn serialize<S: serde::Serializer>(
37 &self,
38 s: S,
39 ) -> Result<S::Ok, S::Error> {
40 s.serialize_str($code)
41 }
42 }
43
44 impl<'de> Deserialize<'de> for $name {
45 fn deserialize<D: serde::Deserializer<'de>>(
46 d: D,
47 ) -> Result<Self, D::Error> {
48 let s = String::deserialize(d)?;
49 match s == $code {
50 true => Ok(Self),
51 false => Err(serde::de::Error::custom(format!(
52 "expected {:?}, got {s:?}",
53 $code
54 ))),
55 }
56 }
57 }
58
59 #[cfg(feature = "schemars")]
60 impl schemars::JsonSchema for $name {
61 fn inline_schema() -> bool {
62 true
63 }
64
65 fn schema_name() -> std::borrow::Cow<'static, str> {
66 std::borrow::Cow::Borrowed(stringify!($name))
67 }
68
69 fn json_schema(
70 _: &mut schemars::SchemaGenerator,
71 ) -> schemars::Schema {
72 schemars::json_schema!({
73 "type": "string",
74 "const": $code,
75 })
76 }
77 }
78 };
79}
80
81pub(crate) use error_code;
82
83error_code!(
84 AccountSuspended = "account_suspended"
86);
87
88error_code!(
89 RateLimited = "rate_limited"
91);
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
96#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
97pub struct ExportRateLimited {
98 pub error: RateLimited,
99 pub message: String,
101 pub previous_at: DateTime<Utc>,
103 pub retry_after_at: DateTime<Utc>,
105}
106
107impl ExportRateLimited {
108 pub fn new(
109 previous_at: DateTime<Utc>,
110 retry_after_at: DateTime<Utc>,
111 ) -> Self {
112 Self {
113 error: RateLimited,
114 message: format!(
115 "An export for this agent was generated at {}. Please retry \
116 after {}.",
117 previous_at.format("%Y-%m-%d %H:%M UTC"),
118 retry_after_at.format("%Y-%m-%d %H:%M UTC"),
119 ),
120 previous_at,
121 retry_after_at,
122 }
123 }
124}
125
126#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
129#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
130pub struct AppealRejected {
131 pub error: String,
133 pub problems: Vec<FilingProblem>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
140pub struct AppealCreditsExhausted {
141 pub error: String,
143 pub balance: u32,
145 pub cap: u32,
147 pub next_accrual_at: DateTime<Utc>,
149}
150
151#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
158#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
159#[serde(untagged)]
160pub enum ApiErrorBody {
161 Suspended(Box<BanInfoResponse>),
163 ExportRateLimited(ExportRateLimited),
164 AppealRejected(AppealRejected),
165 AppealCreditsExhausted(AppealCreditsExhausted),
166 Plain(ErrorResponse),
167}
168
169impl ApiErrorBody {
170 pub fn message(&self) -> &str {
173 match self {
174 Self::Suspended(b) => &b.message,
175 Self::ExportRateLimited(b) => &b.message,
176 Self::AppealRejected(b) => &b.error,
177 Self::AppealCreditsExhausted(b) => &b.error,
178 Self::Plain(b) => &b.error,
179 }
180 }
181}
182
183impl From<ErrorResponse> for ApiErrorBody {
184 fn from(body: ErrorResponse) -> Self {
185 Self::Plain(body)
186 }
187}
188
189impl From<BanInfoResponse> for ApiErrorBody {
190 fn from(body: BanInfoResponse) -> Self {
191 Self::Suspended(Box::new(body))
192 }
193}
194
195impl From<ExportRateLimited> for ApiErrorBody {
196 fn from(body: ExportRateLimited) -> Self {
197 Self::ExportRateLimited(body)
198 }
199}
200
201impl From<AppealRejected> for ApiErrorBody {
202 fn from(body: AppealRejected) -> Self {
203 Self::AppealRejected(body)
204 }
205}
206
207impl From<AppealCreditsExhausted> for ApiErrorBody {
208 fn from(body: AppealCreditsExhausted) -> Self {
209 Self::AppealCreditsExhausted(body)
210 }
211}
212
213#[cfg(test)]
214mod tests;