extern crate std;
use core::time::Duration;
use std::time::SystemTime;
use ::zenwave::{Error, sse::ParseError as SseParseError};
use super::{Failure, FailureKind};
#[must_use]
pub const fn status_kind(status: u16) -> FailureKind {
match status {
408 | 429 | 500..=599 => FailureKind::Transient,
_ => FailureKind::Permanent,
}
}
#[must_use]
pub fn parse_retry_after(value: &str, now: SystemTime) -> Option<Duration> {
let value = value.trim();
if let Ok(seconds) = value.parse::<u64>() {
return Some(Duration::from_secs(seconds));
}
let date = httpdate::parse_http_date(value).ok()?;
Some(date.duration_since(now).unwrap_or(Duration::ZERO))
}
#[must_use]
pub fn retry_after(error: &Error) -> Option<Duration> {
let value = error
.response()?
.headers()
.get(::zenwave::header::RETRY_AFTER)?
.to_str()
.ok()?;
parse_retry_after(value, SystemTime::now())
}
#[must_use]
pub fn failure(error: &Error) -> Failure {
match error {
Error::Http { status, .. } => {
Failure::new(status_kind(status.as_u16())).with_retry_after(retry_after(error))
}
Error::Transport(_) | Error::Tls(_) | Error::Proxy(_) | Error::Timeout => {
Failure::network()
}
Error::Io(error) if io_is_network(error.kind()) => Failure::network(),
Error::BodyParse(error) => body_failure(error),
_ => Failure::permanent(),
}
}
#[must_use]
pub fn body_failure(error: &::zenwave::BodyError) -> Failure {
match error {
::zenwave::BodyError::Io(error) if io_is_network(error.kind()) => Failure::network(),
_ => Failure::permanent(),
}
}
#[must_use]
pub const fn sse_failure(error: &SseParseError) -> Failure {
match error {
SseParseError::BodyError(_) => Failure::network(),
_ => Failure::permanent(),
}
}
#[must_use]
pub const fn io_is_network(kind: std::io::ErrorKind) -> bool {
use std::io::ErrorKind::{
BrokenPipe, ConnectionAborted, ConnectionRefused, ConnectionReset, HostUnreachable,
NetworkDown, NetworkUnreachable, NotConnected, TimedOut, UnexpectedEof,
};
matches!(
kind,
BrokenPipe
| ConnectionAborted
| ConnectionRefused
| ConnectionReset
| HostUnreachable
| NetworkDown
| NetworkUnreachable
| NotConnected
| TimedOut
| UnexpectedEof
)
}
#[must_use]
pub fn http_error(status: u16, headers: &[(&str, &str)], body: &str) -> Error {
use ::zenwave::{Response, StatusCode, error::HttpErrorResponse};
let status = StatusCode::from_u16(status).expect("valid HTTP status");
let mut response = Response::new(::zenwave::Body::empty());
*response.status_mut() = status;
for (name, value) in headers {
response.headers_mut().insert(
::zenwave::header::HeaderName::from_bytes(name.as_bytes()).expect("valid header name"),
::zenwave::header::HeaderValue::from_str(value).expect("valid header value"),
);
}
Error::Http {
status,
message: std::string::String::from(body),
response: alloc::boxed::Box::new(HttpErrorResponse {
response,
body_text: Some(std::string::String::from(body)),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn statuses() {
assert_eq!(status_kind(500), FailureKind::Transient);
assert_eq!(status_kind(503), FailureKind::Transient);
assert_eq!(status_kind(529), FailureKind::Transient);
assert_eq!(status_kind(408), FailureKind::Transient);
assert_eq!(status_kind(429), FailureKind::Transient);
assert_eq!(status_kind(401), FailureKind::Permanent);
assert_eq!(status_kind(400), FailureKind::Permanent);
}
#[test]
fn retry_after_seconds_and_date() {
let now = SystemTime::UNIX_EPOCH + Duration::from_secs(784_111_777);
assert_eq!(
parse_retry_after("120", now),
Some(Duration::from_secs(120))
);
assert_eq!(
parse_retry_after("Sun, 06 Nov 1994 08:49:47 GMT", now),
Some(Duration::from_secs(10))
);
assert_eq!(
parse_retry_after("Sun, 06 Nov 1994 08:49:07 GMT", now),
Some(Duration::ZERO)
);
assert_eq!(parse_retry_after("soon", now), None);
}
}