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