1use http::StatusCode;
15use serde::Deserialize;
16use std::fmt;
17
18#[derive(Debug, thiserror::Error)]
20pub enum MesaError {
21 #[error("API error {status}: [{code}] {message}")]
23 Api {
24 status: StatusCode,
26 code: ApiErrorCode,
28 message: String,
30 details: serde_json::Value,
32 },
33 #[error("HTTP client error: {0}")]
35 HttpClient(#[from] HttpClientError),
36 #[error("Serialization error: {0}")]
38 Serialization(#[from] serde_json::Error),
39 #[error("Request failed after {attempts} attempts: {last_error}")]
41 RetriesExhausted {
42 attempts: u32,
44 last_error: Box<Self>,
46 },
47}
48
49impl MesaError {
50 #[must_use]
52 pub fn is_retryable(&self) -> bool {
53 match self {
54 Self::Api { status, .. } => status.as_u16() == 429 || status.is_server_error(),
55 Self::HttpClient(HttpClientError::Timeout | HttpClientError::Connection(_)) => true,
56 Self::HttpClient(HttpClientError::Other(_))
57 | Self::Serialization(_)
58 | Self::RetriesExhausted { .. } => false,
59 }
60 }
61
62 #[must_use]
64 pub fn status(&self) -> Option<StatusCode> {
65 match self {
66 Self::Api { status, .. } => Some(*status),
67 Self::HttpClient(_) | Self::Serialization(_) | Self::RetriesExhausted { .. } => None,
68 }
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq)]
74pub enum ApiErrorCode {
75 BadRequest,
77 Unauthorized,
79 Forbidden,
81 NotFound,
83 NotAcceptable,
85 Conflict,
87 InternalServerError,
89 Unknown(String),
91}
92
93impl ApiErrorCode {
94 #[must_use]
96 pub fn from_code(s: &str) -> Self {
97 match s {
98 "bad_request" => Self::BadRequest,
99 "unauthorized" => Self::Unauthorized,
100 "forbidden" => Self::Forbidden,
101 "not_found" => Self::NotFound,
102 "not_acceptable" => Self::NotAcceptable,
103 "conflict" => Self::Conflict,
104 "internal_server_error" => Self::InternalServerError,
105 other => Self::Unknown(other.to_owned()),
106 }
107 }
108}
109
110impl fmt::Display for ApiErrorCode {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 Self::BadRequest => f.write_str("bad_request"),
114 Self::Unauthorized => f.write_str("unauthorized"),
115 Self::Forbidden => f.write_str("forbidden"),
116 Self::NotFound => f.write_str("not_found"),
117 Self::NotAcceptable => f.write_str("not_acceptable"),
118 Self::Conflict => f.write_str("conflict"),
119 Self::InternalServerError => f.write_str("internal_server_error"),
120 Self::Unknown(code) => f.write_str(code),
121 }
122 }
123}
124
125#[derive(Debug, thiserror::Error)]
137pub enum HttpClientError {
138 #[error("Request timed out")]
140 Timeout,
141 #[error("Connection error: {0}")]
143 Connection(String),
144 #[error("{0}")]
146 Other(#[from] Box<dyn std::error::Error + Send + Sync>),
147}
148
149#[derive(Debug, Deserialize)]
153pub(crate) struct ApiErrorResponse {
154 pub error: ApiErrorBody,
155}
156
157#[derive(Debug, Deserialize)]
159pub(crate) struct ApiErrorBody {
160 pub code: String,
161 #[serde(default)]
162 pub message: String,
163 #[serde(default = "default_details")]
164 pub details: serde_json::Value,
165}
166
167fn default_details() -> serde_json::Value {
168 serde_json::Value::Null
169}