use google_cloud_gax::error::Error;
use google_cloud_gax::error::rpc::Code;
use google_cloud_gax::retry_policy::RetryPolicy;
use google_cloud_gax::retry_result::RetryResult;
use google_cloud_gax::retry_state::RetryState;
#[derive(Clone, Debug, Default)]
pub struct RetryableErrors;
impl RetryPolicy for RetryableErrors {
fn on_error(&self, _state: &RetryState, error: Error) -> RetryResult {
if error.is_transient_and_before_rpc() {
return RetryResult::Continue(error);
}
if error.is_io() || error.is_timeout() || error.is_connect() {
return RetryResult::Continue(error);
}
if error.is_transport() && error.http_status_code().is_none() {
return RetryResult::Continue(error);
}
if let Some(429 | 500 | 502 | 503 | 504) = error.http_status_code() {
return RetryResult::Continue(error);
}
if let Some(status) = error.status() {
return match status.code {
Code::Aborted
| Code::Cancelled
| Code::DeadlineExceeded
| Code::Internal
| Code::ResourceExhausted
| Code::Unavailable => RetryResult::Continue(error),
_ => RetryResult::Permanent(error),
};
}
RetryResult::Permanent(error)
}
}
#[cfg(test)]
mod tests {
use super::*;
use google_cloud_gax::error::CredentialsError;
use google_cloud_gax::error::rpc::Status;
use http::HeaderMap;
use test_case::test_case;
#[test_case(Code::Aborted)]
#[test_case(Code::Cancelled)]
#[test_case(Code::DeadlineExceeded)]
#[test_case(Code::Internal)]
#[test_case(Code::ResourceExhausted)]
#[test_case(Code::Unavailable)]
fn retryable_status_codes(code: Code) {
let err = || Error::service(Status::default().set_code(code).set_message("try again"));
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(true), err()),
RetryResult::Continue(_)
));
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(false), err()),
RetryResult::Continue(_)
));
}
#[test_case(Code::Unknown)]
#[test_case(Code::InvalidArgument)]
#[test_case(Code::NotFound)]
#[test_case(Code::PermissionDenied)]
#[test_case(Code::FailedPrecondition)]
#[test_case(Code::DataLoss)]
fn permanent_status_codes(code: Code) {
let err = Error::service(Status::default().set_code(code).set_message("fail"));
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(true), err),
RetryResult::Permanent(_)
));
}
#[test_case(429)]
#[test_case(500)]
#[test_case(502)]
#[test_case(503)]
#[test_case(504)]
fn retryable_http_status_codes(code: u16) {
let err = Error::http(code, HeaderMap::new(), bytes::Bytes::new());
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(true), err),
RetryResult::Continue(_)
));
}
#[test_case(400)]
#[test_case(401)]
#[test_case(403)]
#[test_case(404)]
#[test_case(408)]
#[test_case(409)]
#[test_case(501)]
fn permanent_http_status_codes(code: u16) {
let err = Error::http(code, HeaderMap::new(), bytes::Bytes::new());
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(true), err),
RetryResult::Permanent(_)
));
}
#[test]
fn retry_transient_before_rpc() {
let err = Error::authentication(CredentialsError::from_msg(true, "try again"));
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(false), err),
RetryResult::Continue(_)
));
let err = Error::authentication(CredentialsError::from_msg(false, "fail"));
assert!(matches!(
RetryableErrors.on_error(&RetryState::new(true), err),
RetryResult::Permanent(_)
));
}
#[test]
fn retry_io_timeout_connect_transport() {
let state = RetryState::new(true);
assert!(
RetryableErrors
.on_error(&state, Error::io("io"))
.is_continue()
);
assert!(
RetryableErrors
.on_error(&state, Error::timeout("timeout"))
.is_continue()
);
assert!(
RetryableErrors
.on_error(&state, Error::connect("connect"))
.is_continue()
);
assert!(
RetryableErrors
.on_error(&state, Error::transport(HeaderMap::new(), "rst_stream"))
.is_continue()
);
}
#[test]
fn permanent_serde() {
let state = RetryState::new(true);
assert!(
RetryableErrors
.on_error(&state, Error::ser("fail"))
.is_permanent()
);
assert!(
RetryableErrors
.on_error(&state, Error::deser("fail"))
.is_permanent()
);
}
}