use delta_kernel::object_store::{Error as ObjectStoreError, Result as ObjectStoreResult};
use super::generic_error;
pub(crate) enum RetryFailure {
ServerError(reqwest::StatusCode),
Transport(String),
}
impl std::fmt::Display for RetryFailure {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ServerError(status) => write!(f, "HTTP {status}"),
Self::Transport(msg) => write!(f, "{msg}"),
}
}
}
pub(crate) enum PutCreateDisposition {
Success,
Terminal(ObjectStoreError),
Ambiguous { failure: RetryFailure },
}
pub(crate) fn is_retryable_http_status(status: reqwest::StatusCode) -> bool {
status.is_server_error()
}
pub(crate) fn is_transient(err: &reqwest::Error) -> bool {
err.is_timeout()
|| err.is_connect()
|| ((err.is_request() || err.is_body()) && err.status().is_none())
}
pub(crate) fn typed_http_error(
status: reqwest::StatusCode,
path: &str,
) -> Option<ObjectStoreError> {
match status {
reqwest::StatusCode::NOT_FOUND => Some(ObjectStoreError::NotFound {
path: path.to_string(),
source: "HTTP 404".into(),
}),
reqwest::StatusCode::CONFLICT => Some(ObjectStoreError::AlreadyExists {
path: path.to_string(),
source: "HTTP 409".into(),
}),
_ => None,
}
}
pub(crate) fn reject_remaining_non_success(
response: reqwest::Response,
) -> ObjectStoreResult<reqwest::Response> {
response.error_for_status().map_err(generic_error)
}
pub(crate) fn ensure_success_response(
response: reqwest::Response,
path: &str,
) -> ObjectStoreResult<reqwest::Response> {
if let Some(err) = typed_http_error(response.status(), path) {
return Err(err);
}
reject_remaining_non_success(response)
}
pub(crate) fn ensure_list_response(
response: reqwest::Response,
path: &str,
page_token: Option<&str>,
) -> ObjectStoreResult<Option<reqwest::Response>> {
if let Some(err) = typed_http_error(response.status(), path) {
if matches!(err, ObjectStoreError::NotFound { .. }) && page_token.is_none() {
return Ok(None);
}
return Err(err);
}
Ok(Some(reject_remaining_non_success(response)?))
}
pub(crate) fn classify_put_create_response(
response: Result<reqwest::Response, reqwest::Error>,
path: &str,
retries: u32,
) -> ObjectStoreResult<PutCreateDisposition> {
let response = match response {
Ok(resp) => resp,
Err(e) if is_transient(&e) => {
return Ok(PutCreateDisposition::Ambiguous {
failure: RetryFailure::Transport(e.to_string()),
});
}
Err(e) => return Err(generic_error(e)),
};
let status = response.status();
if status.is_success() {
reject_remaining_non_success(response)?;
return Ok(PutCreateDisposition::Success);
}
if let Some(err) = typed_http_error(status, path) {
if matches!(err, ObjectStoreError::AlreadyExists { .. }) && retries > 0 {
return Ok(PutCreateDisposition::Ambiguous {
failure: RetryFailure::ServerError(status),
});
}
return Ok(PutCreateDisposition::Terminal(err));
}
if is_retryable_http_status(status) {
return Ok(PutCreateDisposition::Ambiguous {
failure: RetryFailure::ServerError(status),
});
}
reject_remaining_non_success(response).map(|_| PutCreateDisposition::Success)
}