pub mod client_retry_policy;
pub mod metadata_request_retry_policy;
pub mod resource_throttle_retry_policy;
use crate::constants::{SubStatusCode, RETRY_WITH, SUB_STATUS};
use crate::cosmos_request::CosmosRequest;
use crate::retry_policies::client_retry_policy::ClientRetryPolicy;
use crate::retry_policies::metadata_request_retry_policy::MetadataRequestRetryPolicy;
use crate::retry_policies::resource_throttle_retry_policy::ResourceThrottleRetryPolicy;
use azure_core::error::ErrorKind;
use azure_core::http::{RawResponse, StatusCode};
use azure_core::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RetryResult {
DoNotRetry,
Retry { after: Duration },
}
impl RetryResult {
pub fn is_retry(&self) -> bool {
matches!(self, RetryResult::Retry { .. })
}
}
#[allow(dead_code)]
pub enum RetryPolicy {
Client(Box<ClientRetryPolicy>),
Metadata(MetadataRequestRetryPolicy),
ResourceThrottle(ResourceThrottleRetryPolicy),
}
impl RetryPolicy {
pub async fn before_send_request(&mut self, request: &mut CosmosRequest) {
match self {
RetryPolicy::Client(p) => p.before_send_request(request).await,
RetryPolicy::ResourceThrottle(_p) => {}
RetryPolicy::Metadata(p) => p.before_send_request(request).await,
}
}
pub async fn should_retry(
&mut self,
response: &azure_core::Result<RawResponse>,
) -> RetryResult {
match self {
RetryPolicy::Client(p) => p.should_retry(response).await,
RetryPolicy::ResourceThrottle(p) => p.should_retry(response).await,
RetryPolicy::Metadata(p) => p.should_retry(response).await,
}
}
}
fn is_non_retryable_status_code(
status_code: StatusCode,
sub_status_code: Option<SubStatusCode>,
) -> bool {
if status_code == StatusCode::NotFound {
return sub_status_code != Some(SubStatusCode::READ_SESSION_NOT_AVAILABLE);
}
matches!(
status_code,
StatusCode::BadRequest
| StatusCode::Unauthorized
| StatusCode::MethodNotAllowed
| StatusCode::Conflict
| StatusCode::PreconditionFailed
| StatusCode::PayloadTooLarge
| StatusCode::Locked
| StatusCode::TooManyRequests
| RETRY_WITH
)
}
fn get_substatus_code_from_error(err: &azure_core::Error) -> Option<SubStatusCode> {
if let ErrorKind::HttpResponse { raw_response, .. } = err.kind() {
raw_response
.as_ref()
.and_then(|r| {
r.headers()
.get_as::<u32, std::num::ParseIntError>(&SUB_STATUS)
.ok()
})
.map(SubStatusCode::from)
} else {
None
}
}
fn get_substatus_code_from_response(response: &RawResponse) -> Option<SubStatusCode> {
response
.headers()
.get_as::<u32, std::num::ParseIntError>(&SUB_STATUS)
.ok()
.map(SubStatusCode::from)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RequestSentStatus {
NotSent,
Sent,
Unknown,
}
pub(crate) trait RequestSentExt {
fn request_sent_status(&self) -> RequestSentStatus;
}
impl RequestSentExt for azure_core::Error {
fn request_sent_status(&self) -> RequestSentStatus {
match self.kind() {
ErrorKind::Connection | ErrorKind::Credential | ErrorKind::DataConversion => {
RequestSentStatus::NotSent
}
ErrorKind::HttpResponse { .. } => RequestSentStatus::Sent,
_ => RequestSentStatus::Unknown,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn connection_error_is_not_sent() {
let err = azure_core::Error::with_message(ErrorKind::Connection, "connection refused");
assert_eq!(err.request_sent_status(), RequestSentStatus::NotSent);
}
#[test]
fn io_error_is_unknown() {
let err = azure_core::Error::with_message(ErrorKind::Io, "some io error");
assert_eq!(err.request_sent_status(), RequestSentStatus::Unknown);
}
#[test]
fn http_response_is_sent() {
let err = azure_core::Error::with_message(
ErrorKind::HttpResponse {
status: azure_core::http::StatusCode::InternalServerError,
error_code: None,
raw_response: None,
},
"server error",
);
assert_eq!(err.request_sent_status(), RequestSentStatus::Sent);
}
#[test]
fn credential_is_not_sent() {
let err = azure_core::Error::with_message(ErrorKind::Credential, "auth failed");
assert_eq!(err.request_sent_status(), RequestSentStatus::NotSent);
}
}