1use serde::{Deserialize, Serialize};
2use std::{fmt, time::Duration};
3
4#[derive(Debug)]
6pub enum Error {
7 Config(String),
9 Provider {
11 status: u16,
12 body: String,
13 retry_after: Option<Duration>,
14 },
15 Json(serde_json::Error),
17 Internal(String),
19 Timeout,
21}
22
23impl fmt::Display for Error {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 match self {
26 Error::Config(msg) => write!(f, "config error: {msg}"),
27 Error::Provider { status, body, .. } => {
28 write!(f, "provider error (HTTP {status}): {body}")
29 }
30 Error::Json(e) => write!(f, "json error: {e}"),
31 Error::Internal(msg) => write!(f, "internal error: {msg}"),
32 Error::Timeout => write!(f, "request timed out"),
33 }
34 }
35}
36
37impl Error {
38 pub fn is_transient(&self) -> bool {
42 match self {
43 Error::Provider { status, .. } => matches!(status, 429 | 500 | 502 | 503 | 504),
44 Error::Internal(_) | Error::Timeout => true,
45 _ => false,
46 }
47 }
48
49 pub fn retry_after(&self) -> Option<Duration> {
51 match self {
52 Error::Provider { retry_after, .. } => *retry_after,
53 _ => None,
54 }
55 }
56
57 pub fn not_implemented(method: &str) -> Self {
62 Error::Internal(format!("provider method '{method}' not implemented"))
63 }
64}
65
66impl std::error::Error for Error {
67 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
68 match self {
69 Error::Json(e) => Some(e),
70 _ => None,
71 }
72 }
73}
74
75#[cfg(feature = "gateway")]
76impl From<toml::de::Error> for Error {
77 fn from(e: toml::de::Error) -> Self {
78 Error::Config(e.to_string())
79 }
80}
81
82impl From<serde_json::Error> for Error {
83 fn from(e: serde_json::Error) -> Self {
84 Error::Json(e)
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
90#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
91pub struct ApiError {
92 pub error: ApiErrorBody,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
97pub struct ApiErrorBody {
98 pub message: String,
99 #[serde(rename = "type")]
100 pub kind: String,
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub param: Option<String>,
103 #[serde(skip_serializing_if = "Option::is_none")]
104 pub code: Option<String>,
105}
106
107impl ApiError {
108 pub fn new(message: impl Into<String>, kind: impl Into<String>) -> Self {
109 ApiError {
110 error: ApiErrorBody {
111 message: message.into(),
112 kind: kind.into(),
113 param: None,
114 code: None,
115 },
116 }
117 }
118}