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}