#![forbid(unsafe_code)]
#![doc = include_str!("../README.md")]
use core::fmt;
pub mod prelude {
pub use crate::{
CONFIG_ERROR, ExitCode, ExitCodeError, FAILURE, PERMISSION_DENIED, SUCCESS, UNAVAILABLE,
USAGE_ERROR,
};
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ExitCode(u8);
impl ExitCode {
#[must_use]
pub const fn from_u8(code: u8) -> Self {
Self(code)
}
pub fn try_from_i32(code: i32) -> Result<Self, ExitCodeError> {
u8::try_from(code)
.map(Self)
.map_err(|_| ExitCodeError::OutOfRange(code))
}
#[must_use]
pub const fn as_u8(self) -> u8 {
self.0
}
#[must_use]
pub const fn as_i32(self) -> i32 {
self.0 as i32
}
#[must_use]
pub const fn is_success(self) -> bool {
self.0 == 0
}
}
impl TryFrom<i32> for ExitCode {
type Error = ExitCodeError;
fn try_from(value: i32) -> Result<Self, Self::Error> {
Self::try_from_i32(value)
}
}
impl From<ExitCode> for u8 {
fn from(value: ExitCode) -> Self {
value.as_u8()
}
}
impl From<ExitCode> for i32 {
fn from(value: ExitCode) -> Self {
value.as_i32()
}
}
impl From<ExitCode> for std::process::ExitCode {
fn from(value: ExitCode) -> Self {
Self::from(value.as_u8())
}
}
impl fmt::Display for ExitCode {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ExitCodeError {
OutOfRange(i32),
}
impl fmt::Display for ExitCodeError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfRange(code) => write!(formatter, "exit code {code} is outside 0..=255"),
}
}
}
impl std::error::Error for ExitCodeError {}
pub const SUCCESS: ExitCode = ExitCode::from_u8(0);
pub const FAILURE: ExitCode = ExitCode::from_u8(1);
pub const USAGE_ERROR: ExitCode = ExitCode::from_u8(64);
pub const UNAVAILABLE: ExitCode = ExitCode::from_u8(69);
pub const PERMISSION_DENIED: ExitCode = ExitCode::from_u8(77);
pub const CONFIG_ERROR: ExitCode = ExitCode::from_u8(78);
#[cfg(test)]
mod tests {
use super::{CONFIG_ERROR, ExitCode, ExitCodeError, FAILURE, SUCCESS, USAGE_ERROR};
#[test]
fn exposes_common_exit_codes() {
assert!(SUCCESS.is_success());
assert!(!FAILURE.is_success());
assert_eq!(USAGE_ERROR.as_i32(), 64);
assert_eq!(CONFIG_ERROR.as_u8(), 78);
}
#[test]
fn converts_between_integer_forms() -> Result<(), ExitCodeError> {
let code = ExitCode::try_from_i32(77)?;
assert_eq!(code.as_u8(), 77);
assert_eq!(i32::from(code), 77);
assert_eq!(u8::from(code), 77);
assert_eq!(
ExitCode::try_from_i32(256),
Err(ExitCodeError::OutOfRange(256))
);
Ok(())
}
}