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
19/// Represents an error creating or using a [Credentials].
20///
21/// The Google Cloud client libraries may experience problems creating
22/// credentials and/or using them. An example of problems creating credentials
23/// may be a badly formatted or missing key file. An example of problems using
24/// credentials may be a temporary failure to retrieve or create
25/// [access tokens]. Note that the latter kind of errors may happen even after
26/// 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 err = CredentialsError::from_str(
37/// true, "simulated retryable error while trying to create credentials");
38/// assert!(err.is_retryable());
39/// assert!(format!("{err}").contains("simulated retryable error"));
40/// ```
41///
42/// [access tokens]: https://cloud.google.com/docs/authentication/token-types
43/// [Credentials]: https://docs.rs/google-cloud-auth/latest/google_cloud_auth/credentials/struct.Credential.html
44#[derive(Clone, Debug)]
45pub struct CredentialsError {
46 /// A boolean value indicating whether the error is retryable.
47 ///
48 /// If `true`, the operation that resulted in this error might succeed upon
49 /// retry.
50 is_retryable: bool,
51
52 /// The underlying source of the error.
53 ///
54 /// This provides more specific information about the cause of the failure.
55 source: CredentialsErrorImpl,
56}
57
58#[derive(Clone, Debug)]
59enum CredentialsErrorImpl {
60 SimpleMessage(String),
61 Source(Arc<dyn Error + Send + Sync>),
62}
63
64impl CredentialsError {
65 /// Creates a new `CredentialsError`.
66 ///
67 /// This function is only intended for use in the client libraries
68 /// implementation. Application may use this in mocks, though we do not
69 /// recommend that you write tests for specific error cases. Most tests
70 /// should use the generic [Error][crate::error::Error] type.
71 ///
72 /// # Example
73 /// ```
74 /// # use google_cloud_gax::error::CredentialsError;
75 /// # use google_cloud_gax::error::Error;
76 /// let err = CredentialsError::new(
77 /// false, Error::other("simulated non-retryable error while trying to create credentials"));
78 /// assert!(!err.is_retryable());
79 /// assert!(format!("{err}").contains("simulated non-retryable error"));
80 /// ```
81 /// # Parameters
82 /// * `is_retryable` - A boolean indicating whether the error is retryable.
83 /// * `source` - The underlying error that caused the auth failure.
84 pub fn new<T: Error + Send + Sync + 'static>(is_retryable: bool, source: T) -> Self {
85 CredentialsError {
86 is_retryable,
87 source: CredentialsErrorImpl::Source(Arc::new(source)),
88 }
89 }
90
91 /// Creates a new `CredentialsError`.
92 ///
93 /// This function is only intended for use in the client libraries
94 /// implementation. Application may use this in mocks, though we do not
95 /// recommend that you write tests for specific error cases. Most tests
96 /// should use the generic [Error][crate::error::Error] type.
97 ///
98 /// # Example
99 /// ```
100 /// # use google_cloud_gax::error::CredentialsError;
101 /// let err = CredentialsError::from_str(
102 /// true, "simulated retryable error while trying to create credentials");
103 /// assert!(err.is_retryable());
104 /// assert!(format!("{err}").contains("simulated retryable error"));
105 /// ```
106 ///
107 /// # Parameters
108 /// * `is_retryable` - A boolean indicating whether the error is retryable.
109 /// * `message` - The underlying error that caused the auth failure.
110 pub fn from_str<T: Into<String>>(is_retryable: bool, message: T) -> Self {
111 CredentialsError::new(
112 is_retryable,
113 CredentialsErrorImpl::SimpleMessage(message.into()),
114 )
115 }
116
117 /// Returns `true` if the error is retryable; otherwise returns `false`.
118 pub fn is_retryable(&self) -> bool {
119 self.is_retryable
120 }
121}
122
123impl std::error::Error for CredentialsErrorImpl {
124 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
125 match &self {
126 CredentialsErrorImpl::SimpleMessage(_) => None,
127 CredentialsErrorImpl::Source(source) => Some(source),
128 }
129 }
130}
131
132impl Display for CredentialsErrorImpl {
133 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
134 match &self {
135 CredentialsErrorImpl::SimpleMessage(message) => write!(f, "{}", message),
136 CredentialsErrorImpl::Source(source) => write!(f, "{}", source),
137 }
138 }
139}
140
141impl std::error::Error for CredentialsError {
142 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
143 self.source.source()
144 }
145}
146
147const RETRYABLE_MSG: &str = "but future attempts may succeed";
148const NON_RETRYABLE_MSG: &str = "and future attempts will not succeed";
149
150impl Display for CredentialsError {
151 /// Formats the error message to include retryability and source.
152 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
153 let msg = if self.is_retryable {
154 RETRYABLE_MSG
155 } else {
156 NON_RETRYABLE_MSG
157 };
158 write!(
159 f,
160 "cannot create access token, {}, source:{}",
161 msg, self.source
162 )
163 }
164}
165
166#[cfg(test)]
167mod test {
168 use super::*;
169 use std::collections::HashMap;
170 use test_case::test_case;
171
172 #[test_case(true)]
173 #[test_case(false)]
174 fn new(retryable: bool) {
175 let source = crate::error::HttpError::new(
176 404,
177 HashMap::new(),
178 Some(bytes::Bytes::from_static("test-only".as_bytes())),
179 );
180 let got = CredentialsError::new(retryable, source);
181 assert_eq!(got.is_retryable(), retryable, "{got}");
182 assert!(got.source().is_some(), "{got}");
183 assert!(format!("{got}").contains("test-only"), "{got}");
184 }
185
186 #[test_case(true)]
187 #[test_case(false)]
188 fn from_str(retryable: bool) {
189 let got = CredentialsError::from_str(retryable, "test-only");
190 assert_eq!(got.is_retryable(), retryable, "{got}");
191 assert!(got.source().is_some(), "{got}");
192 assert!(format!("{got}").contains("test-only"), "{got}");
193 }
194
195 #[test]
196 fn fmt() {
197 let e = CredentialsError::from_str(true, "test-only-err-123");
198 let got = format!("{e}");
199 assert!(got.contains("test-only-err-123"), "{got}");
200 assert!(got.contains(RETRYABLE_MSG), "{got}");
201
202 let e = CredentialsError::from_str(false, "test-only-err-123");
203 let got = format!("{e}");
204 assert!(got.contains("test-only-err-123"), "{got}");
205 assert!(got.contains(NON_RETRYABLE_MSG), "{got}");
206 }
207
208 #[test]
209 fn source() {
210 let got = CredentialsErrorImpl::SimpleMessage("test-only".into());
211 assert!(got.source().is_none(), "{got}");
212 let got = CredentialsErrorImpl::Source(Arc::new(crate::error::Error::other("test-only")));
213 assert!(got.source().is_some(), "{got}");
214 }
215}