<div align="center">
 <img src="./logo.png" alt="Recoverable Logo" width="96">

# Recoverable

[![crate.io](https://img.shields.io/crates/v/recoverable.svg)](https://crates.io/crates/recoverable)
[![docs.rs](https://docs.rs/recoverable/badge.svg)](https://docs.rs/recoverable)
[![MSRV](https://img.shields.io/crates/msrv/recoverable)](https://crates.io/crates/recoverable)
[![CI](https://github.com/microsoft/oxidizer/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/microsoft/oxidizer/actions/workflows/main.yml)
[![Coverage](https://codecov.io/gh/microsoft/oxidizer/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/microsoft/oxidizer)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
<a href="../.."><img src="../../logo.svg" alt="This crate was developed as part of the Oxidizer project" width="20"></a>

</div>

Recovery information and classification for resilience patterns.

This crate provides types for classifying conditions based on their **recoverability state**,
enabling consistent recovery behavior across different error types and resilience middleware.

Recovery information describes whether retrying an operation might help, not whether the
operation succeeded or failed. Both successful operations and permanent failures should use
[`RecoveryInfo::never`][__link0] since recovery is not necessary or desirable.

## Core Types

* [`RecoveryInfo`][__link1]: Classifies conditions as recoverable (transient) or non-recoverable (permanent/successful).
* [`Recovery`][__link2]: A trait for types that can provide their own recovery information.
* [`RecoveryKind`][__link3]: An enum representing the kind of recovery that can be attempted.

## Examples

### Classifying Errors

```rust
use recoverable::{Recovery, RecoveryInfo, RecoveryKind};

#[derive(Debug)]
enum DatabaseError {
    ConnectionTimeout,
    InvalidCredentials,
    TableNotFound,
}

impl Recovery for DatabaseError {
    fn recovery(&self) -> RecoveryInfo {
        match self {
            // Transient failure - might succeed if retried
            DatabaseError::ConnectionTimeout => RecoveryInfo::retry(),
            // Permanent failures - retrying won't help
            DatabaseError::InvalidCredentials => RecoveryInfo::never(),
            DatabaseError::TableNotFound => RecoveryInfo::never(),
        }
    }
}

let error = DatabaseError::ConnectionTimeout;
assert_eq!(error.recovery().kind(), RecoveryKind::Retry);

// Successful operations also use never() since retry is unnecessary
let success_result: Result<(), DatabaseError> = Ok(());
```

### Retry Delay

You can specify when to retry an operation using the `delay` method:

```rust
use std::time::Duration;

use recoverable::{RecoveryInfo, RecoveryKind};

// Retry with a 30-second delay (e.g., from a Retry-After header)
let recovery = RecoveryInfo::retry().delay(Duration::from_secs(30));
assert_eq!(recovery.kind(), RecoveryKind::Retry);
assert_eq!(recovery.get_delay(), Some(Duration::from_secs(30)));

// Immediate retry
let immediate = RecoveryInfo::retry().delay(Duration::ZERO);
assert_eq!(immediate.get_delay(), Some(Duration::ZERO));
```


<hr/>
<sub>
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/oxidizer/tree/main/crates/recoverable">source code</a>.
</sub>

 [__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGy4k8ldDFPOhG2VNeXtD5nnKG6EPY6OfW5wBG8g18NOFNdxpYXKEG8WkGld0dAdTG1oAzDm5S-yFG7msTiEH07YIG3TZOaUlSzkRYWSBgmtyZWNvdmVyYWJsZWUwLjEuMg
 [__link0]: https://docs.rs/recoverable/0.1.2/recoverable/?search=RecoveryInfo::never
 [__link1]: https://docs.rs/recoverable/0.1.2/recoverable/struct.RecoveryInfo.html
 [__link2]: https://docs.rs/recoverable/0.1.2/recoverable/trait.Recovery.html
 [__link3]: https://docs.rs/recoverable/0.1.2/recoverable/enum.RecoveryKind.html
