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}