nblm_core/
error.rs

1use reqwest::StatusCode;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum Error {
6    #[error("token provider error: {0}")]
7    TokenProvider(String),
8    #[error("invalid endpoint configuration: {0}")]
9    Endpoint(String),
10    #[error("request error: {0}")]
11    Request(#[from] reqwest::Error),
12    #[error("http error {status}: {message}")]
13    Http {
14        status: StatusCode,
15        message: String,
16        body: String,
17    },
18    #[error("url parse error: {0}")]
19    Url(#[from] url::ParseError),
20    #[error("validation error: {0}")]
21    Validation(String),
22}
23
24pub type Result<T> = std::result::Result<T, Error>;
25
26impl Error {
27    pub fn http(status: StatusCode, body: impl Into<String>) -> Self {
28        let body = body.into();
29        let message = extract_error_message(&body).unwrap_or_else(|| body.clone());
30        Self::Http {
31            status,
32            message,
33            body,
34        }
35    }
36
37    pub fn validation(message: impl Into<String>) -> Self {
38        Self::Validation(message.into())
39    }
40}
41
42fn extract_error_message(body: &str) -> Option<String> {
43    let json: serde_json::Value = serde_json::from_str(body).ok()?;
44    json.get("error")
45        .and_then(|err| err.get("message"))
46        .and_then(|msg| msg.as_str())
47        .map(|s| s.to_string())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn extract_error_message_from_gcp_response() {
56        let body = r#"{"error":{"message":"Not Found"}}"#;
57        assert_eq!(extract_error_message(body), Some("Not Found".to_string()));
58    }
59
60    #[test]
61    fn extract_error_message_missing() {
62        assert_eq!(extract_error_message("{}"), None);
63        assert_eq!(extract_error_message("invalid"), None);
64    }
65
66    #[test]
67    fn http_error_uses_message_field_if_available() {
68        let e = Error::http(
69            StatusCode::TOO_MANY_REQUESTS,
70            r#"{"error":{"message":"Too Many Requests"}}"#,
71        );
72        match e {
73            Error::Http {
74                message,
75                body,
76                status,
77            } => {
78                assert_eq!(status, StatusCode::TOO_MANY_REQUESTS);
79                assert_eq!(message, "Too Many Requests");
80                assert!(body.contains("Too Many Requests"));
81            }
82            _ => panic!("expected Error::Http"),
83        }
84    }
85
86    #[test]
87    fn http_error_uses_body_when_no_message() {
88        let e = Error::http(StatusCode::BAD_REQUEST, "plain error text");
89        match e {
90            Error::Http { message, body, .. } => {
91                assert_eq!(message, "plain error text");
92                assert_eq!(body, "plain error text");
93            }
94            _ => panic!("expected Error::Http"),
95        }
96    }
97}