#![cfg_attr(all(coverage_nightly, test), feature(coverage_attribute))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![doc(html_logo_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/recoverable/logo.png")]
#![doc(html_favicon_url = "https://media.githubusercontent.com/media/microsoft/oxidizer/refs/heads/main/crates/recoverable/favicon.ico")]
use std::fmt::{Display, Formatter};
use std::time::Duration;
mod io;
pub mod _documentation;
#[derive(Debug, PartialEq, Clone, Eq, Hash)]
pub struct RecoveryInfo {
kind: RecoveryKind,
delay: Option<Duration>,
}
#[derive(Debug, PartialEq, Clone, Eq, Copy, Hash)]
#[non_exhaustive]
pub enum RecoveryKind {
Unknown,
Retry,
Never,
Unavailable,
}
impl RecoveryInfo {
#[must_use]
pub const fn unknown() -> Self {
Self {
kind: RecoveryKind::Unknown,
delay: None,
}
}
#[must_use]
pub const fn never() -> Self {
Self {
kind: RecoveryKind::Never,
delay: None,
}
}
#[must_use]
pub const fn retry() -> Self {
Self {
kind: RecoveryKind::Retry,
delay: None,
}
}
#[must_use]
pub const fn unavailable() -> Self {
Self {
kind: RecoveryKind::Unavailable,
delay: None,
}
}
#[must_use]
pub const fn delay(self, delay: Duration) -> Self {
Self {
kind: self.kind,
delay: Some(delay),
}
}
#[must_use]
pub const fn kind(&self) -> RecoveryKind {
self.kind
}
#[must_use]
pub const fn get_delay(&self) -> Option<Duration> {
self.delay
}
}
pub trait Recovery {
fn recovery(&self) -> RecoveryInfo;
}
impl<R, E> Recovery for Result<R, E>
where
R: Recovery,
E: Recovery,
{
fn recovery(&self) -> RecoveryInfo {
match self {
Ok(res) => res.recovery(),
Err(err) => err.recovery(),
}
}
}
impl Display for RecoveryInfo {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
if let Some(delay) = self.delay {
return write!(f, "{} (delay {:?})", self.kind, delay);
}
Display::fmt(&self.kind, f)
}
}
impl RecoveryKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Unknown => "unknown",
Self::Never => "never",
Self::Retry => "retry",
Self::Unavailable => "unavailable",
}
}
}
impl Display for RecoveryKind {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use std::fmt::Debug;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use super::*;
assert_impl_all!(RecoveryInfo: Debug, PartialEq, Clone, Send, Sync, Eq, PartialEq);
assert_impl_all!(RecoveryKind: Debug, PartialEq, Clone, Eq, Copy, std::hash::Hash);
assert_not_impl_all!(RecoveryInfo: Copy);
#[test]
fn recovery_enum() {
assert_eq!(RecoveryInfo::unknown().kind(), RecoveryKind::Unknown);
assert_eq!(RecoveryInfo::unavailable().kind(), RecoveryKind::Unavailable);
assert_eq!(RecoveryInfo::retry().kind(), RecoveryKind::Retry);
assert_eq!(RecoveryInfo::retry().delay(Duration::ZERO).kind(), RecoveryKind::Retry);
assert_eq!(RecoveryInfo::never().kind(), RecoveryKind::Never);
}
#[test]
fn display_ok() {
assert_eq!(RecoveryInfo::unknown().to_string(), "unknown");
assert_eq!(RecoveryInfo::never().to_string(), "never");
assert_eq!(RecoveryInfo::retry().to_string(), "retry");
assert_eq!(RecoveryInfo::unavailable().to_string(), "unavailable");
assert_eq!(
RecoveryInfo::retry().delay(Duration::from_secs(30)).to_string(),
"retry (delay 30s)"
);
assert_eq!(
RecoveryInfo::unavailable().delay(Duration::from_mins(5)).to_string(),
"unavailable (delay 300s)"
);
}
#[test]
fn recovery_kind_as_str() {
assert_eq!(RecoveryKind::Unknown.as_str(), "unknown");
assert_eq!(RecoveryKind::Never.as_str(), "never");
assert_eq!(RecoveryKind::Retry.as_str(), "retry");
assert_eq!(RecoveryKind::Unavailable.as_str(), "unavailable");
}
#[test]
fn recovery_kind_display_ok() {
assert_eq!(RecoveryKind::Unknown.to_string(), "unknown");
assert_eq!(RecoveryKind::Never.to_string(), "never");
assert_eq!(RecoveryKind::Retry.to_string(), "retry");
assert_eq!(RecoveryKind::Unavailable.to_string(), "unavailable");
}
#[test]
fn delay_behavior() {
let thirty_seconds = Duration::from_secs(30);
let recovery = RecoveryInfo::retry().delay(thirty_seconds);
assert_eq!(recovery.get_delay(), Some(thirty_seconds));
assert_eq!(recovery.kind(), RecoveryKind::Retry);
let zero_duration = RecoveryInfo::retry().delay(Duration::ZERO);
assert_eq!(zero_duration.get_delay(), Some(Duration::ZERO));
let unavailable = RecoveryInfo::unavailable().delay(Duration::from_mins(5));
assert_eq!(unavailable.get_delay(), Some(Duration::from_mins(5)));
assert_eq!(unavailable.kind(), RecoveryKind::Unavailable);
let updated = RecoveryInfo::retry().delay(Duration::from_secs(10)).delay(Duration::from_secs(20));
assert_eq!(updated.get_delay(), Some(Duration::from_secs(20)));
}
#[test]
fn unavailable_behavior() {
let recovery = RecoveryInfo::unavailable();
assert_eq!(recovery.get_delay(), None);
let recovery = RecoveryInfo::unavailable().delay(Duration::ZERO);
assert_eq!(recovery.get_delay(), Some(Duration::ZERO));
let recovery = RecoveryInfo::unavailable().delay(Duration::from_secs(1));
assert_eq!(recovery.get_delay(), Some(Duration::from_secs(1)));
}
#[test]
fn assert_result_implements_recover() {
assert_impl_all!(Result<TestType, TestType>: Recovery);
assert_not_impl_all!(Result<TestType, String>: Recovery);
}
#[test]
fn get_delay_ok() {
assert_eq!(RecoveryInfo::unknown().get_delay(), None);
assert_eq!(RecoveryInfo::never().get_delay(), None);
assert_eq!(RecoveryInfo::retry().get_delay(), None);
assert_eq!(
RecoveryInfo::retry().delay(Duration::from_mins(1)).get_delay(),
Some(Duration::from_mins(1))
);
assert_eq!(RecoveryInfo::unavailable().get_delay(), None);
assert_eq!(
RecoveryInfo::unavailable().delay(Duration::from_mins(5)).get_delay(),
Some(Duration::from_mins(5))
);
}
#[test]
fn recover_trait_implementations() {
assert_eq!(
(Ok(TestType) as Result<TestType, TestType>).recovery().kind(),
RecoveryKind::Unknown
);
assert_eq!(
(Err(TestType) as Result<TestType, TestType>).recovery().kind(),
RecoveryKind::Unknown
);
}
#[derive(Debug)]
struct TestType;
impl Recovery for TestType {
fn recovery(&self) -> RecoveryInfo {
RecoveryInfo::unknown()
}
}
}