proc_exit/code.rs
1/// Process exit code.
2///
3/// Common exit codes:
4/// - [`Code::SUCCESS`]
5/// - [`Code::FAILURE`]
6/// - [`bash::USAGE`][crate::bash::USAGE]
7#[derive(Clone, Copy, Debug, Eq, PartialEq)]
8pub struct Code(i32);
9
10/// # Create a [`Code`]
11impl Code {
12 /// The process exited successfully.
13 pub const SUCCESS: Code = Code(0);
14
15 /// Generic failure.
16 pub const FAILURE: Code = Code(1);
17
18 /// Create a custom error code
19 pub const fn new(code: i32) -> Self {
20 Self(code)
21 }
22
23 /// Converts [`std::process::ExitStatus`] to [`Code`].
24 ///
25 /// On Unix, if the process was terminated by a fatal signal, the corresponding
26 /// signal exit code is returned.
27 #[inline]
28 pub fn from_status(status: std::process::ExitStatus) -> Self {
29 Self::from(status)
30 }
31}
32
33/// # Bubble up the exit [`Code`]
34impl Code {
35 /// [`exit`][std::process::exit] now!
36 #[inline]
37 pub fn process_exit(self) -> ! {
38 std::process::exit(self.as_raw())
39 }
40
41 /// Convert to [`Result`]
42 #[inline]
43 pub fn ok(self) -> crate::ExitResult {
44 if self.as_raw() == Self::SUCCESS.as_raw() {
45 Ok(())
46 } else {
47 Err(crate::Exit::new(self))
48 }
49 }
50
51 /// Convert to [`Exit`][crate::Exit] error type
52 #[inline]
53 pub fn as_exit(self) -> crate::Exit {
54 crate::Exit::new(self)
55 }
56
57 /// Add user-visible message (like an [`Error`][std::error::Error])
58 #[inline]
59 pub fn with_message<D: std::fmt::Display + 'static>(self, msg: D) -> crate::Exit {
60 self.as_exit().with_message(msg)
61 }
62}
63
64/// # Introspection and Integration
65impl Code {
66 /// Convert to [`ExitCode`][std::process::ExitCode]
67 #[inline]
68 pub fn as_exit_code(self) -> Option<std::process::ExitCode> {
69 self.as_portable().map(|c| c.into())
70 }
71
72 /// Convert to raw value
73 #[inline]
74 pub const fn as_raw(self) -> i32 {
75 self.0
76 }
77
78 /// Convert to portable, raw value
79 #[inline]
80 pub const fn as_portable(self) -> Option<u8> {
81 if self.is_portable() {
82 Some(self.as_raw() as u8)
83 } else {
84 None
85 }
86 }
87
88 /// Determines if the provided [`std::process::ExitStatus`] was successful.
89 ///
90 /// Example:
91 ///
92 /// ```
93 /// use std::process;
94 ///
95 /// let exit_status = process::Command::new("true")
96 /// .status()
97 /// .expect("failed to run true(1)");
98 /// assert!(proc_exit::Code::from_status(exit_status).is_ok());
99 /// ```
100 ///
101 #[inline]
102 pub const fn is_ok(self) -> bool {
103 self.as_raw() == Self::SUCCESS.as_raw()
104 }
105
106 /// Determines if the provided [`std::process::ExitStatus`] was unsuccessful.
107 ///
108 /// Example:
109 ///
110 /// ```
111 /// use std::process;
112 ///
113 /// let exit_status = process::Command::new("false")
114 /// .status()
115 /// .expect("failed to run false(1)");
116 /// assert!(proc_exit::Code::from_status(exit_status).is_err());
117 /// ```
118 ///
119 #[inline]
120 pub const fn is_err(self) -> bool {
121 !self.is_ok()
122 }
123
124 /// Test if provided exit code is portable across platforms.
125 ///
126 /// While Windows has wider types for return codes, Unix OS's tend to only support 8-bits,
127 /// stripping off the higher order bits.
128 #[inline]
129 pub const fn is_portable(self) -> bool {
130 0 <= self.as_raw() && self.as_raw() <= 255
131 }
132}
133
134impl Default for Code {
135 #[inline]
136 fn default() -> Self {
137 // Chosen to allow `coerce().unwrap_or_default`
138 Self::FAILURE
139 }
140}
141
142/// Converts an `i32` primitive integer to an exit code.
143impl From<i32> for Code {
144 #[inline]
145 fn from(n: i32) -> Self {
146 Self(n)
147 }
148}
149
150/// Converts [`std::process::ExitStatus`] to an exit code by looking at its
151/// [`ExitStatus::code()`] value.
152///
153/// On Unix, if the process was terminated by a fatal signal, the corresponding
154/// signal exit code is returned.
155///
156/// [`std::process::ExitStatus`]:
157/// https://doc.rust-lang.org/std/process/struct.ExitStatus.html
158/// [`ExitStatus::code()`]:
159/// https://doc.rust-lang.org/std/process/struct.ExitStatus.html#method.code
160impl From<std::process::ExitStatus> for Code {
161 #[inline]
162 fn from(status: std::process::ExitStatus) -> Self {
163 let n = platform_exit_code(status).unwrap_or(Code::default().0);
164 From::from(n)
165 }
166}
167
168#[cfg(target_family = "unix")]
169#[inline]
170fn platform_exit_code(status: std::process::ExitStatus) -> Option<i32> {
171 use std::os::unix::process::ExitStatusExt;
172 status.code().or_else(|| status.signal())
173}
174
175#[cfg(not(target_family = "unix"))]
176#[inline]
177fn platform_exit_code(status: std::process::ExitStatus) -> Option<i32> {
178 status.code()
179}
180
181impl std::process::Termination for Code {
182 #[inline]
183 fn report(self) -> std::process::ExitCode {
184 self.as_exit_code()
185 .unwrap_or(std::process::ExitCode::FAILURE)
186 }
187}