use std::fmt;
use std::io;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ErrorKind {
Source,
Sink,
BackPressure,
Decode,
Encode,
Placement,
PreProcess,
Config,
FeatureNotEnabled,
NotImplemented,
Lifecycle,
Io,
Other,
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let label = match self {
ErrorKind::Source => "source",
ErrorKind::Sink => "sink",
ErrorKind::BackPressure => "back-pressure",
ErrorKind::Decode => "decode",
ErrorKind::Encode => "encode",
ErrorKind::Placement => "placement",
ErrorKind::PreProcess => "preprocess",
ErrorKind::Config => "config",
ErrorKind::FeatureNotEnabled => "feature-not-enabled",
ErrorKind::NotImplemented => "not-implemented",
ErrorKind::Lifecycle => "lifecycle",
ErrorKind::Io => "io",
ErrorKind::Other => "other",
};
f.write_str(label)
}
}
#[derive(Debug)]
pub struct Error {
kind: ErrorKind,
message: String,
source: Option<Box<dyn std::error::Error + Send + Sync>>,
}
impl Error {
pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
Self {
kind,
message: message.into(),
source: None,
}
}
pub fn with_source(
kind: ErrorKind,
message: impl Into<String>,
source: impl std::error::Error + Send + Sync + 'static,
) -> Self {
Self {
kind,
message: message.into(),
source: Some(Box::new(source)),
}
}
pub fn source(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Source, message)
}
pub fn sink(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Sink, message)
}
pub fn back_pressure(message: impl Into<String>) -> Self {
Self::new(ErrorKind::BackPressure, message)
}
pub fn decode(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Decode, message)
}
pub fn encode(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Encode, message)
}
pub fn placement(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Placement, message)
}
pub fn preprocess(message: impl Into<String>) -> Self {
Self::new(ErrorKind::PreProcess, message)
}
pub fn config(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Config, message)
}
pub fn feature_not_enabled(message: impl Into<String>) -> Self {
Self::new(ErrorKind::FeatureNotEnabled, message)
}
pub fn not_implemented(message: impl Into<String>) -> Self {
Self::new(ErrorKind::NotImplemented, message)
}
pub fn lifecycle(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Lifecycle, message)
}
pub fn io(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Io, message)
}
pub fn other(message: impl Into<String>) -> Self {
Self::new(ErrorKind::Other, message)
}
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> &str {
&self.message
}
}
impl Clone for Error {
fn clone(&self) -> Self {
Self {
kind: self.kind,
message: self.message.clone(),
source: None,
}
}
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.message == other.message
}
}
impl Eq for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}: {}", self.kind, self.message)
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source
.as_ref()
.map(|e| e.as_ref() as &(dyn std::error::Error + 'static))
}
}
impl From<io::Error> for Error {
fn from(value: io::Error) -> Self {
let kind = match value.kind() {
io::ErrorKind::WouldBlock | io::ErrorKind::Interrupted => ErrorKind::BackPressure,
_ => ErrorKind::Io,
};
let message = value.to_string();
Self::with_source(kind, message, value)
}
}
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display_uses_stable_kind_label() {
let err = Error::source("boom");
assert_eq!(err.to_string(), "source: boom");
}
#[test]
fn from_io_preserves_source_chain() {
let io = io::Error::new(io::ErrorKind::NotFound, "missing");
let err: Error = io.into();
assert_eq!(err.kind(), ErrorKind::Io);
assert!(std::error::Error::source(&err).is_some());
}
#[test]
fn would_block_maps_to_back_pressure() {
let io = io::Error::new(io::ErrorKind::WouldBlock, "busy");
let err: Error = io.into();
assert_eq!(err.kind(), ErrorKind::BackPressure);
}
#[test]
fn not_implemented_helper() {
let err = Error::not_implemented("stub");
assert_eq!(err.kind(), ErrorKind::NotImplemented);
}
#[test]
fn all_constructors_and_labels() {
let cases = [
(Error::sink("s"), ErrorKind::Sink, "sink"),
(
Error::back_pressure("b"),
ErrorKind::BackPressure,
"back-pressure",
),
(Error::decode("d"), ErrorKind::Decode, "decode"),
(Error::encode("e"), ErrorKind::Encode, "encode"),
(Error::placement("p"), ErrorKind::Placement, "placement"),
(Error::preprocess("pp"), ErrorKind::PreProcess, "preprocess"),
(Error::config("c"), ErrorKind::Config, "config"),
(
Error::feature_not_enabled("f"),
ErrorKind::FeatureNotEnabled,
"feature-not-enabled",
),
(Error::lifecycle("l"), ErrorKind::Lifecycle, "lifecycle"),
(Error::io("i"), ErrorKind::Io, "io"),
(Error::other("o"), ErrorKind::Other, "other"),
];
for (err, kind, label) in cases {
assert_eq!(err.kind(), kind);
assert!(err.message().chars().next().is_some());
assert!(err.to_string().starts_with(label));
let cloned = err.clone();
assert_eq!(cloned, err);
}
let interrupted: Error = io::Error::new(io::ErrorKind::Interrupted, "intr").into();
assert_eq!(interrupted.kind(), ErrorKind::BackPressure);
}
#[test]
fn with_source_preserves_chain() {
let err = Error::with_source(ErrorKind::Other, "wrap", io::Error::other("inner"));
assert!(std::error::Error::source(&err).is_some());
assert!(err.clone().message() == "wrap");
}
}