1use serde_json::Value;
2
3#[derive(Debug, Default)]
4pub struct ErrorFields {
5 pub message: Option<String>,
6 pub code: Option<String>,
7}
8
9const MESSAGE_KEYS: &[&str] = &[
10 "message",
11 "error_description",
12 "error",
13 "reason",
14 "detail",
15 "details",
16];
17const CODE_KEYS: &[&str] = &["code", "error_code", "status"];
18
19pub fn parse_error_fields(body: &str) -> ErrorFields {
22 let mut fields = ErrorFields::default();
23 let Ok(v) = serde_json::from_str::<Value>(body) else {
24 return fields;
25 };
26 fields.message = first_string(&v, MESSAGE_KEYS);
27 fields.code = first_string(&v, CODE_KEYS);
28 fields
29}
30
31fn first_string(v: &Value, keys: &[&str]) -> Option<String> {
32 let obj = v.as_object()?;
33 for k in keys {
34 match obj.get(*k) {
35 Some(Value::String(s)) => return Some(s.clone()),
36 Some(Value::Number(n)) => return Some(n.to_string()),
37 _ => {}
38 }
39 }
40 if let Some(Value::Object(err)) = obj.get("error") {
42 for k in keys {
43 match err.get(*k) {
44 Some(Value::String(s)) => return Some(s.clone()),
45 Some(Value::Number(n)) => return Some(n.to_string()),
46 _ => {}
47 }
48 }
49 }
50 None
51}
52
53#[cfg(test)]
54mod tests {
55 use super::parse_error_fields;
56
57 #[test]
58 fn extracts_message_and_code_flat() {
59 let f = parse_error_fields(r#"{"message":"Not found","code":"NF404"}"#);
60 assert_eq!(f.message.as_deref(), Some("Not found"));
61 assert_eq!(f.code.as_deref(), Some("NF404"));
62 }
63
64 #[test]
65 fn extracts_from_nested_error_object() {
66 let f = parse_error_fields(r#"{"error":{"message":"bad token","code":"E1"}}"#);
67 assert_eq!(f.message.as_deref(), Some("bad token"));
68 assert_eq!(f.code.as_deref(), Some("E1"));
69 }
70
71 #[test]
72 fn numeric_code_becomes_string() {
73 let f = parse_error_fields(r#"{"error":"boom","status":500}"#);
74 assert_eq!(f.message.as_deref(), Some("boom"));
75 assert_eq!(f.code.as_deref(), Some("500"));
76 }
77
78 #[test]
79 fn non_json_is_empty() {
80 let f = parse_error_fields("<html>500</html>");
81 assert!(f.message.is_none());
82 assert!(f.code.is_none());
83 }
84}