google_cloud_auth/
errors.rs1use http::StatusCode;
18use std::error::Error;
19
20pub use gax::error::CredentialsError;
21
22pub(crate) fn from_http_error(err: reqwest::Error, msg: &str) -> CredentialsError {
23 let transient = self::is_retryable(&err);
24 CredentialsError::new(transient, msg, err)
25}
26
27pub(crate) async fn from_http_response(response: reqwest::Response, msg: &str) -> CredentialsError {
28 let err = response
29 .error_for_status_ref()
30 .expect_err("this function is only called on errors");
31 let body = response.text().await;
32 let transient = crate::errors::is_retryable(&err);
33 match body {
34 Err(e) => CredentialsError::new(transient, msg, e),
35 Ok(b) => CredentialsError::new(transient, format!("{msg}, body=<{b}>"), err),
36 }
37}
38
39pub(crate) fn non_retryable<T: Error + Send + Sync + 'static>(source: T) -> CredentialsError {
41 CredentialsError::from_source(false, source)
42}
43
44pub(crate) fn non_retryable_from_str<T: Into<String>>(message: T) -> CredentialsError {
45 CredentialsError::from_msg(false, message)
46}
47
48fn is_retryable(err: &reqwest::Error) -> bool {
49 if err.is_connect() {
54 return true;
55 }
56 match err.status() {
57 Some(code) => is_retryable_code(code),
58 None => false,
59 }
60}
61
62fn is_retryable_code(code: StatusCode) -> bool {
63 match code {
64 StatusCode::INTERNAL_SERVER_ERROR
67 | StatusCode::SERVICE_UNAVAILABLE
68 | StatusCode::REQUEST_TIMEOUT
69 | StatusCode::TOO_MANY_REQUESTS => true,
70 _ => false,
71 }
72}
73
74#[cfg(test)]
75mod test {
76 use super::*;
77 use std::num::ParseIntError;
78 use test_case::test_case;
79
80 #[test_case(StatusCode::INTERNAL_SERVER_ERROR)]
81 #[test_case(StatusCode::SERVICE_UNAVAILABLE)]
82 #[test_case(StatusCode::REQUEST_TIMEOUT)]
83 #[test_case(StatusCode::TOO_MANY_REQUESTS)]
84 fn retryable(c: StatusCode) {
85 assert!(is_retryable_code(c));
86 }
87
88 #[test_case(StatusCode::NOT_FOUND)]
89 #[test_case(StatusCode::UNAUTHORIZED)]
90 #[test_case(StatusCode::BAD_REQUEST)]
91 #[test_case(StatusCode::BAD_GATEWAY)]
92 #[test_case(StatusCode::PRECONDITION_FAILED)]
93 fn non_retryable(c: StatusCode) {
94 assert!(!is_retryable_code(c));
95 }
96
97 #[test]
98 fn helpers() {
99 let e = super::non_retryable_from_str("test-only-err-123");
100 assert!(!e.is_transient(), "{e}");
101 let got = format!("{e}");
102 assert!(got.contains("test-only-err-123"), "{got}");
103
104 let input = "NaN".parse::<u32>().unwrap_err();
105 let e = super::non_retryable(input.clone());
106 assert!(!e.is_transient(), "{e:?}");
107 let source = e.source().and_then(|e| e.downcast_ref::<ParseIntError>());
108 assert!(matches!(source, Some(ParseIntError { .. })), "{e:?}");
109 }
110}