use std::error::Error as StdError;
use std::fmt;
use std::io;
use std::num::ParseIntError;
use thiserror::Error;
type BoxError = Box<dyn StdError + Send + Sync + 'static>;
pub type Result<T> = std::result::Result<T, DsctError>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorCategory {
InvalidArguments,
FileNotFound,
PermissionDenied,
InvalidFormat,
Io,
Error,
}
impl ErrorCategory {
pub fn exit_code(self) -> i32 {
match self {
Self::InvalidArguments => 2,
Self::FileNotFound | Self::PermissionDenied => 3,
Self::InvalidFormat => 4,
Self::Io | Self::Error => 1,
}
}
pub fn code(self) -> &'static str {
match self {
Self::InvalidArguments => "invalid_arguments",
Self::FileNotFound => "file_not_found",
Self::PermissionDenied => "permission_denied",
Self::InvalidFormat => "invalid_format",
Self::Io => "io_error",
Self::Error => "error",
}
}
}
#[derive(Debug, Error)]
#[error("{message}")]
pub struct DsctError {
category: ErrorCategory,
message: String,
#[source]
source: Option<BoxError>,
}
impl DsctError {
pub fn msg(message: impl Into<String>) -> Self {
Self {
category: ErrorCategory::Error,
message: message.into(),
source: None,
}
}
pub fn invalid_argument(message: impl Into<String>) -> Self {
Self {
category: ErrorCategory::InvalidArguments,
message: message.into(),
source: None,
}
}
pub fn with_source(
category: ErrorCategory,
message: impl Into<String>,
source: impl Into<BoxError>,
) -> Self {
Self {
category,
message: message.into(),
source: Some(source.into()),
}
}
pub fn context(self, message: impl Into<String>) -> Self {
Self {
category: self.category,
message: message.into(),
source: Some(Box::new(self)),
}
}
pub fn reclassify(mut self, category: ErrorCategory) -> Self {
self.category = category;
self
}
pub fn category(&self) -> ErrorCategory {
self.category
}
}
impl From<io::Error> for DsctError {
fn from(error: io::Error) -> Self {
let category = match error.kind() {
io::ErrorKind::NotFound => ErrorCategory::FileNotFound,
io::ErrorKind::PermissionDenied => ErrorCategory::PermissionDenied,
_ => ErrorCategory::Io,
};
Self::with_source(category, error.to_string(), error)
}
}
impl From<serde_json::Error> for DsctError {
fn from(error: serde_json::Error) -> Self {
Self::with_source(ErrorCategory::Error, error.to_string(), error)
}
}
impl From<toml::de::Error> for DsctError {
fn from(error: toml::de::Error) -> Self {
Self::with_source(ErrorCategory::Error, error.to_string(), error)
}
}
impl From<ParseIntError> for DsctError {
fn from(error: ParseIntError) -> Self {
Self::with_source(ErrorCategory::Error, error.to_string(), error)
}
}
impl From<packet_dissector_pcap::PcapError> for DsctError {
fn from(error: packet_dissector_pcap::PcapError) -> Self {
Self::with_source(ErrorCategory::InvalidFormat, error.to_string(), error)
}
}
#[cfg(feature = "tui")]
impl From<rustix::io::Errno> for DsctError {
fn from(error: rustix::io::Errno) -> Self {
let io_error = io::Error::from_raw_os_error(error.raw_os_error());
Self::from(io_error)
}
}
pub trait ResultExt<T> {
fn context(self, message: impl Into<String>) -> Result<T>;
fn invalid_argument(self) -> Result<T>;
}
impl<T, E> ResultExt<T> for std::result::Result<T, E>
where
E: Into<DsctError>,
{
fn context(self, message: impl Into<String>) -> Result<T> {
self.map_err(|error| error.into().context(message))
}
fn invalid_argument(self) -> Result<T> {
self.map_err(|error| error.into().reclassify(ErrorCategory::InvalidArguments))
}
}
pub fn format_error(error: &DsctError) -> String {
let mut rendered = error.to_string();
let mut source = error.source();
while let Some(next) = source {
rendered.push_str(": ");
rendered.push_str(&next.to_string());
source = next.source();
}
rendered
}
impl fmt::Display for ErrorCategory {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.code())
}
}