inference_remote_core/
classify.rs1use std::time::Duration;
10
11use inference_core::error::InferenceError;
12use inference_core::runtime::ProviderKind;
13
14pub fn classify_http_status(
18 provider: ProviderKind,
19 status: u16,
20 retry_after: Option<Duration>,
21 body: Option<String>,
22) -> InferenceError {
23 match status {
24 429 => InferenceError::RateLimited {
25 provider,
26 retry_after,
27 },
28 400 => InferenceError::BadRequest {
29 message: body.unwrap_or_else(|| "bad request".into()),
30 },
31 401 => InferenceError::Unauthorized {
32 message: body.unwrap_or_else(|| "unauthorized".into()),
33 },
34 403 => InferenceError::Forbidden {
35 message: body.unwrap_or_else(|| "forbidden".into()),
36 },
37 s if (500..600).contains(&s) => InferenceError::ServerError { status: s, body },
38 s => InferenceError::Internal(format!("unexpected status {s}: {body:?}")),
39 }
40}
41
42pub fn parse_retry_after(value: Option<&str>) -> Option<Duration> {
45 let v = value?;
46 if let Ok(secs) = v.trim().parse::<u64>() {
47 return Some(Duration::from_secs(secs));
48 }
49 chrono::DateTime::parse_from_rfc2822(v.trim()).ok().and_then(|t| {
51 let now = chrono::Utc::now().timestamp();
52 let then = t.timestamp();
53 (then > now).then(|| Duration::from_secs((then - now) as u64))
54 })
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60
61 #[test]
62 fn delta_seconds_retry_after() {
63 assert_eq!(parse_retry_after(Some("12")), Some(Duration::from_secs(12)));
64 assert_eq!(parse_retry_after(Some(" 3 ")), Some(Duration::from_secs(3)));
65 assert_eq!(parse_retry_after(None), None);
66 assert_eq!(parse_retry_after(Some("garbage")), None);
67 }
68
69 #[test]
70 fn classify_known_codes() {
71 let e = classify_http_status(ProviderKind::OpenAi, 429, Some(Duration::from_secs(2)), None);
72 assert!(matches!(e, InferenceError::RateLimited { .. }));
73 let e = classify_http_status(ProviderKind::Anthropic, 503, None, Some("oops".into()));
74 assert!(matches!(e, InferenceError::ServerError { status: 503, .. }));
75 let e = classify_http_status(ProviderKind::Gemini, 401, None, None);
76 assert!(matches!(e, InferenceError::Unauthorized { .. }));
77 }
78}