1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use core::fmt;
5
6pub mod prelude {
8 pub use crate::{
9 CONFIG_ERROR, ExitCode, ExitCodeError, FAILURE, PERMISSION_DENIED, SUCCESS, UNAVAILABLE,
10 USAGE_ERROR,
11 };
12}
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
16pub struct ExitCode(u8);
17
18impl ExitCode {
19 #[must_use]
21 pub const fn from_u8(code: u8) -> Self {
22 Self(code)
23 }
24
25 pub fn try_from_i32(code: i32) -> Result<Self, ExitCodeError> {
31 u8::try_from(code)
32 .map(Self)
33 .map_err(|_| ExitCodeError::OutOfRange(code))
34 }
35
36 #[must_use]
38 pub const fn as_u8(self) -> u8 {
39 self.0
40 }
41
42 #[must_use]
44 pub const fn as_i32(self) -> i32 {
45 self.0 as i32
46 }
47
48 #[must_use]
50 pub const fn is_success(self) -> bool {
51 self.0 == 0
52 }
53}
54
55impl TryFrom<i32> for ExitCode {
56 type Error = ExitCodeError;
57
58 fn try_from(value: i32) -> Result<Self, Self::Error> {
59 Self::try_from_i32(value)
60 }
61}
62
63impl From<ExitCode> for u8 {
64 fn from(value: ExitCode) -> Self {
65 value.as_u8()
66 }
67}
68
69impl From<ExitCode> for i32 {
70 fn from(value: ExitCode) -> Self {
71 value.as_i32()
72 }
73}
74
75impl From<ExitCode> for std::process::ExitCode {
76 fn from(value: ExitCode) -> Self {
77 Self::from(value.as_u8())
78 }
79}
80
81impl fmt::Display for ExitCode {
82 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
83 write!(formatter, "{}", self.0)
84 }
85}
86
87#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum ExitCodeError {
90 OutOfRange(i32),
92}
93
94impl fmt::Display for ExitCodeError {
95 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
96 match self {
97 Self::OutOfRange(code) => write!(formatter, "exit code {code} is outside 0..=255"),
98 }
99 }
100}
101
102impl std::error::Error for ExitCodeError {}
103
104pub const SUCCESS: ExitCode = ExitCode::from_u8(0);
106
107pub const FAILURE: ExitCode = ExitCode::from_u8(1);
109
110pub const USAGE_ERROR: ExitCode = ExitCode::from_u8(64);
112
113pub const UNAVAILABLE: ExitCode = ExitCode::from_u8(69);
115
116pub const PERMISSION_DENIED: ExitCode = ExitCode::from_u8(77);
118
119pub const CONFIG_ERROR: ExitCode = ExitCode::from_u8(78);
121
122#[cfg(test)]
123mod tests {
124 use super::{CONFIG_ERROR, ExitCode, ExitCodeError, FAILURE, SUCCESS, USAGE_ERROR};
125
126 #[test]
127 fn exposes_common_exit_codes() {
128 assert!(SUCCESS.is_success());
129 assert!(!FAILURE.is_success());
130 assert_eq!(USAGE_ERROR.as_i32(), 64);
131 assert_eq!(CONFIG_ERROR.as_u8(), 78);
132 }
133
134 #[test]
135 fn converts_between_integer_forms() -> Result<(), ExitCodeError> {
136 let code = ExitCode::try_from_i32(77)?;
137
138 assert_eq!(code.as_u8(), 77);
139 assert_eq!(i32::from(code), 77);
140 assert_eq!(u8::from(code), 77);
141 assert_eq!(
142 ExitCode::try_from_i32(256),
143 Err(ExitCodeError::OutOfRange(256))
144 );
145 Ok(())
146 }
147}