///|
/// Conservative defaults for transient HTTP responses, not transport failures.
pub fn retryable_status(status : Int) -> Bool {
match status {
408 | 429 | 500 | 502 | 503 | 504 => true
_ => false
}
}
///|
/// HTTP methods are case-sensitive. Body replayability is checked separately.
pub fn idempotent_method(http_method : String) -> Bool {
match http_method {
"GET" | "HEAD" | "PUT" | "DELETE" | "OPTIONS" | "TRACE" => true
_ => false
}
}
///|
/// attempt=0 is the first retry. Returns None for invalid input/settings.
/// Growth saturates before multiplication, including near Int64::MAX.
pub fn backoff_ceiling(settings : Settings, attempt : Int) -> Int64? {
if !valid_settings(settings) || attempt < 0 || attempt > 1000 {
return None
}
let mut delay = settings.base_delay_ms
for i = 0; i < attempt; i = i + 1 {
if delay >= settings.max_delay_ms - delay {
return Some(settings.max_delay_ms)
}
delay = delay * 2L
}
Some(delay)
}
///|
/// Apply caller-provided full-jitter sample in 0..1_000_000, inclusive.
/// Splitting quotient/remainder avoids overflow without floating point.
pub fn jitter_delay(ceiling_ms : Int64, sample_ppm : Int) -> Int64? {
if ceiling_ms < 0L || sample_ppm < 0 || sample_ppm > 1000000 {
return None
}
let n = sample_ppm.to_int64()
Some(ceiling_ms / 1000000L * n + ceiling_ms % 1000000L * n / 1000000L)
}