use std::io;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum StorageError {
#[error("{context}")]
Io {
context: String,
#[source]
source: io::Error,
},
#[error("cannot parse the state file")]
Deserialization(#[from] toml::de::Error),
#[error("cannot serialize the state")]
Serialization(#[from] toml::ser::Error),
}
pub type StorageResult<T> = std::result::Result<T, StorageError>;
pub trait IoResultExt<T> {
fn io_context(self, context: impl Into<String>) -> StorageResult<T>;
}
impl<T> IoResultExt<T> for io::Result<T> {
fn io_context(self, context: impl Into<String>) -> StorageResult<T> {
self.map_err(|source| StorageError::Io {
context: context.into(),
source,
})
}
}
#[cfg(test)]
mod tests {
use std::error::Error as _;
use super::*;
#[test]
fn io_context_wraps_the_error_and_keeps_it_as_the_source() {
let failure: io::Result<()> =
Err(io::Error::new(io::ErrorKind::PermissionDenied, "denied"));
let error = failure.io_context("write state file").unwrap_err();
assert_eq!(error.to_string(), "write state file");
assert_eq!(error.source().unwrap().to_string(), "denied");
}
#[test]
fn a_parse_failure_converts_from_a_toml_error() {
let parsed = toml::from_str::<toml::Value>("= nonsense");
let error: StorageError = parsed.unwrap_err().into();
assert!(matches!(error, StorageError::Deserialization(_)));
assert_eq!(error.to_string(), "cannot parse the state file");
}
}