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}