openkind_client/error/
api_error.rs1use std::time::Duration;
4
5use serde_json::Value;
6
7use super::envelope::{extract_code, extract_message, MAX_ERROR_BODY_LENGTH};
8
9#[derive(Debug, Clone)]
13pub struct ApiError {
14 pub status: u16,
16 pub code: Option<String>,
18 pub message: Option<String>,
20 pub body: Option<Value>,
22 pub request_id: Option<String>,
24 pub retry_after: Option<Duration>,
27 pub endpoint: Option<String>,
29}
30
31impl ApiError {
32 pub(crate) fn from_response(
33 status: u16,
34 request_id: Option<String>,
35 retry_after: Option<Duration>,
36 body: &[u8],
37 endpoint: String,
38 ) -> Self {
39 let parsed: Option<Value> = if body.is_empty() {
40 None
41 } else {
42 serde_json::from_slice(body).ok()
43 };
44 let message = extract_message(parsed.as_ref()).or_else(|| {
45 if body.is_empty() {
46 return None;
47 }
48 let raw = String::from_utf8_lossy(body);
50 let mut text = raw.chars().take(MAX_ERROR_BODY_LENGTH).collect::<String>();
51 if raw.chars().count() > MAX_ERROR_BODY_LENGTH {
52 text.push('…');
53 }
54 Some(text)
55 });
56 Self {
57 status,
58 code: extract_code(parsed.as_ref()),
59 message,
60 body: parsed,
61 request_id,
62 retry_after,
63 endpoint: Some(endpoint),
64 }
65 }
66
67 pub fn kind(&self) -> ApiErrorKind {
69 ApiErrorKind::from_status(self.status)
70 }
71
72 pub fn is_rate_limit(&self) -> bool {
74 self.status == 429
75 }
76
77 pub fn is_overloaded(&self) -> bool {
79 self.status == 529
80 }
81
82 pub fn is_retryable_later(&self) -> bool {
84 self.is_rate_limit() || self.is_overloaded()
85 }
86}
87
88impl std::fmt::Display for ApiError {
89 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90 write!(f, "{}", self.status)?;
91 if let Some(code) = &self.code {
92 write!(f, " {code}")?;
93 }
94 if let Some(message) = &self.message {
95 write!(f, ": ")?;
96 for ch in message.chars().take(super::envelope::MAX_ERROR_BODY_LENGTH) {
97 write!(f, "{ch}")?;
98 }
99 if message
100 .chars()
101 .nth(super::envelope::MAX_ERROR_BODY_LENGTH)
102 .is_some()
103 {
104 write!(f, "…")?;
105 }
106 }
107 if let Some(endpoint) = &self.endpoint {
108 write!(f, " ({endpoint})")?;
109 }
110 if let Some(request_id) = &self.request_id {
111 write!(f, " (request_id={request_id})")?;
112 }
113 Ok(())
114 }
115}
116
117impl std::error::Error for ApiError {}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122pub enum ApiErrorKind {
123 BadRequest,
125 Authentication,
127 PermissionDenied,
130 NotFound,
132 PayloadTooLarge,
134 UnprocessableEntity,
136 RateLimit,
138 Overloaded,
140 InternalServer,
142 Other,
144}
145
146impl ApiErrorKind {
147 pub fn from_status(status: u16) -> Self {
149 match status {
150 400 => Self::BadRequest,
151 401 => Self::Authentication,
152 403 => Self::PermissionDenied,
153 404 => Self::NotFound,
154 413 => Self::PayloadTooLarge,
155 422 => Self::UnprocessableEntity,
156 429 => Self::RateLimit,
157 529 => Self::Overloaded,
158 s if (500..600).contains(&s) => Self::InternalServer,
159 _ => Self::Other,
160 }
161 }
162}