1use std::fmt;
4
5const QUOTED: usize = 240;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum ProviderError {
15 Unreachable {
17 url: String,
19 reason: String,
21 },
22 Refused {
24 url: String,
26 status: u16,
28 said: String,
30 },
31 Stopped {
33 said: String,
35 },
36 Unreadable {
38 what: String,
40 },
41}
42
43impl fmt::Display for ProviderError {
44 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
45 match self {
46 Self::Unreachable { url, reason } => write!(formatter, "{url}: {reason}"),
47 Self::Refused { url, status, said } => write!(formatter, "{url}: {status} {said}"),
48 Self::Stopped { said } => formatter.write_str(said),
49 Self::Unreadable { what } => formatter.write_str(what),
50 }
51 }
52}
53
54pub(crate) fn complaint(body: &str) -> String {
61 let Ok(value) = serde_json::from_str::<serde_json::Value>(body) else { return shorten(body) };
62 let error = value.get("error");
63 let said = [
64 value.get("metadata").and_then(|metadata| metadata.get("raw")),
65 error.and_then(|error| error.get("metadata")).and_then(|metadata| metadata.get("raw")),
66 error.and_then(|error| error.get("message")),
67 error.filter(|error| error.is_string()),
68 value.get("message"),
69 ]
70 .into_iter()
71 .flatten()
72 .find_map(|said| said.as_str().filter(|said| !said.trim().is_empty()))
73 .unwrap_or(body);
74 shorten(said)
75}
76
77fn shorten(said: &str) -> String {
79 let trimmed = said.trim();
80 match trimmed.char_indices().nth(QUOTED) {
81 Some((end, _)) => format!("{}…", &trimmed[..end]),
82 None => trimmed.to_owned(),
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn the_sentence_is_taken_out_of_the_shapes_providers_use() {
92 let anthropic = r#"{"type":"error","error":{"type":"authentication_error","message":"invalid x-api-key"}}"#;
93 assert_eq!(complaint(anthropic), "invalid x-api-key");
94 assert_eq!(complaint(r#"{"error":"model not found"}"#), "model not found");
95 assert_eq!(complaint(r#"{"message":"rate limited"}"#), "rate limited");
96 assert_eq!(complaint(" Bad Gateway \n"), "Bad Gateway");
97 }
98
99 #[test]
100 fn a_long_page_is_cut_with_an_ellipsis() {
101 let page = "x".repeat(1000);
102 let said = complaint(&page);
103 assert_eq!(said.chars().count(), QUOTED + 1);
104 assert!(said.ends_with('…'));
105 }
106}