use std::time::Duration;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Outcome {
Completed {
note: Option<String>,
},
Pause {
resume_in: Duration,
note: Option<String>,
},
}
impl Outcome {
pub fn completed() -> Outcome {
Outcome::Completed { note: None }
}
pub fn completed_with(note: impl Into<String>) -> Outcome {
Outcome::Completed {
note: Some(note.into()),
}
}
pub fn pause_in(resume_in: Duration) -> Outcome {
Outcome::Pause {
resume_in,
note: None,
}
}
pub fn pause_in_with(resume_in: Duration, note: impl Into<String>) -> Outcome {
Outcome::Pause {
resume_in,
note: Some(note.into()),
}
}
}
#[derive(Debug)]
pub struct TaskError {
permanent: bool,
message: String,
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
}
impl TaskError {
pub fn retryable<E>(error: E) -> TaskError
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
TaskError::build(false, error)
}
pub fn permanent<E>(error: E) -> TaskError
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
TaskError::build(true, error)
}
pub fn is_permanent(&self) -> bool {
self.permanent
}
pub fn message(&self) -> &str {
&self.message
}
fn build<E>(permanent: bool, error: E) -> TaskError
where
E: Into<Box<dyn std::error::Error + Send + Sync + 'static>>,
{
let source = error.into();
TaskError {
permanent,
message: source.to_string(),
source: Some(source),
}
}
}
impl std::fmt::Display for TaskError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.message)
}
}
impl TaskError {
pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|boxed| boxed.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl<E> From<E> for TaskError
where
E: std::error::Error + Send + Sync + 'static,
{
fn from(error: E) -> TaskError {
TaskError::retryable(error)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, thiserror::Error)]
#[error("boom: {0}")]
struct Boom(&'static str);
#[test]
fn question_mark_errors_are_retryable() {
fn run() -> Result<(), TaskError> {
Err(Boom("network"))?;
Ok(())
}
let err = run().unwrap_err();
assert!(!err.is_permanent());
assert_eq!(err.message(), "boom: network");
}
#[test]
fn permanent_is_permanent_and_keeps_message() {
let err = TaskError::permanent(Boom("gone"));
assert!(err.is_permanent());
assert_eq!(err.message(), "boom: gone");
assert_eq!(err.to_string(), "boom: gone");
}
#[test]
fn outcome_constructors() {
assert_eq!(Outcome::completed(), Outcome::Completed { note: None });
assert_eq!(
Outcome::completed_with("done"),
Outcome::Completed {
note: Some("done".into())
}
);
assert_eq!(
Outcome::pause_in(Duration::from_secs(5)),
Outcome::Pause {
resume_in: Duration::from_secs(5),
note: None
}
);
}
}