1use http::StatusCode;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct ProviderResponseError {
16 pub status: Option<StatusCode>,
18 pub body: String,
20 pub provider_request_id: Option<String>,
22 pub headers: Option<http::HeaderMap>,
25 pub code: Option<String>,
30 pub transient: Option<bool>,
33 pub refusal: bool,
36 pub route: Option<String>,
40}
41
42impl ProviderResponseError {
43 pub fn new(status: StatusCode, body: impl Into<String>) -> Self {
45 let status = Some(status);
46 Self {
47 status,
48 ..Self::without_status(body)
49 }
50 }
51
52 pub fn without_status(body: impl Into<String>) -> Self {
55 Self {
56 status: None,
57 body: body.into(),
58 provider_request_id: None,
59 headers: None,
60 code: None,
61 transient: None,
62 refusal: false,
63 route: None,
64 }
65 }
66
67 pub fn from_body(body: impl Into<String>) -> Self {
73 let mut reply = Self::without_status(body);
74 let status = reply.located(|envelope| envelope.http_status().filter(|_| envelope.nested));
75 reply.status = status.and_then(|status| StatusCode::from_u16(status).ok());
76 reply
77 }
78
79 pub fn with_refusal(mut self, refusal: bool) -> Self {
82 self.refusal = refusal;
83 self
84 }
85
86 pub fn with_status(mut self, status: Option<StatusCode>) -> Self {
90 if self.status.is_none() {
91 self.status = status;
92 }
93 self
94 }
95
96 pub fn with_code(mut self, code: Option<String>) -> Self {
98 self.code = code.filter(|code| !code.is_empty());
99 self
100 }
101
102 pub fn with_transient(mut self, transient: Option<bool>) -> Self {
105 self.transient = transient;
106 self
107 }
108
109 pub fn is_retryable(&self) -> bool {
118 if self.refusal {
119 return false;
120 }
121 if let Some(status) = self.status.filter(|status| !status.is_success()) {
122 return crate::error::retryable_status(Some(status.as_u16()));
123 }
124 let known = match &self.code {
125 Some(code) => Envelope::default().named(code).verdict(),
126 None => self.located(|envelope| envelope.verdict()),
127 };
128 known.or(self.transient).unwrap_or(false)
129 }
130
131 pub fn machine_code(&self) -> Option<String> {
135 self.code
136 .clone()
137 .or_else(|| self.located(|envelope| envelope.code()))
138 }
139
140 pub fn envelope_json(&self) -> Option<serde_json::Value> {
143 let doc: serde_json::Value = serde_json::from_str(&self.body).ok()?;
144 let envelope = envelope(&doc)?;
145 let error = envelope.error?.clone();
146 Some(match envelope.nested {
147 true => serde_json::json!({ "error": error }),
148 false => error,
149 })
150 }
151
152 fn located<T>(&self, f: impl FnOnce(Envelope<'_>) -> Option<T>) -> Option<T> {
154 let doc: serde_json::Value = serde_json::from_str(&self.body).ok()?;
155 delivered(&doc).and_then(f)
156 }
157
158 pub fn with_provider_request_id(mut self, request_id: Option<String>) -> Self {
160 self.provider_request_id = request_id.filter(|id| !id.is_empty());
161 self
162 }
163
164 pub fn with_headers(mut self, headers: Option<http::HeaderMap>) -> Self {
166 self.headers = headers;
167 self
168 }
169}
170
171impl std::fmt::Display for ProviderResponseError {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 match self.status {
174 Some(status) => write!(f, "status {status}: {}", self.body)?,
175 None => write!(f, "{}", self.body)?,
176 }
177 if let Some(request_id) = &self.provider_request_id {
179 write!(f, " (request id: {request_id})")?;
180 }
181 if let Some(route) = &self.route {
182 write!(f, " [{route}]")?;
183 }
184 Ok(())
185 }
186}
187
188impl std::error::Error for ProviderResponseError {}
189
190#[derive(serde::Serialize, serde::Deserialize)]
193#[serde(deny_unknown_fields)]
194struct ProviderResponseErrorWire {
195 status: Option<u16>,
196 body: String,
197 provider_request_id: Option<String>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
199 code: Option<String>,
200 #[serde(default, skip_serializing_if = "Option::is_none")]
201 transient: Option<bool>,
202 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
203 refusal: bool,
204}
205
206impl serde::Serialize for ProviderResponseError {
207 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
208 ProviderResponseErrorWire {
209 status: self.status.map(|status| status.as_u16()),
210 body: self.body.clone(),
211 provider_request_id: self.provider_request_id.clone(),
212 code: self.code.clone(),
213 transient: self.transient,
214 refusal: self.refusal,
215 }
216 .serialize(serializer)
217 }
218}
219
220impl<'de> serde::Deserialize<'de> for ProviderResponseError {
221 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
222 use serde::de::Error as _;
223 let wire = ProviderResponseErrorWire::deserialize(deserializer)?;
224 let status = wire
225 .status
226 .map(StatusCode::from_u16)
227 .transpose()
228 .map_err(D::Error::custom)?;
229 Ok(Self {
230 status,
231 body: wire.body,
232 provider_request_id: wire.provider_request_id,
233 headers: None,
234 code: wire.code,
235 transient: wire.transient,
236 refusal: wire.refusal,
237 route: None,
238 })
239 }
240}
241
242#[derive(Default)]
245struct Envelope<'a> {
246 error: Option<&'a serde_json::Value>,
248 name: Option<&'a str>,
249 number: Option<i64>,
250 nested: bool,
253}
254
255pub(crate) fn located_error(doc: &serde_json::Value) -> Option<(&serde_json::Value, bool)> {
258 envelope(doc).and_then(|envelope| Some((envelope.error?, envelope.nested)))
259}
260
261const TRANSIENT: &[&str] = &[
264 "overloaded",
265 "overloaded_error",
266 "api_error",
267 "rate_limit_error",
268 "rate_limit_exceeded",
269 "too_many_requests",
270 "server_error",
271 "server_is_overloaded",
272 "slow_down",
273 "unavailable",
274 "resource_exhausted",
275 "deadline_exceeded",
276 "aborted",
277 "ThrottlingException",
278 "ServiceUnavailableException",
279 "InternalServerException",
280 "ModelNotReadyException",
281 "ModelTimeoutException",
282 "ModelStreamErrorException",
283];
284
285const FINAL: &[&str] = &[
287 "insufficient_quota",
288 "quota_exceeded",
289 "billing_hard_limit_reached",
290 "context_length_exceeded",
291 "invalid_prompt",
292];
293
294fn envelope(doc: &serde_json::Value) -> Option<Envelope<'_>> {
301 use serde_json::Value;
302 const FIELDS: &[&str] = &["code", "status", "type"];
303 let object = |value: &&Value| value.as_object().is_some_and(|fields| !fields.is_empty());
304 if let Some(error) = doc.pointer("/response/error").filter(object) {
305 return Some(Envelope::of(error, FIELDS, true));
306 }
307 match doc.get("error") {
308 Some(error) if object(&error) => Some(Envelope::of(error, FIELDS, true)),
309 Some(error @ Value::String(message)) if !message.is_empty() => {
310 Some(Envelope::of(error, &[], true))
311 }
312 _ => (doc.get("type").and_then(Value::as_str) == Some("error"))
313 .then(|| Envelope::of(doc, &["code"], false)),
314 }
315}
316
317fn delivered(doc: &serde_json::Value) -> Option<Envelope<'_>> {
325 if let Some(items) = doc.as_array() {
326 return items.iter().find_map(delivered);
327 }
328 let message = doc.get("message").and_then(serde_json::Value::as_str);
329 let bare = message.is_some_and(|message| !message.is_empty())
330 && doc.get("error").is_none()
331 && doc.get("response").is_none();
332 envelope(doc).or_else(|| bare.then(|| Envelope::of(doc, &["code", "status", "type"], false)))
333}
334
335impl<'a> Envelope<'a> {
336 fn named(mut self, name: &'a str) -> Self {
337 self.name = Some(name);
338 self
339 }
340
341 fn of(error: &'a serde_json::Value, fields: &[&str], nested: bool) -> Self {
342 let values = || fields.iter().filter_map(|field| error.get(field));
343 Self {
344 error: Some(error),
345 name: values().find_map(|value| value.as_str().filter(|name| !name.is_empty())),
346 number: values().find_map(serde_json::Value::as_i64),
347 nested,
348 }
349 }
350
351 fn code(&self) -> Option<String> {
353 let number = || self.number.map(|number| number.to_string());
354 self.name.map(str::to_owned).or_else(number)
355 }
356
357 fn verdict(&self) -> Option<bool> {
362 let named = |codes: &[&str]| {
363 let name = self.name.unwrap_or_default();
364 codes.iter().any(|code| code.eq_ignore_ascii_case(name))
365 };
366 let status = || {
367 self.http_status()
368 .map(|s| crate::error::retryable_status(Some(s)))
369 };
370 (named(TRANSIENT).then_some(true))
371 .or_else(|| named(FINAL).then_some(false))
372 .or_else(status)
373 }
374
375 fn http_status(&self) -> Option<u16> {
376 let number = u16::try_from(self.number?).ok();
377 number.filter(|number| (400..=599).contains(number))
378 }
379}
380
381pub(crate) fn reported(id: Option<String>) -> Option<String> {
383 id.filter(|id| !id.is_empty())
384}
385
386pub(crate) fn json(body: Option<&str>) -> Result<Option<serde_json::Value>, serde_json::Error> {
393 body.filter(|body| !body.is_empty())
394 .map(serde_json::from_str)
395 .transpose()
396}
397
398#[cfg(test)]
399mod tests;