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}