#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PublishOutcome {
Success,
AlreadyPublished,
RateLimited,
AuthFailed,
BuildFailed,
Failed,
}
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum RetryDecision {
Retry { delay_ms: u64, attempt: u32 },
Fail,
}
#[allow(dead_code)]
const MAX_RETRIES: u32 = 3;
#[allow(dead_code)]
const BASE_DELAY_MS: u64 = 2000;
#[allow(dead_code)]
pub(crate) fn classify_status(status: u16) -> PublishOutcome {
match status {
200 | 201 => PublishOutcome::Success,
409 => PublishOutcome::AlreadyPublished,
429 => PublishOutcome::RateLimited,
401 | 403 => PublishOutcome::AuthFailed,
400 => PublishOutcome::BuildFailed,
_ => PublishOutcome::Failed,
}
}
#[allow(dead_code)]
pub(crate) fn decide_retry(outcome: &PublishOutcome, current_attempt: u32) -> RetryDecision {
match outcome {
PublishOutcome::RateLimited => {
if current_attempt >= MAX_RETRIES {
RetryDecision::Fail
} else {
let delay = BASE_DELAY_MS * (1u64 << (current_attempt - 1));
RetryDecision::Retry {
delay_ms: delay,
attempt: current_attempt + 1,
}
}
}
PublishOutcome::Success
| PublishOutcome::AlreadyPublished
| PublishOutcome::AuthFailed
| PublishOutcome::BuildFailed
| PublishOutcome::Failed => RetryDecision::Fail,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_success() {
assert_eq!(classify_status(200), PublishOutcome::Success);
assert_eq!(classify_status(201), PublishOutcome::Success);
}
#[test]
fn classify_already_published() {
assert_eq!(classify_status(409), PublishOutcome::AlreadyPublished);
}
#[test]
fn classify_rate_limited() {
assert_eq!(classify_status(429), PublishOutcome::RateLimited);
}
#[test]
fn classify_auth_failed() {
assert_eq!(classify_status(401), PublishOutcome::AuthFailed);
assert_eq!(classify_status(403), PublishOutcome::AuthFailed);
}
#[test]
fn classify_build_failed() {
assert_eq!(classify_status(400), PublishOutcome::BuildFailed);
}
#[test]
fn classify_other() {
assert_eq!(classify_status(500), PublishOutcome::Failed);
assert_eq!(classify_status(404), PublishOutcome::Failed);
}
#[test]
fn retry_rate_limited_first_attempt() {
let decision = decide_retry(&PublishOutcome::RateLimited, 1);
assert_eq!(
decision,
RetryDecision::Retry {
delay_ms: 2000,
attempt: 2
}
);
}
#[test]
fn retry_rate_limited_second_attempt() {
let decision = decide_retry(&PublishOutcome::RateLimited, 2);
assert_eq!(
decision,
RetryDecision::Retry {
delay_ms: 4000,
attempt: 3
}
);
}
#[test]
fn retry_rate_limited_max_attempts_fails() {
let decision = decide_retry(&PublishOutcome::RateLimited, 3);
assert_eq!(decision, RetryDecision::Fail);
}
#[test]
fn retry_rate_limited_beyond_max_fails() {
let decision = decide_retry(&PublishOutcome::RateLimited, 4);
assert_eq!(decision, RetryDecision::Fail);
}
#[test]
fn non_rate_limited_outcomes_fail_immediately() {
assert_eq!(
decide_retry(&PublishOutcome::Success, 1),
RetryDecision::Fail
);
assert_eq!(
decide_retry(&PublishOutcome::AuthFailed, 1),
RetryDecision::Fail
);
assert_eq!(
decide_retry(&PublishOutcome::BuildFailed, 1),
RetryDecision::Fail
);
assert_eq!(
decide_retry(&PublishOutcome::Failed, 1),
RetryDecision::Fail
);
assert_eq!(
decide_retry(&PublishOutcome::AlreadyPublished, 1),
RetryDecision::Fail
);
}
}