pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("source error: {0}")]
SourceError(String),
#[error("checkpoint error: {0}")]
CheckpointError(String),
#[error("schema error: {0}")]
SchemaError(String),
#[error("validation error(s): {0:?}")]
ValidationError(Vec<String>),
#[error("configuration error: {0}")]
ConfigError(String),
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error("serialization error: {0}")]
SerializationError(String),
#[error("timeout error: {0}")]
TimeoutError(String),
#[error("unrecoverable error: {0}")]
Unrecoverable(String),
#[error("state error: {0}")]
StateError(String),
#[error("transform error: {0}")]
TransformError(String),
#[error("not implemented: {0}")]
NotImplemented(String),
}
impl Error {
pub fn is_recoverable(&self) -> bool {
matches!(self, Self::SourceError(_) | Self::TimeoutError(_))
}
}
impl From<serde_json::Error> for Error {
fn from(value: serde_json::Error) -> Self {
Self::SerializationError(value.to_string())
}
}
#[cfg(test)]
mod tests {
use super::Error;
#[test]
fn recoverable_flag_matches_contract() {
assert!(Error::SourceError("conn reset".into()).is_recoverable());
assert!(Error::TimeoutError("deadline exceeded".into()).is_recoverable());
assert!(!Error::ConfigError("invalid".into()).is_recoverable());
assert!(!Error::ValidationError(vec!["bad field".into()]).is_recoverable());
assert!(!Error::CheckpointError("io".into()).is_recoverable());
assert!(!Error::SchemaError("missing".into()).is_recoverable());
assert!(!Error::StateError("illegal transition".into()).is_recoverable());
assert!(!Error::TransformError("crash".into()).is_recoverable());
assert!(!Error::Unrecoverable("boom".into()).is_recoverable());
}
#[test]
fn serde_errors_map_to_serialization_errors() {
let error = serde_json::from_str::<serde_json::Value>("{").unwrap_err();
assert!(matches!(Error::from(error), Error::SerializationError(_)));
}
}