google_cloud_gax/error/credentials.rs
1// Copyright 2025 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::error::Error;
16use std::fmt::{Debug, Display, Formatter, Result};
17use std::sync::Arc;
18
19type ArcError = Arc<dyn Error + Send + Sync>;
20
21/// Represents an error using [Credentials].
22///
23/// The Google Cloud client libraries may experience problems using credentials
24/// to create the necessary authentication headers. For example, a temporary
25/// failure to retrieve or create [access tokens]. Note that these failures may
26/// happen even after the credentials files are successfully loaded and parsed.
27///
28/// Applications rarely need to create instances of this error type. The
29/// exception might be when testing application code, where the application is
30/// mocking a client library behavior. Such tests are extremely rare, most
31/// applications should only work with the [Error][crate::error::Error] type.
32///
33/// # Example
34/// ```
35/// use google_cloud_gax::error::CredentialsError;
36/// let mut headers = fetch_headers();
37/// while let Err(e) = &headers {
38/// if e.is_transient() {
39/// headers = fetch_headers();
40/// }
41/// }
42///
43/// fn fetch_headers() -> Result<http::HeaderMap, CredentialsError> {
44/// # Ok(http::HeaderMap::new())
45/// }
46/// ```
47///
48/// [access tokens]: https://cloud.google.com/docs/authentication/token-types
49/// [Credentials]: https://docs.rs/google-cloud-auth/latest/google_cloud_auth/credentials/struct.Credential.html
50#[derive(Clone, Debug)]
51pub struct CredentialsError {
52 is_transient: bool,
53 message: Option<String>,
54 source: Option<ArcError>,
55}
56
57impl CredentialsError {
58 /// Creates a new `CredentialsError`.
59 ///
60 /// This function is only intended for use in the client libraries
61 /// implementation. Application may use this in mocks, though we do not
62 /// recommend that you write tests for specific error cases. Most tests
63 /// should use the generic [Error][crate::error::Error] type.
64 ///
65 /// # Example
66 /// ```
67 /// use google_cloud_gax::error::CredentialsError;
68 /// let mut headers = fetch_headers();
69 /// while let Err(e) = &headers {
70 /// if e.is_transient() {
71 /// headers = fetch_headers();
72 /// }
73 /// }
74 ///
75 /// fn fetch_headers() -> Result<http::HeaderMap, CredentialsError> {
76 /// # Ok(http::HeaderMap::new())
77 /// }
78 /// ```
79 ///
80 /// # Parameters
81 /// * `is_transient` - if true, the operation may succeed in future attempts.
82 /// * `source` - The underlying error that caused the auth failure.
83 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
84 pub fn from_source<T: Error + Send + Sync + 'static>(is_transient: bool, source: T) -> Self {
85 CredentialsError {
86 is_transient,
87 source: Some(Arc::new(source)),
88 message: None,
89 }
90 }
91
92 /// Creates a new `CredentialsError`.
93 ///
94 /// This function is only intended for use in the client libraries
95 /// implementation. Application may use this in mocks, though we do not
96 /// recommend that you write tests for specific error cases. Most tests
97 /// should use the generic [Error][crate::error::Error] type.
98 ///
99 /// # Example
100 /// ```
101 /// # use google_cloud_gax::error::CredentialsError;
102 /// let err = CredentialsError::from_msg(
103 /// true, "simulated retryable error while trying to create credentials");
104 /// assert!(err.is_transient());
105 /// assert!(format!("{err}").contains("simulated retryable error"));
106 /// ```
107 ///
108 /// # Parameters
109 /// * `is_transient` - if true, the operation may succeed in future attempts.
110 /// * `message` - The underlying error that caused the auth failure.
111 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
112 pub fn from_msg<T: Into<String>>(is_transient: bool, message: T) -> Self {
113 CredentialsError {
114 is_transient,
115 message: Some(message.into()),
116 source: None,
117 }
118 }
119
120 /// Creates a new `CredentialsError`.
121 ///
122 /// This function is only intended for use in the client libraries
123 /// implementation. Application may use this in mocks, though we do not
124 /// recommend that you write tests for specific error cases. Most tests
125 /// should use the generic [Error][crate::error::Error] type.
126 ///
127 /// # Example
128 /// ```
129 /// # use google_cloud_gax::error::CredentialsError;
130 /// let source = std::io::Error::new(std::io::ErrorKind::ConnectionRefused, "cannot connect");
131 /// let err = CredentialsError::new(
132 /// true,
133 /// "simulated retryable error while trying to create credentials",
134 /// source);
135 /// assert!(err.is_transient());
136 /// assert!(format!("{err}").contains("simulated retryable error"));
137 /// ```
138 ///
139 /// # Parameters
140 /// * `is_transient` - if true, the operation may succeed in future attempts.
141 /// * `message` - The underlying error that caused the auth failure.
142 #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
143 pub fn new<M, S>(is_transient: bool, message: M, source: S) -> Self
144 where
145 M: Into<String>,
146 S: std::error::Error + Send + Sync + 'static,
147 {
148 CredentialsError {
149 is_transient,
150 message: Some(message.into()),
151 source: Some(Arc::new(source)),
152 }
153 }
154
155 /// Returns true if the error is transient and may succeed in future attempts.
156 ///
157 /// # Example
158 /// ```
159 /// # use google_cloud_gax::error::CredentialsError;
160 /// let mut headers = fetch_headers();
161 /// while let Err(e) = &headers {
162 /// if e.is_transient() {
163 /// headers = fetch_headers();
164 /// }
165 /// }
166 ///
167 /// fn fetch_headers() -> Result<http::HeaderMap, CredentialsError> {
168 /// # Ok(http::HeaderMap::new())
169 /// }
170 /// ```
171 pub fn is_transient(&self) -> bool {
172 self.is_transient
173 }
174}
175
176impl std::error::Error for CredentialsError {
177 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
178 self.source
179 .as_ref()
180 .map(|arc| arc.as_ref() as &(dyn std::error::Error + 'static))
181 }
182}
183
184const TRANSIENT_MSG: &str = "but future attempts may succeed";
185const PERMANENT_MSG: &str = "and future attempts will not succeed";
186
187impl Display for CredentialsError {
188 /// Formats the error message to include retryability and source.
189 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
190 let msg = if self.is_transient {
191 TRANSIENT_MSG
192 } else {
193 PERMANENT_MSG
194 };
195 match &self.message {
196 None => write!(f, "cannot create auth headers {msg}"),
197 Some(m) => write!(f, "{m} {msg}"),
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205 use test_case::test_case;
206
207 #[test_case(true)]
208 #[test_case(false)]
209 fn from_source(transient: bool) {
210 let source = wkt::TimestampError::OutOfRange;
211 let got = CredentialsError::from_source(transient, source);
212 assert_eq!(got.is_transient(), transient, "{got:?}");
213 assert!(
214 got.source()
215 .and_then(|e| e.downcast_ref::<wkt::TimestampError>())
216 .is_some(),
217 "{got:?}"
218 );
219 assert!(
220 got.to_string().contains("cannot create auth headers"),
221 "{got:?}"
222 );
223 }
224
225 #[test_case(true)]
226 #[test_case(false)]
227 fn from_str(transient: bool) {
228 let got = CredentialsError::from_msg(transient, "test-only");
229 assert_eq!(got.is_transient(), transient, "{got:?}");
230 assert!(got.source().is_none(), "{got:?}");
231 assert!(got.to_string().contains("test-only"), "{got}");
232 }
233
234 #[test_case(true)]
235 #[test_case(false)]
236 fn new(transient: bool) {
237 let source = wkt::TimestampError::OutOfRange;
238 let got = CredentialsError::new(transient, "additional information", source);
239 assert_eq!(got.is_transient(), transient, "{got:?}");
240 assert!(
241 got.source()
242 .and_then(|e| e.downcast_ref::<wkt::TimestampError>())
243 .is_some(),
244 "{got:?}"
245 );
246 assert!(
247 got.to_string().contains("additional information"),
248 "{got:?}"
249 );
250 }
251
252 #[test]
253 fn fmt() {
254 let e = CredentialsError::from_msg(true, "test-only-err-123");
255 let got = format!("{e}");
256 assert!(got.contains("test-only-err-123"), "{got}");
257 assert!(got.contains(TRANSIENT_MSG), "{got}");
258
259 let e = CredentialsError::from_msg(false, "test-only-err-123");
260 let got = format!("{e}");
261 assert!(got.contains("test-only-err-123"), "{got}");
262 assert!(got.contains(PERMANENT_MSG), "{got}");
263 }
264}