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}