use std::fmt;
const QUOTED: usize = 240;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ProviderError {
Unreachable {
url: String,
reason: String,
},
Refused {
url: String,
status: u16,
said: String,
},
Stopped {
said: String,
},
Unreadable {
what: String,
},
}
impl fmt::Display for ProviderError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Unreachable { url, reason } => write!(formatter, "{url}: {reason}"),
Self::Refused { url, status, said } => write!(formatter, "{url}: {status} {said}"),
Self::Stopped { said } => formatter.write_str(said),
Self::Unreadable { what } => formatter.write_str(what),
}
}
}
impl ProviderError {
#[must_use]
pub fn without(self, secret: &str) -> Self {
let hide = |text: String| if secret.is_empty() { text } else { text.replace(secret, "…") };
match self {
Self::Unreachable { url, reason } => Self::Unreachable { url: hide(url), reason: hide(reason) },
Self::Refused { url, status, said } => Self::Refused { url: hide(url), status, said: hide(said) },
Self::Stopped { said } => Self::Stopped { said: hide(said) },
Self::Unreadable { what } => Self::Unreadable { what: hide(what) },
}
}
}
pub(crate) fn complaint(body: &str) -> String {
let Ok(value) = serde_json::from_str::<serde_json::Value>(body) else { return shorten(body) };
let error = value.get("error");
let said = [
value.get("metadata").and_then(|metadata| metadata.get("raw")),
error.and_then(|error| error.get("metadata")).and_then(|metadata| metadata.get("raw")),
error.and_then(|error| error.get("message")),
error.filter(|error| error.is_string()),
value.get("message"),
]
.into_iter()
.flatten()
.find_map(|said| said.as_str().filter(|said| !said.trim().is_empty()))
.unwrap_or(body);
shorten(said)
}
fn shorten(said: &str) -> String {
let trimmed = said.trim();
match trimmed.char_indices().nth(QUOTED) {
Some((end, _)) => format!("{}…", &trimmed[..end]),
None => trimmed.to_owned(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_sentence_is_taken_out_of_the_shapes_providers_use() {
let anthropic = r#"{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}"#;
assert_eq!(complaint(anthropic), "invalid x-api-key");
assert_eq!(complaint(r#"{"error":"model not found"}"#), "model not found");
assert_eq!(complaint(r#"{"message":"rate limited"}"#), "rate limited");
assert_eq!(complaint(" Bad Gateway \n"), "Bad Gateway");
}
#[test]
fn a_long_page_is_cut_with_an_ellipsis() {
let page = "x".repeat(1000);
let said = complaint(&page);
assert_eq!(said.chars().count(), QUOTED + 1);
assert!(said.ends_with('…'));
}
}