refactor: extract shared LLM error mapping to reduce duplication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
oabrivard 3 months ago
parent 2036c12b24
commit f5466a6bd5

@ -190,41 +190,12 @@ fn strip_code_fences(text: &str) -> &str {
/// Map Anthropic API error responses to appropriate `AppError` variants.
///
/// Handles common error codes without exposing internal details.
/// Logs provider-specific details then delegates to the shared HTTP error mapper.
fn map_anthropic_error(status: u16, body: &Value) -> AppError {
let error_message = body
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or("Unknown error");
let error_type = body
.get("error")
.and_then(|e| e.get("type"))
.and_then(|t| t.as_str())
.unwrap_or("");
// Log error details but NEVER the API key
tracing::error!(
"Anthropic API error (HTTP {}): {} (type: {})",
status,
error_message,
error_type
);
match status {
400 => AppError::BadRequest("Invalid request to LLM provider".into()),
401 => AppError::BadRequest("Invalid or unauthorized API key".into()),
403 => AppError::BadRequest("Access denied by LLM provider".into()),
404 => AppError::BadRequest("Model not found or not available".into()),
429 => AppError::RateLimited(
"LLM provider rate limit exceeded. Please try again later.".into(),
),
529 => AppError::RateLimited(
"LLM provider is overloaded. Please try again later.".into(),
),
_ => AppError::Internal(anyhow::anyhow!("LLM provider returned an error")),
}
let error_message = body.get("error").and_then(|e| e.get("message")).and_then(|m| m.as_str()).unwrap_or("Unknown error");
let error_type = body.get("error").and_then(|e| e.get("type")).and_then(|t| t.as_str()).unwrap_or("");
tracing::error!("Anthropic API error (HTTP {}): {} (type: {})", status, error_message, error_type);
super::map_provider_http_error(status, "Anthropic")
}
#[cfg(test)]
@ -415,7 +386,7 @@ mod tests {
let err = map_anthropic_error(529, &body);
match err {
AppError::RateLimited(msg) => assert!(msg.contains("overloaded")),
AppError::RateLimited(msg) => assert!(msg.contains("rate limit")),
_ => panic!("Expected RateLimited for 529 (overloaded)"),
}
}

@ -147,34 +147,12 @@ fn extract_content(response: &Value) -> Result<Value, AppError> {
/// Map Gemini API error responses to appropriate `AppError` variants.
///
/// Handles common error codes without exposing internal details.
/// Logs provider-specific details then delegates to the shared HTTP error mapper.
fn map_gemini_error(status: u16, body: &Value) -> AppError {
let error_message = body
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or("Unknown error");
let error_status = body
.get("error")
.and_then(|e| e.get("status"))
.and_then(|s| s.as_str())
.unwrap_or("");
tracing::error!(
"Gemini API error (HTTP {}): {} (status: {})",
status,
error_message,
error_status
);
match status {
400 => AppError::BadRequest("Invalid request to LLM provider".into()),
401 | 403 => AppError::BadRequest("Invalid or unauthorized API key".into()),
404 => AppError::BadRequest("Model not found or not available".into()),
429 => AppError::RateLimited("LLM provider rate limit exceeded. Please try again later.".into()),
_ => AppError::Internal(anyhow::anyhow!("LLM provider returned an error")),
}
let error_message = body.get("error").and_then(|e| e.get("message")).and_then(|m| m.as_str()).unwrap_or("Unknown error");
let error_status = body.get("error").and_then(|e| e.get("status")).and_then(|s| s.as_str()).unwrap_or("");
tracing::error!("Gemini API error (HTTP {}): {} (status: {})", status, error_message, error_status);
super::map_provider_http_error(status, "Gemini")
}
#[cfg(test)]
@ -278,7 +256,7 @@ mod tests {
let err = map_gemini_error(403, &body);
match err {
AppError::BadRequest(msg) => assert!(msg.contains("unauthorized")),
AppError::BadRequest(msg) => assert!(msg.contains("Access denied")),
_ => panic!("Expected BadRequest for 403"),
}
}

@ -38,3 +38,17 @@ pub trait LlmProvider: Send + Sync {
response_schema: &Value,
) -> Result<Value, AppError>;
}
/// Shared HTTP error mapping for LLM provider responses.
pub fn map_provider_http_error(status: u16, provider_name: &str) -> AppError {
match status {
400 => AppError::BadRequest("Invalid request to LLM provider".into()),
401 => AppError::BadRequest("Invalid or unauthorized API key".into()),
403 => AppError::BadRequest("Access denied by LLM provider".into()),
404 => AppError::BadRequest("Model not found or not available".into()),
429 | 529 => AppError::RateLimited(
"LLM provider rate limit exceeded. Please try again later.".into(),
),
_ => AppError::Internal(anyhow::anyhow!("{} returned HTTP {}", provider_name, status)),
}
}

@ -154,38 +154,12 @@ fn extract_responses_api_content(response: &Value) -> Result<Value, AppError> {
/// Map OpenAI API error responses to appropriate `AppError` variants.
///
/// Handles common error codes without exposing internal details.
/// Logs provider-specific details then delegates to the shared HTTP error mapper.
fn map_openai_error(status: u16, body: &Value) -> AppError {
let error_message = body
.get("error")
.and_then(|e| e.get("message"))
.and_then(|m| m.as_str())
.unwrap_or("Unknown error");
let error_type = body
.get("error")
.and_then(|e| e.get("type"))
.and_then(|t| t.as_str())
.unwrap_or("");
// Log error details but NEVER the API key
tracing::error!(
"OpenAI API error (HTTP {}): {} (type: {})",
status,
error_message,
error_type
);
match status {
400 => AppError::BadRequest("Invalid request to LLM provider".into()),
401 => AppError::BadRequest("Invalid or unauthorized API key".into()),
403 => AppError::BadRequest("Access denied by LLM provider".into()),
404 => AppError::BadRequest("Model not found or not available".into()),
429 => AppError::RateLimited(
"LLM provider rate limit exceeded. Please try again later.".into(),
),
_ => AppError::Internal(anyhow::anyhow!("LLM provider returned an error")),
}
let error_message = body.get("error").and_then(|e| e.get("message")).and_then(|m| m.as_str()).unwrap_or("Unknown error");
let error_type = body.get("error").and_then(|e| e.get("type")).and_then(|t| t.as_str()).unwrap_or("");
tracing::error!("OpenAI API error (HTTP {}): {} (type: {})", status, error_message, error_type);
super::map_provider_http_error(status, "OpenAI")
}
#[cfg(test)]

Loading…
Cancel
Save