#![allow(rustdoc::broken_intra_doc_links)]
use clap;
use std::{
error::Error,
fmt::{Display, Formatter},
sync::atomic::{AtomicI32, Ordering},
};
static EXIT_CODE: AtomicI32 = AtomicI32::new(0);
pub fn get_exit_code() -> i32 {
EXIT_CODE.load(Ordering::SeqCst)
}
pub fn set_exit_code(code: i32) {
EXIT_CODE.store(code, Ordering::SeqCst);
}
pub type UResult<T> = Result<T, Box<dyn UError>>;
pub trait UError: Error + Send {
fn code(&self) -> i32 {
1
}
fn usage(&self) -> bool {
false
}
}
impl<T> From<T> for Box<dyn UError>
where
T: UError + 'static,
{
fn from(t: T) -> Self {
Box::new(t)
}
}
#[derive(Debug)]
pub struct USimpleError {
pub code: i32,
pub message: String,
}
impl USimpleError {
#[allow(clippy::new_ret_no_self)]
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
Box::new(Self {
code,
message: message.into(),
})
}
}
impl Error for USimpleError {}
impl Display for USimpleError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.message.fmt(f)
}
}
impl UError for USimpleError {
fn code(&self) -> i32 {
self.code
}
}
#[derive(Debug)]
pub struct UUsageError {
pub code: i32,
pub message: String,
}
impl UUsageError {
#[allow(clippy::new_ret_no_self)]
pub fn new<S: Into<String>>(code: i32, message: S) -> Box<dyn UError> {
Box::new(Self {
code,
message: message.into(),
})
}
}
impl Error for UUsageError {}
impl Display for UUsageError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.message.fmt(f)
}
}
impl UError for UUsageError {
fn code(&self) -> i32 {
self.code
}
fn usage(&self) -> bool {
true
}
}
#[derive(Debug)]
pub struct UIoError {
context: Option<String>,
inner: std::io::Error,
}
impl UIoError {
#[allow(clippy::new_ret_no_self)]
pub fn new<S: Into<String>>(kind: std::io::ErrorKind, context: S) -> Box<dyn UError> {
Box::new(Self {
context: Some(context.into()),
inner: kind.into(),
})
}
}
impl UError for UIoError {}
impl Error for UIoError {}
impl Display for UIoError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
use std::io::ErrorKind::*;
let mut message;
let message = if self.inner.raw_os_error().is_some() {
match self.inner.kind() {
NotFound => "No such file or directory",
PermissionDenied => "Permission denied",
ConnectionRefused => "Connection refused",
ConnectionReset => "Connection reset",
ConnectionAborted => "Connection aborted",
NotConnected => "Not connected",
AddrInUse => "Address in use",
AddrNotAvailable => "Address not available",
BrokenPipe => "Broken pipe",
AlreadyExists => "Already exists",
WouldBlock => "Would block",
InvalidInput => "Invalid input",
InvalidData => "Invalid data",
TimedOut => "Timed out",
WriteZero => "Write zero",
Interrupted => "Interrupted",
UnexpectedEof => "Unexpected end of file",
_ => {
message = strip_errno(&self.inner);
capitalize(&mut message);
&message
}
}
} else {
message = self.inner.to_string();
capitalize(&mut message);
&message
};
if let Some(ctx) = &self.context {
write!(f, "{}: {}", ctx, message)
} else {
write!(f, "{}", message)
}
}
}
fn capitalize(text: &mut str) {
if let Some(first) = text.get_mut(..1) {
first.make_ascii_uppercase();
}
}
pub fn strip_errno(err: &std::io::Error) -> String {
let mut msg = err.to_string();
if let Some(pos) = msg.find(" (os error ") {
msg.truncate(pos);
}
msg
}
pub trait FromIo<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> T;
}
impl FromIo<Box<UIoError>> for std::io::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
Box::new(UIoError {
context: Some((context)()),
inner: self,
})
}
}
impl<T> FromIo<UResult<T>> for std::io::Result<T> {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
self.map_err(|e| e.map_err_context(context) as Box<dyn UError>)
}
}
impl FromIo<Box<UIoError>> for std::io::ErrorKind {
fn map_err_context(self, context: impl FnOnce() -> String) -> Box<UIoError> {
Box::new(UIoError {
context: Some((context)()),
inner: std::io::Error::new(self, ""),
})
}
}
impl From<std::io::Error> for UIoError {
fn from(f: std::io::Error) -> Self {
Self {
context: None,
inner: f,
}
}
}
impl From<std::io::Error> for Box<dyn UError> {
fn from(f: std::io::Error) -> Self {
let u_error: UIoError = f.into();
Box::new(u_error) as Self
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl<T> FromIo<UResult<T>> for Result<T, nix::Error> {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
self.map_err(|e| {
Box::new(UIoError {
context: Some((context)()),
inner: std::io::Error::from_raw_os_error(e as i32),
}) as Box<dyn UError>
})
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl<T> FromIo<UResult<T>> for nix::Error {
fn map_err_context(self, context: impl FnOnce() -> String) -> UResult<T> {
Err(Box::new(UIoError {
context: Some((context)()),
inner: std::io::Error::from_raw_os_error(self as i32),
}) as Box<dyn UError>)
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl From<nix::Error> for UIoError {
fn from(f: nix::Error) -> Self {
Self {
context: None,
inner: std::io::Error::from_raw_os_error(f as i32),
}
}
}
#[cfg(any(target_os = "linux", target_os = "android"))]
impl From<nix::Error> for Box<dyn UError> {
fn from(f: nix::Error) -> Self {
let u_error: UIoError = f.into();
Box::new(u_error) as Self
}
}
#[macro_export]
macro_rules! uio_error(
($err:expr, $($args:tt)+) => ({
UIoError::new(
$err.kind(),
format!($($args)+)
)
})
);
#[derive(Debug)]
pub struct ExitCode(pub i32);
impl ExitCode {
#[allow(clippy::new_ret_no_self)]
pub fn new(code: i32) -> Box<dyn UError> {
Box::new(Self(code))
}
}
impl Error for ExitCode {}
impl Display for ExitCode {
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
Ok(())
}
}
impl UError for ExitCode {
fn code(&self) -> i32 {
self.0
}
}
impl From<i32> for Box<dyn UError> {
fn from(i: i32) -> Self {
ExitCode::new(i)
}
}
#[derive(Debug)]
pub struct ClapErrorWrapper {
code: i32,
error: clap::Error,
}
pub trait UClapError<T> {
fn with_exit_code(self, code: i32) -> T;
}
impl From<clap::Error> for Box<dyn UError> {
fn from(e: clap::Error) -> Self {
Box::new(ClapErrorWrapper { code: 1, error: e })
}
}
impl UClapError<ClapErrorWrapper> for clap::Error {
fn with_exit_code(self, code: i32) -> ClapErrorWrapper {
ClapErrorWrapper { code, error: self }
}
}
impl UClapError<Result<clap::ArgMatches, ClapErrorWrapper>>
for Result<clap::ArgMatches, clap::Error>
{
fn with_exit_code(self, code: i32) -> Result<clap::ArgMatches, ClapErrorWrapper> {
self.map_err(|e| e.with_exit_code(code))
}
}
impl UError for ClapErrorWrapper {
fn code(&self) -> i32 {
if let clap::error::ErrorKind::DisplayHelp | clap::error::ErrorKind::DisplayVersion =
self.error.kind()
{
0
} else {
self.code
}
}
}
impl Error for ClapErrorWrapper {}
impl Display for ClapErrorWrapper {
fn fmt(&self, _f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.error.print().unwrap();
Ok(())
}
}
#[cfg(test)]
mod tests {
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_nix_error_conversion() {
use super::{FromIo, UIoError};
use nix::errno::Errno;
use std::io::ErrorKind;
for (nix_error, expected_error_kind) in [
(Errno::EACCES, ErrorKind::PermissionDenied),
(Errno::ENOENT, ErrorKind::NotFound),
(Errno::EEXIST, ErrorKind::AlreadyExists),
] {
let error = UIoError::from(nix_error);
assert_eq!(expected_error_kind, error.inner.kind());
}
assert_eq!(
"test: Permission denied",
Err::<(), nix::Error>(Errno::EACCES)
.map_err_context(|| String::from("test"))
.unwrap_err()
.to_string()
)
}
}