use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum LynnError {
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Network error: {0}")]
Network(String),
#[error("Connection error: {0}")]
Connection(String),
#[error("Invalid address: {0}")]
InvalidAddress(String),
#[error("Configuration error: {0}")]
Config(String),
#[error("Buffer error: {0}")]
Buffer(String),
#[error("Protocol error: {0}")]
Protocol(String),
#[error("Handler error: {0}")]
Handler(String),
#[error("Timeout: {0}")]
Timeout(String),
#[error("Client error: {0}")]
Client(String),
#[error("Server error: {0}")]
Server(String),
#[error("TLS error: {0}")]
Tls(String),
#[error("Error: {0}")]
Generic(String),
}
pub type Result<T> = std::result::Result<T, LynnError>;
impl LynnError {
pub fn network<S: Into<String>>(msg: S) -> Self {
Self::Network(msg.into())
}
pub fn connection<S: Into<String>>(msg: S) -> Self {
Self::Connection(msg.into())
}
pub fn invalid_address<S: Into<String>>(msg: S) -> Self {
Self::InvalidAddress(msg.into())
}
pub fn config<S: Into<String>>(msg: S) -> Self {
Self::Config(msg.into())
}
pub fn buffer<S: Into<String>>(msg: S) -> Self {
Self::Buffer(msg.into())
}
pub fn protocol<S: Into<String>>(msg: S) -> Self {
Self::Protocol(msg.into())
}
pub fn handler<S: Into<String>>(msg: S) -> Self {
Self::Handler(msg.into())
}
pub fn timeout<S: Into<String>>(msg: S) -> Self {
Self::Timeout(msg.into())
}
pub fn client<S: Into<String>>(msg: S) -> Self {
Self::Client(msg.into())
}
pub fn server<S: Into<String>>(msg: S) -> Self {
Self::Server(msg.into())
}
pub fn tls<S: Into<String>>(msg: S) -> Self {
Self::Tls(msg.into())
}
}
pub trait ToLynnError<T> {
fn with_context<S: Into<String>>(self, ctx: S) -> Result<T>;
}
impl<T, E> ToLynnError<T> for std::result::Result<T, E>
where
E: std::error::Error + Send + Sync + 'static,
{
fn with_context<S: Into<String>>(self, ctx: S) -> Result<T> {
self.map_err(|e| LynnError::Generic(format!("{}: {}", ctx.into(), e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io;
#[test]
fn constructors_produce_expected_variants() {
assert!(matches!(LynnError::network("n"), LynnError::Network(_)));
assert!(matches!(
LynnError::connection("c"),
LynnError::Connection(_)
));
assert!(matches!(
LynnError::invalid_address("a"),
LynnError::InvalidAddress(_)
));
assert!(matches!(LynnError::config("c"), LynnError::Config(_)));
assert!(matches!(LynnError::buffer("b"), LynnError::Buffer(_)));
assert!(matches!(LynnError::protocol("p"), LynnError::Protocol(_)));
assert!(matches!(LynnError::handler("h"), LynnError::Handler(_)));
assert!(matches!(LynnError::timeout("t"), LynnError::Timeout(_)));
assert!(matches!(LynnError::client("c"), LynnError::Client(_)));
assert!(matches!(LynnError::server("s"), LynnError::Server(_)));
}
#[test]
fn display_is_human_readable() {
assert_eq!(
LynnError::Timeout("too long".into()).to_string(),
"Timeout: too long"
);
assert_eq!(
LynnError::InvalidAddress("x".into()).to_string(),
"Invalid address: x"
);
}
#[test]
fn io_errors_convert_automatically() {
let e: LynnError = io::Error::other("disk").into();
assert!(matches!(e, LynnError::Io(_)));
assert_eq!(e.to_string(), "IO error: disk");
}
#[test]
fn with_context_passes_success_through() {
let ok: std::result::Result<i32, io::Error> = Ok(5);
assert_eq!(ok.with_context("ctx").unwrap(), 5);
}
#[test]
fn with_context_wraps_failures() {
let err: std::result::Result<i32, io::Error> = Err(io::Error::other("boom"));
let wrapped = err.with_context("reading config").unwrap_err();
assert!(matches!(wrapped, LynnError::Generic(_)));
assert_eq!(wrapped.to_string(), "Error: reading config: boom");
}
}