google_cloud_gax/retry_result.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
15//! Polling and retry loop control types.
16//!
17//! This module contains types to control polling loops and retry loops.
18//! Applications only need to use these types when implementing their own retry
19//! and polling policies.
20
21use crate::error::Error;
22
23/// The result of a loop control decision.
24///
25/// # Example
26///
27/// ```
28/// # use google_cloud_gax::{error::Error, retry_policy::RetryPolicy};
29/// # use google_cloud_gax::retry_state::RetryState;
30/// # use google_cloud_gax::{retry_result::RetryResult, throttle_result::ThrottleResult};
31/// #[derive(Debug)]
32/// struct MyRetryPolicy;
33/// impl google_cloud_gax::retry_policy::RetryPolicy for MyRetryPolicy {
34/// fn on_error(&self, state: &RetryState, error: Error) -> RetryResult {
35/// if state.attempt_count > 42 {
36/// return RetryResult::Exhausted(error);
37/// }
38/// RetryResult::Continue(error)
39/// }
40/// fn on_throttle(&self, state: &RetryState, error: Error) -> ThrottleResult {
41/// # panic!();
42/// }
43/// }
44/// ```
45#[derive(Debug)]
46#[allow(clippy::exhaustive_enums)]
47pub enum RetryResult {
48 /// The error is non-retryable, stop the loop.
49 Permanent(Error),
50
51 /// The error is retryable, but the policy is stopping the loop.
52 ///
53 /// Loop control policies may stop the loop on retryable errors, for
54 /// example, because the policy only allows a limited number of attempts.
55 Exhausted(Error),
56
57 /// The error was retryable, continue the loop.
58 Continue(Error),
59}
60
61impl RetryResult {
62 /// Returns `true` if the operation should not be retried due to a permanent error.
63 pub fn is_permanent(&self) -> bool {
64 match &self {
65 Self::Permanent(_) => true,
66 Self::Exhausted(_) | Self::Continue(_) => false,
67 }
68 }
69
70 /// Returns `true` if the operation should not be retried because the retry policy is exhausted.
71 pub fn is_exhausted(&self) -> bool {
72 match &self {
73 Self::Exhausted(_) => true,
74 Self::Permanent(_) | Self::Continue(_) => false,
75 }
76 }
77
78 /// Returns `true` if the operation should be retried.
79 pub fn is_continue(&self) -> bool {
80 match &self {
81 Self::Continue(_) => true,
82 Self::Permanent(_) | Self::Exhausted(_) => false,
83 }
84 }
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn loop_state() {
93 let flow = RetryResult::Permanent(permanent_error());
94 assert!(flow.is_permanent(), "{flow:?}");
95 assert!(!flow.is_exhausted(), "{flow:?}");
96 assert!(!flow.is_continue(), "{flow:?}");
97
98 let flow = RetryResult::Exhausted(transient_error());
99 assert!(!flow.is_permanent(), "{flow:?}");
100 assert!(flow.is_exhausted(), "{flow:?}");
101 assert!(!flow.is_continue(), "{flow:?}");
102
103 let flow = RetryResult::Continue(transient_error());
104 assert!(!flow.is_permanent(), "{flow:?}");
105 assert!(!flow.is_exhausted(), "{flow:?}");
106 assert!(flow.is_continue(), "{flow:?}");
107 }
108
109 fn permanent_error() -> Error {
110 use crate::error::rpc::*;
111 Error::service(Status::default().set_code(Code::PermissionDenied))
112 }
113
114 fn transient_error() -> Error {
115 use crate::error::rpc::*;
116 Error::service(Status::default().set_code(Code::Unavailable))
117 }
118}