google_cloud_gax/error/
core_error.rs

1// Copyright 2024 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 super::CredentialsError;
16use super::rpc::Status;
17use http::HeaderMap;
18use std::error::Error as StdError;
19
20type BoxError = Box<dyn StdError + Send + Sync>;
21
22/// The core error returned by all client libraries.
23///
24/// The client libraries report errors from multiple sources. For example, the
25/// service may return an error, the transport may be unable to create the
26/// necessary connection to make a request, the request may timeout before a
27/// response is received, the retry policy may be exhausted, or the library may
28/// be unable to format the request due to invalid or missing application
29/// application inputs.
30///
31/// Most applications will just return the error or log it, without any further
32/// action. However, some applications may need to interrogate the error
33/// details. This type offers a series of predicates to determine the error
34/// kind. The type also offers accessors to query the most common error details.
35/// Applications can query the error [source][std::error::Error::source] for
36/// deeper information.
37///
38/// # Example
39/// ```
40/// use google_cloud_gax::error::Error;
41/// match example_function() {
42///     Err(e) if matches!(e.status(), Some(_)) => {
43///         println!("service error {e}, debug using {:?}", e.status().unwrap());
44///     },
45///     Err(e) if e.is_timeout() => { println!("not enough time {e}"); },
46///     Err(e) => { println!("some other error {e}"); },
47///     Ok(_) => { println!("success, how boring"); },
48/// }
49///
50/// fn example_function() -> Result<String, Error> {
51///     // ... details omitted ...
52///     # use google_cloud_gax::error::rpc::{Code, Status};
53///     # Err(Error::service(Status::default().set_code(Code::NotFound).set_message("NOT FOUND")))
54/// }
55/// ```
56#[derive(Debug)]
57pub struct Error {
58    kind: ErrorKind,
59    source: Option<BoxError>,
60}
61
62impl Error {
63    /// Creates an error with the information returned by Google Cloud services.
64    ///
65    /// # Example
66    /// ```
67    /// use google_cloud_gax::error::Error;
68    /// use google_cloud_gax::error::rpc::{Code, Status};
69    /// let status = Status::default().set_code(Code::NotFound).set_message("NOT FOUND");
70    /// let error = Error::service(status.clone());
71    /// assert_eq!(error.status(), Some(&status));
72    /// ```
73    pub fn service(status: Status) -> Self {
74        let details = ServiceDetails {
75            status,
76            status_code: None,
77            headers: None,
78        };
79        Self {
80            kind: ErrorKind::Service(Box::new(details)),
81            source: None,
82        }
83    }
84
85    /// Creates an error representing a timeout.
86    ///
87    /// # Example
88    /// ```
89    /// use std::error::Error as _;
90    /// use google_cloud_gax::error::Error;
91    /// let error = Error::timeout("simulated timeout");
92    /// assert!(error.is_timeout());
93    /// assert!(error.source().is_some());
94    /// ```
95    pub fn timeout<T: Into<BoxError>>(source: T) -> Self {
96        Self {
97            kind: ErrorKind::Timeout,
98            source: Some(source.into()),
99        }
100    }
101
102    /// The request could not be completed before its deadline.
103    ///
104    /// This is always a client-side generated error. Note that the request may
105    /// or may not have started, and it may or may not complete in the service.
106    /// If the request mutates any state in the service, it may or may not be
107    /// safe to attempt the request again.
108    ///
109    /// # Troubleshooting
110    ///
111    /// The most common cause of this problem is setting a timeout value that is
112    /// based on the observed latency when the service is not under load.
113    /// Consider increasing the timeout value to handle temporary latency
114    /// increases too.
115    ///
116    /// It could also indicate a congestion in the network, a service outage, or
117    /// a service that is under load and will take time to scale up.
118    pub fn is_timeout(&self) -> bool {
119        matches!(self.kind, ErrorKind::Timeout)
120    }
121
122    /// Creates an error representing an exhausted policy.
123    ///
124    /// # Example
125    /// ```
126    /// use std::error::Error as _;
127    /// use google_cloud_gax::error::Error;
128    /// let error = Error::exhausted("too many retry attempts");
129    /// assert!(error.is_exhausted());
130    /// assert!(error.source().is_some());
131    /// ```
132    pub fn exhausted<T: Into<BoxError>>(source: T) -> Self {
133        Self {
134            kind: ErrorKind::Exhausted,
135            source: Some(source.into()),
136        }
137    }
138
139    /// The request could not complete be before the retry policy expired.
140    ///
141    /// This is always a client-side generated error, but it may be the result
142    /// of multiple errors received from the service.
143    ///
144    /// # Troubleshooting
145    ///
146    /// The most common cause of this problem is a transient problem that lasts
147    /// longer than your retry policy. For example, your retry policy may
148    /// effectively be exhausted after a few seconds, but some services may take
149    /// minutes to recover.
150    ///
151    /// If your application can tolerate longer recovery times then extend the
152    /// retry policy. Otherwise consider recovery at a higher level, such as
153    /// seeking human intervention, switching the workload to a different
154    /// location, failing the batch job and starting from a previous checkpoint,
155    /// or even presenting an error to the application user.
156    pub fn is_exhausted(&self) -> bool {
157        matches!(self.kind, ErrorKind::Exhausted)
158    }
159
160    /// Not part of the public API, subject to change without notice.
161    ///
162    /// Creates an error representing a deserialization problem.
163    ///
164    /// Applications should have no need to use this function. The exception
165    /// could be mocks, but this error is too rare to merit mocks. If you are
166    /// writing a mock that extracts values from [wkt::Any], consider using
167    /// `.expect()` calls instead.
168    ///
169    /// # Example
170    /// ```
171    /// use std::error::Error as _;
172    /// use google_cloud_gax::error::Error;
173    /// let error = Error::deser("simulated problem");
174    /// assert!(error.is_deserialization());
175    /// assert!(error.source().is_some());
176    /// ```
177    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
178    pub fn deser<T: Into<BoxError>>(source: T) -> Self {
179        Self {
180            kind: ErrorKind::Deserialization,
181            source: Some(source.into()),
182        }
183    }
184
185    /// The response could not be deserialized.
186    ///
187    /// This is always a client-side generated error. Note that the request may
188    /// or may not have started, and it may or may not complete in the service.
189    /// If the request mutates any state in the service, it may or may not be
190    /// safe to attempt the request again.
191    ///
192    /// # Troubleshooting
193    ///
194    /// The most common cause for deserialization problems are bugs in the
195    /// client library and (rarely) bugs in the service.
196    ///
197    /// When using gRPC services, and if the response includes a [wkt::Any]
198    /// field, the client library may not be able to handle unknown types within
199    /// the `Any`. In all services we know of, this should not happen, but it is
200    /// impossible to prepare the client library for breaking changes in the
201    /// service. Upgrading to the latest version of the client library may be
202    /// the only possible fix.
203    ///
204    /// Beyond this issue with `Any`, while the client libraries are designed to
205    /// handle all valid responses, including unknown fields and unknown
206    /// enumeration values, it is possible that the client library has a bug.
207    /// Please [open an issue] if you run in to this problem. Include any
208    /// instructions on how to reproduce the problem. If you cannot use, or
209    /// prefer not to use, GitHub to discuss this problem, then contact
210    /// [Google Cloud support].
211    ///
212    /// [open an issue]: https://github.com/googleapis/google-cloud-rust/issues/new/choose
213    /// [Google Cloud support]: https://cloud.google.com/support
214    pub fn is_deserialization(&self) -> bool {
215        matches!(self.kind, ErrorKind::Deserialization)
216    }
217
218    /// Not part of the public API, subject to change without notice.
219    ///
220    /// Creates an error representing a serialization problem.
221    ///
222    /// Applications should have no need to use this function. The exception
223    /// could be mocks, but this error is too rare to merit mocks. If you are
224    /// writing a mock that stores values into [wkt::Any], consider using
225    /// `.expect()` calls instead.
226    ///
227    /// # Example
228    /// ```
229    /// use std::error::Error as _;
230    /// use google_cloud_gax::error::Error;
231    /// let error = Error::ser("simulated problem");
232    /// assert!(error.is_serialization());
233    /// assert!(error.source().is_some());
234    /// ```
235    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
236    pub fn ser<T: Into<BoxError>>(source: T) -> Self {
237        Self {
238            kind: ErrorKind::Serialization,
239            source: Some(source.into()),
240        }
241    }
242
243    /// The request could not be serialized.
244    ///
245    /// This is always a client-side generated error, generated before the
246    /// request is made. This error is never transient: the serialization is
247    /// deterministic (modulo out of memory conditions), and will fail on future
248    /// attempts with the same input data.
249    ///
250    /// # Troubleshooting
251    ///
252    /// Most client libraries use HTTP and JSON as the transport, though some
253    /// client libraries use gRPC for some, or all RPCs.
254    ///
255    /// The most common cause for serialization problems is using an unknown
256    /// enum value name with a gRPC-based RPC. gRPC requires integer enum
257    /// values, while JSON accepts both. The client libraries convert **known**
258    /// enum value names to their integer representation, but unknown values
259    /// cannot be sent over gRPC. Verify the enum value is valid, and if so:
260    /// - try using an integer value instead of the enum name, or
261    /// - upgrade the client library: newer versions should include the new
262    ///   value.
263    ///
264    /// In all other cases please [open an issue]. While we do not expect these
265    /// problems to be common, we would like to hear if they are so we can
266    /// prevent them. If you cannot use a public issue tracker, contact
267    /// [Google Cloud support].
268    ///
269    /// A less common cause for serialization problems may be an out of memory
270    /// condition, or any other runtime error. Use `format!("{:?}", ...)` to
271    /// examine the error as it should include the original problem.
272    ///
273    /// Finally, sending a [wkt::Any] with a gRPC-based client is unsupported.
274    /// As of this writing, no client libraries sends `Any` via gRPC, but this
275    /// could be a problem in the future.
276    ///
277    /// [open an issue]: https://github.com/googleapis/google-cloud-rust/issues/new/choose
278    /// [Google Cloud support]: https://cloud.google.com/support
279    pub fn is_serialization(&self) -> bool {
280        matches!(self.kind, ErrorKind::Serialization)
281    }
282
283    /// The [Status] payload associated with this error.
284    ///
285    /// # Examples
286    /// ```
287    /// use google_cloud_gax::error::{Error, rpc::{Code, Status}};
288    /// let error = Error::service(Status::default().set_code(Code::NotFound));
289    /// if let Some(status) = error.status() {
290    ///     if status.code == Code::NotFound {
291    ///         println!("cannot find the thing, more details in {:?}", status.details);
292    ///     }
293    /// }
294    /// ```
295    ///
296    /// Google Cloud services return a detailed `Status` message including a
297    /// numeric code for the error type, a human-readable message, and a
298    /// sequence of details which may include localization messages, or more
299    /// information about what caused the failure.
300    ///
301    /// See [AIP-193] for background information about the error model in Google
302    /// Cloud services.
303    ///
304    /// # Troubleshooting
305    ///
306    /// As this error type is typically created by the service, troubleshooting
307    /// this problem typically involves reading the service documentation to
308    /// root cause the problem.
309    ///
310    /// Some services include additional details about the error, sometimes
311    /// including what fields are missing or have bad values in the
312    /// [Status::details] vector. The `std::fmt::Debug` format will include
313    /// such details.
314    ///
315    /// With that said, review the status [Code][crate::error::rpc::Code]
316    /// documentation. The description of the status codes provides a good
317    /// starting point.
318    ///
319    /// [AIP-193]: https://google.aip.dev/193
320    pub fn status(&self) -> Option<&Status> {
321        match &self.kind {
322            ErrorKind::Service(d) => Some(&d.as_ref().status),
323            _ => None,
324        }
325    }
326
327    /// The HTTP status code, if any, associated with this error.
328    ///
329    /// # Example
330    /// ```
331    /// use google_cloud_gax::error::{Error, rpc::{Code, Status}};
332    /// let e = search_for_thing("the thing");
333    /// if let Some(code) = e.http_status_code() {
334    ///     if code == 404 {
335    ///         println!("cannot find the thing, more details in {e}");
336    ///     }
337    /// }
338    ///
339    /// fn search_for_thing(name: &str) -> Error {
340    ///     # Error::http(400, http::HeaderMap::new(), bytes::Bytes::from_static(b"NOT FOUND"))
341    /// }
342    /// ```
343    ///
344    /// Sometimes the error is generated before it reaches any Google Cloud
345    /// service. For example, your proxy or the Google load balancers may
346    /// generate errors without the detailed payload described in [AIP-193].
347    /// In such cases the client library returns the status code, headers, and
348    /// http payload.
349    ///
350    /// Note that `http_status_code()`, `http_headers()`, `http_payload()`, and
351    /// `status()` are represented as different fields, because they may be
352    /// set in some errors but not others.
353    ///
354    /// [AIP-193]: https://google.aip.dev/193
355    pub fn http_status_code(&self) -> Option<u16> {
356        match &self.kind {
357            ErrorKind::Transport(d) => d.as_ref().status_code,
358            ErrorKind::Service(d) => d.as_ref().status_code,
359            _ => None,
360        }
361    }
362
363    /// The headers, if any, associated with this error.
364    ///
365    /// # Example
366    /// ```
367    /// use google_cloud_gax::error::{Error, rpc::{Code, Status}};
368    /// let e = search_for_thing("the thing");
369    /// if let Some(headers) = e.http_headers() {
370    ///     if let Some(id) = headers.get("x-guploader-uploadid") {
371    ///         println!("this can speed up troubleshooting for the Google Cloud Storage support team {id:?}");
372    ///     }
373    /// }
374    ///
375    /// fn search_for_thing(name: &str) -> Error {
376    ///     # let mut map = http::HeaderMap::new();
377    ///     # map.insert("x-guploader-uploadid", http::HeaderValue::from_static("placeholder"));
378    ///     # Error::http(400, map, bytes::Bytes::from_static(b"NOT FOUND"))
379    /// }
380    /// ```
381    ///
382    /// Sometimes the error may have headers associated with it. Some services
383    /// include information useful for troubleshooting in the response headers.
384    /// Over gRPC this is called `metadata`, the Google Cloud client libraries
385    /// for Rust normalize this to a [http::HeaderMap].
386    ///
387    /// Many errors do not have this information, e.g. errors detected before
388    /// the request is set, or timeouts. Some RPCs also return "partial"
389    /// errors, which do not include such information.
390    ///
391    /// Note that `http_status_code()`, `http_headers()`, `http_payload()`, and
392    /// `status()` are represented as different fields, because they may be
393    /// set in some errors but not others.
394    pub fn http_headers(&self) -> Option<&http::HeaderMap> {
395        match &self.kind {
396            ErrorKind::Transport(d) => d.as_ref().headers.as_ref(),
397            ErrorKind::Service(d) => d.as_ref().headers.as_ref(),
398            _ => None,
399        }
400    }
401
402    /// The payload, if any, associated with this error.
403    ///
404    /// # Example
405    /// ```
406    /// use google_cloud_gax::error::{Error, rpc::{Code, Status}};
407    /// let e = search_for_thing("the thing");
408    /// if let Some(payload) = e.http_payload() {
409    ///    println!("the error included some extra payload {payload:?}");
410    /// }
411    ///
412    /// fn search_for_thing(name: &str) -> Error {
413    ///     # Error::http(400, http::HeaderMap::new(), bytes::Bytes::from_static(b"NOT FOUND"))
414    /// }
415    /// ```
416    ///
417    /// Sometimes the error may contain a payload that is useful for
418    /// troubleshooting.
419    ///
420    /// Note that `http_status_code()`, `http_headers()`, `http_payload()`, and
421    /// `status()` are represented as different fields, because they may be
422    /// set in some errors but not others.
423    pub fn http_payload(&self) -> Option<&bytes::Bytes> {
424        match &self.kind {
425            ErrorKind::Transport(d) => d.payload.as_ref(),
426            _ => None,
427        }
428    }
429
430    /// Not part of the public API, subject to change without notice.
431    ///
432    /// Create service errors including transport metadata.
433    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
434    pub fn service_with_http_metadata(
435        status: Status,
436        status_code: Option<u16>,
437        headers: Option<http::HeaderMap>,
438    ) -> Self {
439        Self::service_full(status, status_code, headers, None)
440    }
441
442    /// Not part of the public API, subject to change without notice.
443    ///
444    /// Create service errors including transport metadata.
445    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
446    pub fn service_full(
447        status: Status,
448        status_code: Option<u16>,
449        headers: Option<http::HeaderMap>,
450        source: Option<BoxError>,
451    ) -> Self {
452        let details = ServiceDetails {
453            status_code,
454            headers,
455            status,
456        };
457        let kind = ErrorKind::Service(Box::new(details));
458        Self { kind, source }
459    }
460
461    /// Not part of the public API, subject to change without notice.
462    ///
463    /// Cannot find a valid HTTP binding to make the request.
464    ///
465    /// This indicates the request is missing required parameters, or the
466    /// required parameters do not have a valid format.
467    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
468    pub fn binding<T: Into<BoxError>>(source: T) -> Self {
469        Self {
470            kind: ErrorKind::Binding,
471            source: Some(source.into()),
472        }
473    }
474
475    // TODO(#2316) - update the troubleshooting text.
476    /// Not part of the public API, subject to change without notice.
477    ///
478    /// If true, the request was missing required parameters or the parameters
479    /// did not match any of the expected formats.
480    ///
481    /// # Troubleshooting
482    ///
483    /// Typically this indicates a problem in the application. A required field
484    /// in the request builder was not initialized or the format of the field
485    /// does not match the expectations.
486    ///
487    /// We are working to improve the messages in these errors to make them
488    /// self-explanatory, until bug [#2316] is fixed, please consult the service
489    /// REST API documentation.
490    ///
491    /// [#2316]: https://github.com/googleapis/google-cloud-rust/issues/2316
492    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
493    pub fn is_binding(&self) -> bool {
494        matches!(&self.kind, ErrorKind::Binding)
495    }
496
497    /// Not part of the public API, subject to change without notice.
498    ///
499    /// Cannot create the authentication headers.
500    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
501    pub fn authentication(source: CredentialsError) -> Self {
502        Self {
503            kind: ErrorKind::Authentication,
504            source: Some(source.into()),
505        }
506    }
507
508    /// Not part of the public API, subject to change without notice.
509    ///
510    /// Could not create the authentication headers before sending the request.
511    ///
512    /// # Troubleshooting
513    ///
514    /// Typically this indicates a misconfigured authentication environment for
515    /// your application. Very rarely, this may indicate a failure to contact
516    /// the HTTP services used to create [access tokens].
517    ///
518    /// If you are using the default [Credentials], the
519    /// [Authenticate for using client libraries] guide includes good
520    /// information on how to set up your environment for authentication.
521    ///
522    /// If you have configured custom `Credentials`, consult the documentation
523    /// for the specific credential type you used.
524    ///
525    /// [Credentials]: https://docs.rs/google-cloud-auth/latest/google_cloud_auth/credentials/struct.Credentials.html
526    /// [Authenticate for using client libraries]: https://cloud.google.com/docs/authentication/client-libraries
527    /// [access tokens]: https://cloud.google.com/docs/authentication/token-types
528    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
529    pub fn is_authentication(&self) -> bool {
530        matches!(self.kind, ErrorKind::Authentication)
531    }
532
533    /// Not part of the public API, subject to change without notice.
534    ///
535    /// A problem reported by the transport layer.
536    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
537    pub fn http(status_code: u16, headers: HeaderMap, payload: bytes::Bytes) -> Self {
538        let details = TransportDetails {
539            status_code: Some(status_code),
540            headers: Some(headers),
541            payload: Some(payload),
542        };
543        let kind = ErrorKind::Transport(Box::new(details));
544        Self { kind, source: None }
545    }
546
547    /// Not part of the public API, subject to change without notice.
548    ///
549    /// A problem in the transport layer without a full HTTP response.
550    ///
551    /// Examples include: a broken connection after the request is sent, or a
552    /// any HTTP error that did not include a status code or other headers.
553    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
554    pub fn io<T: Into<BoxError>>(source: T) -> Self {
555        let details = TransportDetails {
556            status_code: None,
557            headers: None,
558            payload: None,
559        };
560        Self {
561            kind: ErrorKind::Transport(Box::new(details)),
562            source: Some(source.into()),
563        }
564    }
565
566    /// Not part of the public API, subject to change without notice.
567    ///
568    /// A problem in the transport layer without a full HTTP response.
569    ///
570    /// Examples include read or write problems, and broken connections.
571    ///
572    /// # Troubleshooting
573    ///
574    /// This indicates a problem completing the request. This type of error is
575    /// rare, but includes crashes and restarts on proxies and load balancers.
576    /// It could indicate a bug in the client library, if it tried to use a
577    /// stale connection that had been closed by the service.
578    ///
579    /// Most often, the solution is to use the right retry policy. This may
580    /// involve changing your request to be idempotent, or configuring the
581    /// policy to retry non-idempotent failures.
582    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
583    pub fn is_io(&self) -> bool {
584        matches!(
585        &self.kind,
586        ErrorKind::Transport(d) if matches!(**d, TransportDetails {
587            status_code: None,
588            headers: None,
589            payload: None,
590            ..
591        }))
592    }
593
594    /// Not part of the public API, subject to change without notice.
595    ///
596    /// A problem connecting with the endpoint.
597    ///
598    /// Examples include DNS errors and broken connections.
599    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
600    pub fn connect<T: Into<BoxError>>(source: T) -> Self {
601        Self {
602            kind: ErrorKind::Connect,
603            source: Some(source.into()),
604        }
605    }
606
607    /// Not part of the public API, subject to change without notice.
608    ///
609    /// A problem connecting with the endpoint.
610    ///
611    /// Examples include DNS errors and broken connections.
612    ///
613    /// # Troubleshooting
614    ///
615    /// This indicates a problem starting the request. It always occurs before a
616    /// request is made, so it is always safe to retry, even if the request is
617    /// not idempotent. However, retrying does not indicate the request will
618    /// ever succeed (e.g. if a network is permanently down).
619    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
620    pub fn is_connect(&self) -> bool {
621        matches!(&self.kind, ErrorKind::Connect)
622    }
623
624    /// Not part of the public API, subject to change without notice.
625    ///
626    /// A problem reported by the transport layer.
627    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
628    pub fn transport<T: Into<BoxError>>(headers: HeaderMap, source: T) -> Self {
629        let details = TransportDetails {
630            headers: Some(headers),
631            status_code: None,
632            payload: None,
633        };
634        Self {
635            kind: ErrorKind::Transport(Box::new(details)),
636            source: Some(source.into()),
637        }
638    }
639
640    /// Not part of the public API, subject to change without notice.
641    ///
642    /// A problem in the transport layer.
643    ///
644    /// Examples include errors in a proxy, load balancer, or other network
645    /// element generated before the service is able to send a full response.
646    ///
647    /// # Troubleshooting
648    ///
649    /// This indicates that the request did not reach the service. Most commonly
650    /// the problem are invalid or mismatched request parameters that route
651    /// the request to the wrong backend.
652    ///
653    /// In this regard, this is similar to the [is_binding][Error::is_binding]
654    /// errors, except that the client library was unable to detect the problem
655    /// locally.
656    ///
657    /// An increasingly common cause for this error is trying to use regional
658    /// resources (e.g. `projects/my-project/locations/us-central1/secrets/my-secret`)
659    /// while using the default, non-regional endpoint. Some services require
660    /// using regional endpoints (e.g.
661    /// `https://secretmanager.us-central1.rep.googleapis.com`) to access such
662    /// resources.
663    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
664    pub fn is_transport(&self) -> bool {
665        matches!(&self.kind, ErrorKind::Transport { .. })
666    }
667
668    /// The error was generated before the RPC started and is transient.
669    #[cfg_attr(not(feature = "_internal-semver"), doc(hidden))]
670    pub fn is_transient_and_before_rpc(&self) -> bool {
671        match &self.kind {
672            ErrorKind::Connect => true,
673            ErrorKind::Authentication => self
674                .source
675                .as_ref()
676                .and_then(|e| e.downcast_ref::<CredentialsError>())
677                .map(|e| e.is_transient())
678                .unwrap_or(false),
679            _ => false,
680        }
681    }
682}
683
684impl std::fmt::Display for Error {
685    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
686        match (&self.kind, &self.source) {
687            (ErrorKind::Binding, Some(e)) => {
688                write!(f, "cannot find a matching binding to send the request: {e}")
689            }
690            (ErrorKind::Serialization, Some(e)) => write!(f, "cannot serialize the request {e}"),
691            (ErrorKind::Deserialization, Some(e)) => {
692                write!(f, "cannot deserialize the response {e}")
693            }
694            (ErrorKind::Authentication, Some(e)) => {
695                write!(f, "cannot create the authentication headers {e}")
696            }
697            (ErrorKind::Timeout, Some(e)) => {
698                write!(f, "the request exceeded the request deadline {e}")
699            }
700            (ErrorKind::Exhausted, Some(e)) => {
701                write!(f, "{e}")
702            }
703            (ErrorKind::Connect, Some(e)) => {
704                write!(f, "cannot connect to endpoint: {e}")
705            }
706            (ErrorKind::Transport(details), _) => details.display(self.source(), f),
707            (ErrorKind::Service(d), _) => {
708                write!(
709                    f,
710                    "the service reports an error with code {} described as: {}",
711                    d.status.code, d.status.message
712                )
713            }
714            (_, None) => unreachable!("no constructor allows this"),
715        }
716    }
717}
718
719impl std::error::Error for Error {
720    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
721        self.source
722            .as_ref()
723            .map(|e| e.as_ref() as &dyn std::error::Error)
724    }
725}
726
727/// The type of error held by an [Error] instance.
728#[derive(Debug)]
729enum ErrorKind {
730    Binding,
731    Serialization,
732    Deserialization,
733    Authentication,
734    Timeout,
735    Exhausted,
736    Connect,
737    Transport(Box<TransportDetails>),
738    Service(Box<ServiceDetails>),
739}
740
741#[derive(Debug)]
742struct TransportDetails {
743    status_code: Option<u16>,
744    headers: Option<HeaderMap>,
745    payload: Option<bytes::Bytes>,
746}
747
748impl TransportDetails {
749    fn display(
750        &self,
751        source: Option<&(dyn StdError + 'static)>,
752        f: &mut std::fmt::Formatter<'_>,
753    ) -> std::fmt::Result {
754        match (source, &self) {
755            (
756                _,
757                TransportDetails {
758                    status_code: Some(code),
759                    payload: Some(p),
760                    ..
761                },
762            ) => {
763                if let Ok(message) = std::str::from_utf8(p.as_ref()) {
764                    write!(f, "the HTTP transport reports a [{code}] error: {message}")
765                } else {
766                    write!(f, "the HTTP transport reports a [{code}] error: {p:?}")
767                }
768            }
769            (Some(source), _) => {
770                write!(f, "the transport reports an error: {source}")
771            }
772            (None, _) => unreachable!("no Error constructor allows this"),
773        }
774    }
775}
776
777#[derive(Debug)]
778struct ServiceDetails {
779    status_code: Option<u16>,
780    headers: Option<HeaderMap>,
781    status: Status,
782}
783
784#[cfg(test)]
785mod tests {
786    use super::*;
787    use crate::error::CredentialsError;
788    use crate::error::rpc::Code;
789    use std::error::Error as StdError;
790
791    #[test]
792    fn service() {
793        let status = Status::default()
794            .set_code(Code::NotFound)
795            .set_message("NOT FOUND");
796        let error = Error::service(status.clone());
797        assert!(error.source().is_none(), "{error:?}");
798        assert_eq!(error.status(), Some(&status));
799        assert!(error.to_string().contains("NOT FOUND"), "{error}");
800        assert!(error.to_string().contains(Code::NotFound.name()), "{error}");
801        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
802    }
803
804    #[test]
805    fn timeout() {
806        let source = wkt::TimestampError::OutOfRange;
807        let error = Error::timeout(source);
808        assert!(error.is_timeout(), "{error:?}");
809        assert!(error.source().is_some(), "{error:?}");
810        let got = error
811            .source()
812            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
813        assert!(
814            matches!(got, Some(wkt::TimestampError::OutOfRange)),
815            "{error:?}"
816        );
817        let source = wkt::TimestampError::OutOfRange;
818        assert!(error.to_string().contains(&source.to_string()), "{error}");
819        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
820
821        assert!(error.http_headers().is_none(), "{error:?}");
822        assert!(error.http_status_code().is_none(), "{error:?}");
823        assert!(error.http_payload().is_none(), "{error:?}");
824        assert!(error.status().is_none(), "{error:?}");
825    }
826
827    #[test]
828    fn exhausted() {
829        let source = wkt::TimestampError::OutOfRange;
830        let error = Error::exhausted(source);
831        assert!(error.is_exhausted(), "{error:?}");
832        assert!(error.source().is_some(), "{error:?}");
833        let got = error
834            .source()
835            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
836        assert!(
837            matches!(got, Some(wkt::TimestampError::OutOfRange)),
838            "{error:?}"
839        );
840        let source = wkt::TimestampError::OutOfRange;
841        assert!(error.to_string().contains(&source.to_string()), "{error}");
842        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
843
844        assert!(error.http_headers().is_none(), "{error:?}");
845        assert!(error.http_status_code().is_none(), "{error:?}");
846        assert!(error.http_payload().is_none(), "{error:?}");
847        assert!(error.status().is_none(), "{error:?}");
848    }
849
850    #[test]
851    fn serialization() {
852        let source = wkt::TimestampError::OutOfRange;
853        let error = Error::deser(source);
854        assert!(error.is_deserialization(), "{error:?}");
855        let got = error
856            .source()
857            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
858        assert!(
859            matches!(got, Some(wkt::TimestampError::OutOfRange)),
860            "{error:?}"
861        );
862        let source = wkt::TimestampError::OutOfRange;
863        assert!(error.to_string().contains(&source.to_string()), "{error}");
864        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
865    }
866
867    #[test]
868    fn connect() {
869        let source = wkt::TimestampError::OutOfRange;
870        let error = Error::connect(source);
871        assert!(error.is_connect(), "{error:?}");
872        assert!(error.source().is_some(), "{error:?}");
873        let got = error
874            .source()
875            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
876        assert!(
877            matches!(got, Some(wkt::TimestampError::OutOfRange)),
878            "{error:?}"
879        );
880        let source = wkt::TimestampError::OutOfRange;
881        assert!(error.to_string().contains(&source.to_string()), "{error}");
882        assert!(error.is_transient_and_before_rpc(), "{error:?}");
883
884        assert!(error.http_headers().is_none(), "{error:?}");
885        assert!(error.http_status_code().is_none(), "{error:?}");
886        assert!(error.http_payload().is_none(), "{error:?}");
887        assert!(error.status().is_none(), "{error:?}");
888    }
889
890    #[test]
891    fn service_with_http_metadata() {
892        let status = Status::default()
893            .set_code(Code::NotFound)
894            .set_message("NOT FOUND");
895        let status_code = 404_u16;
896        let headers = {
897            let mut headers = http::HeaderMap::new();
898            headers.insert(
899                "content-type",
900                http::HeaderValue::from_static("application/json"),
901            );
902            headers
903        };
904        let error = Error::service_with_http_metadata(
905            status.clone(),
906            Some(status_code),
907            Some(headers.clone()),
908        );
909        assert_eq!(error.status(), Some(&status));
910        assert!(error.to_string().contains("NOT FOUND"), "{error}");
911        assert!(error.to_string().contains(Code::NotFound.name()), "{error}");
912        assert_eq!(error.http_status_code(), Some(status_code));
913        assert_eq!(error.http_headers(), Some(&headers));
914        assert!(error.http_payload().is_none(), "{error:?}");
915        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
916    }
917
918    #[test]
919    fn service_full() {
920        let status = Status::default()
921            .set_code(Code::NotFound)
922            .set_message("NOT FOUND");
923        let status_code = 404_u16;
924        let headers = {
925            let mut headers = http::HeaderMap::new();
926            headers.insert(
927                "content-type",
928                http::HeaderValue::from_static("application/json"),
929            );
930            headers
931        };
932        let error = Error::service_full(
933            status.clone(),
934            Some(status_code),
935            Some(headers.clone()),
936            Some(Box::new(wkt::TimestampError::OutOfRange)),
937        );
938        assert_eq!(error.status(), Some(&status));
939        assert!(error.to_string().contains("NOT FOUND"), "{error}");
940        assert!(error.to_string().contains(Code::NotFound.name()), "{error}");
941        assert_eq!(error.http_status_code(), Some(status_code));
942        assert_eq!(error.http_headers(), Some(&headers));
943        assert!(error.http_payload().is_none(), "{error:?}");
944        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
945        assert!(error.source().is_some(), "{error:?}");
946        let got = error
947            .source()
948            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
949        assert!(
950            matches!(got, Some(wkt::TimestampError::OutOfRange)),
951            "{error:?}"
952        );
953    }
954
955    #[test]
956    fn binding() {
957        let source = wkt::TimestampError::OutOfRange;
958        let error = Error::binding(source);
959        assert!(error.is_binding(), "{error:?}");
960        assert!(error.source().is_some(), "{error:?}");
961        let got = error
962            .source()
963            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
964        assert!(
965            matches!(got, Some(wkt::TimestampError::OutOfRange)),
966            "{error:?}"
967        );
968        let source = wkt::TimestampError::OutOfRange;
969        assert!(error.to_string().contains(&source.to_string()), "{error}");
970        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
971
972        assert!(error.status().is_none(), "{error:?}");
973        assert!(error.http_status_code().is_none(), "{error:?}");
974        assert!(error.http_headers().is_none(), "{error:?}");
975        assert!(error.http_payload().is_none(), "{error:?}");
976    }
977
978    #[test]
979    fn ser() {
980        let source = wkt::TimestampError::OutOfRange;
981        let error = Error::ser(source);
982        assert!(error.is_serialization(), "{error:?}");
983        assert!(error.source().is_some(), "{error:?}");
984        let got = error
985            .source()
986            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
987        assert!(
988            matches!(got, Some(wkt::TimestampError::OutOfRange)),
989            "{error:?}"
990        );
991        let source = wkt::TimestampError::OutOfRange;
992        assert!(error.to_string().contains(&source.to_string()), "{error}");
993        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
994    }
995
996    #[test]
997    fn auth_transient() {
998        let source = CredentialsError::from_msg(true, "test-message");
999        let error = Error::authentication(source);
1000        assert!(error.is_authentication(), "{error:?}");
1001        assert!(error.source().is_some(), "{error:?}");
1002        let got = error
1003            .source()
1004            .and_then(|e| e.downcast_ref::<CredentialsError>());
1005        assert!(matches!(got, Some(c) if c.is_transient()), "{error:?}");
1006        assert!(error.to_string().contains("test-message"), "{error}");
1007        assert!(error.is_transient_and_before_rpc(), "{error:?}");
1008    }
1009
1010    #[test]
1011    fn auth_not_transient() {
1012        let source = CredentialsError::from_msg(false, "test-message");
1013        let error = Error::authentication(source);
1014        assert!(error.is_authentication(), "{error:?}");
1015        assert!(error.source().is_some(), "{error:?}");
1016        let got = error
1017            .source()
1018            .and_then(|e| e.downcast_ref::<CredentialsError>());
1019        assert!(matches!(got, Some(c) if !c.is_transient()), "{error:?}");
1020        assert!(error.to_string().contains("test-message"), "{error}");
1021        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
1022    }
1023
1024    #[test]
1025    fn http() {
1026        let status_code = 404_u16;
1027        let headers = {
1028            let mut headers = http::HeaderMap::new();
1029            headers.insert(
1030                "content-type",
1031                http::HeaderValue::from_static("application/json"),
1032            );
1033            headers
1034        };
1035        let payload = bytes::Bytes::from_static(b"NOT FOUND");
1036        let error = Error::http(status_code, headers.clone(), payload.clone());
1037        assert!(error.is_transport(), "{error:?}");
1038        assert!(!error.is_io(), "{error:?}");
1039        assert!(error.source().is_none(), "{error:?}");
1040        assert!(error.status().is_none(), "{error:?}");
1041        assert!(error.to_string().contains("NOT FOUND"), "{error}");
1042        assert!(error.to_string().contains("404"), "{error}");
1043        assert_eq!(error.http_status_code(), Some(status_code));
1044        assert_eq!(error.http_headers(), Some(&headers));
1045        assert_eq!(error.http_payload(), Some(&payload));
1046        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
1047    }
1048
1049    #[test]
1050    fn http_binary() {
1051        let status_code = 404_u16;
1052        let headers = {
1053            let mut headers = http::HeaderMap::new();
1054            headers.insert(
1055                "content-type",
1056                http::HeaderValue::from_static("application/json"),
1057            );
1058            headers
1059        };
1060        let payload = bytes::Bytes::from_static(&[0xFF, 0xFF]);
1061        let error = Error::http(status_code, headers.clone(), payload.clone());
1062        assert!(error.is_transport(), "{error:?}");
1063        assert!(!error.is_io(), "{error:?}");
1064        assert!(error.source().is_none(), "{error:?}");
1065        assert!(error.status().is_none(), "{error:?}");
1066        assert!(
1067            error.to_string().contains(&format! {"{payload:?}"}),
1068            "{error}"
1069        );
1070        assert!(error.to_string().contains("404"), "{error}");
1071        assert_eq!(error.http_status_code(), Some(status_code));
1072        assert_eq!(error.http_headers(), Some(&headers));
1073        assert_eq!(error.http_payload(), Some(&payload));
1074        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
1075    }
1076
1077    #[test]
1078    fn io() {
1079        let source = wkt::TimestampError::OutOfRange;
1080        let error = Error::io(source);
1081        assert!(error.is_transport(), "{error:?}");
1082        assert!(error.is_io(), "{error:?}");
1083        assert!(error.status().is_none(), "{error:?}");
1084        let got = error
1085            .source()
1086            .and_then(|e| e.downcast_ref::<wkt::TimestampError>());
1087        assert!(
1088            matches!(got, Some(wkt::TimestampError::OutOfRange)),
1089            "{error:?}"
1090        );
1091        let source = wkt::TimestampError::OutOfRange;
1092        assert!(error.to_string().contains(&source.to_string()), "{error}");
1093        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
1094    }
1095
1096    #[test]
1097    fn transport() {
1098        let headers = {
1099            let mut headers = http::HeaderMap::new();
1100            headers.insert(
1101                "content-type",
1102                http::HeaderValue::from_static("application/json"),
1103            );
1104            headers
1105        };
1106        let source = wkt::TimestampError::OutOfRange;
1107        let error = Error::transport(headers.clone(), source);
1108        assert!(error.is_transport(), "{error:?}");
1109        assert!(!error.is_io(), "{error:?}");
1110        assert!(error.status().is_none(), "{error:?}");
1111        let source = wkt::TimestampError::OutOfRange;
1112        assert!(error.to_string().contains(&source.to_string()), "{error}");
1113        assert!(error.http_status_code().is_none(), "{error:?}");
1114        assert_eq!(error.http_headers(), Some(&headers));
1115        assert!(error.http_payload().is_none(), "{error:?}");
1116        assert!(!error.is_transient_and_before_rpc(), "{error:?}");
1117    }
1118}