use gax::error::Error;
use gax::{
retry_policy::{RetryPolicy, RetryPolicyExt},
retry_result::RetryResult,
};
use std::sync::Arc;
use std::time::Duration;
pub(crate) fn default() -> impl RetryPolicy {
RecommendedPolicy.with_time_limit(Duration::from_secs(10))
}
#[derive(Clone, Debug)]
pub struct RecommendedPolicy;
impl RetryPolicy for RecommendedPolicy {
fn on_error(
&self,
_loop_start: std::time::Instant,
_attempt_count: u32,
idempotent: bool,
error: Error,
) -> RetryResult {
if error.is_transient_and_before_rpc() {
return RetryResult::Continue(error);
}
if !idempotent {
return RetryResult::Permanent(error);
}
if error.is_io() {
return RetryResult::Continue(error);
}
if let Some(code) = error.http_status_code() {
return match code {
408 | 429 | 500..600 => RetryResult::Continue(error),
_ => RetryResult::Permanent(error),
};
}
RetryResult::Permanent(error)
}
}
#[derive(Clone, Debug)]
pub(crate) struct ContinueOn308<T> {
inner: T,
}
impl<T> ContinueOn308<T> {
pub fn new(inner: T) -> Self {
Self { inner }
}
}
impl RetryPolicy for ContinueOn308<Arc<dyn RetryPolicy + 'static>> {
fn on_error(
&self,
loop_start: std::time::Instant,
attempt_count: u32,
idempotent: bool,
error: Error,
) -> RetryResult {
if error.http_status_code() == Some(308) {
return RetryResult::Continue(error);
}
self.inner
.on_error(loop_start, attempt_count, idempotent, error)
}
}
#[cfg(test)]
mod tests {
use super::*;
use gax::throttle_result::ThrottleResult;
use http::HeaderMap;
use test_case::test_case;
#[test_case(408)]
#[test_case(429)]
#[test_case(500)]
#[test_case(502)]
#[test_case(503)]
#[test_case(504)]
fn retryable(code: u16) {
let p = RecommendedPolicy;
let now = std::time::Instant::now();
assert!(p.on_error(now, 0, true, http_error(code)).is_continue());
assert!(p.on_error(now, 0, false, http_error(code)).is_permanent());
let t = p.on_throttle(now, 0, http_error(code));
assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
}
#[test_case(401)]
#[test_case(403)]
fn not_recommended(code: u16) {
let p = RecommendedPolicy;
let now = std::time::Instant::now();
assert!(p.on_error(now, 0, true, http_error(code)).is_permanent());
assert!(p.on_error(now, 0, false, http_error(code)).is_permanent());
let t = p.on_throttle(now, 0, http_error(code));
assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
}
#[test]
fn continue_on_308() {
let inner: Arc<dyn RetryPolicy + 'static> = Arc::new(RecommendedPolicy);
let p = ContinueOn308::new(inner);
let now = std::time::Instant::now();
assert!(p.on_error(now, 0, true, http_error(308)).is_continue());
assert!(p.on_error(now, 0, false, http_error(308)).is_continue());
assert!(p.on_error(now, 0, true, http_error(429)).is_continue());
assert!(p.on_error(now, 0, false, http_error(429)).is_permanent());
let t = p.on_throttle(now, 0, http_error(308));
assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
let t = p.on_throttle(now, 0, http_error(429));
assert!(matches!(t, ThrottleResult::Continue(_)), "{t:?}");
}
fn http_error(code: u16) -> Error {
Error::http(code, HeaderMap::new(), bytes::Bytes::new())
}
}