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