use aimux_core::{AiMuxError, ApiCallError};
pub struct ErrorStructure {
pub message_path: &'static [&'static str],
pub type_path: &'static [&'static str],
}
pub const DEFAULT_ERROR_STRUCTURE: ErrorStructure = ErrorStructure {
message_path: &["error", "message"],
type_path: &["error", "type"],
};
#[must_use]
pub fn parse_provider_error(status: u16, body: &str, structure: &ErrorStructure) -> AiMuxError {
let mut message = String::new();
let mut provider_code = None;
if let Ok(val) = serde_json::from_str::<serde_json::Value>(body) {
let mut current = &val;
for key in structure.message_path {
if let Some(v) = current.get(key) {
current = v;
} else {
current = &val;
break;
}
}
if let Some(msg) = current.as_str() {
message = msg.to_string();
} else if !body.is_empty() {
message = body.to_string();
}
let mut current = &val;
for key in structure.type_path {
if let Some(v) = current.get(key) {
current = v;
} else {
current = &val;
break;
}
}
if let Some(t) = current.as_str() {
provider_code = Some(t.to_string());
}
} else if !body.is_empty() {
message = body.to_string();
}
let response_body = (!body.is_empty()).then(|| body.to_string());
error_for_status(status, provider_code, message, None, response_body)
}
#[must_use]
pub fn error_for_status(
status: u16,
provider_code: Option<String>,
message: String,
retry_after_ms: Option<u64>,
response_body: Option<String>,
) -> AiMuxError {
AiMuxError::ApiCall(ApiCallError {
status_code: Some(status),
provider_code,
message,
response_body,
retry_after_ms,
is_retryable: matches!(status, 408 | 409 | 429) || status >= 500,
..Default::default()
})
}
#[must_use]
pub fn parse_stream_error(err_obj: &serde_json::Value) -> AiMuxError {
let str_field = |k: &str| {
err_obj
.get(k)
.and_then(|v| v.as_str())
.map(std::string::ToString::to_string)
};
let message = str_field("message").unwrap_or_else(|| "Unknown stream error".to_string());
let code = err_obj.get("code");
let status = code
.and_then(serde_json::Value::as_u64)
.filter(|c| (100..=599).contains(c))
.map(|c| c as u16);
let provider_code = code
.and_then(|v| v.as_str())
.map(std::string::ToString::to_string)
.or_else(|| str_field("type"));
let retry_after_ms = err_obj
.get("retry_after_ms")
.and_then(serde_json::Value::as_u64)
.or_else(|| {
err_obj
.get("retry_after")
.and_then(serde_json::Value::as_u64)
.map(|s| s * 1000)
});
match status {
Some(status) => error_for_status(
status,
provider_code,
message,
retry_after_ms,
Some(err_obj.to_string()),
),
None => AiMuxError::ApiCall(ApiCallError {
provider_code,
message,
response_body: Some(err_obj.to_string()),
retry_after_ms,
..Default::default()
}),
}
}