interprocess/
error.rs

1//! Generic error types used throughout the crate.
2
3use std::{
4    error::Error,
5    fmt::{self, Debug, Display, Formatter, Write},
6    io,
7};
8
9/// General error type for fallible constructors.
10///
11/// In Interprocess, many types feature conversions to and from handles/file descriptors and types
12/// from the standard library. Many of those conversions are fallible because the semantic mapping
13/// between the source and destination types is not always 1:1, with various invariants that need to
14/// be upheld and which are always queried for. With async types, this is further complicated:
15/// runtimes typically need to register OS objects in their polling/completion infrastructure to use
16/// them asynchronously.
17///
18/// All those conversion have one thing in common: they consume ownership of one object and return
19/// ownership of its new form. If the conversion fails, it would be invaluably useful in some cases
20/// to return ownership of the original object back to the caller, so that it could use it in some
21/// other way. The `source` field allows for that, but also reserves the callee's freedom not to do
22/// so. (Hey, it's not like I *wanted* it to be like this, Tokio just doesn't have `.try_clone()`
23/// and returns [`io::Error`]. Oh well, unwrapping's always an option.)
24///
25/// Many (but not all) of those conversions also have an OS error they can attribute the failure to.
26///
27/// Additionally, some conversions have an additional "details" error field that contains extra
28/// infromation about the error. That is typically an enumeration that specifies which stage of the
29/// conversion the OS error happened on.
30#[derive(Debug)]
31pub struct ConversionError<S, E = NoDetails> {
32    /// Extra information about the error.
33    pub details: E,
34    /// The underlying OS error, if any.
35    pub cause: Option<io::Error>,
36    /// Ownership of the input of the conversion.
37    pub source: Option<S>,
38}
39impl<S, E: Default> ConversionError<S, E> {
40    /// Constructs an error value without an OS cause and with default contents for the "details"
41    /// field.
42    pub fn from_source(source: S) -> Self {
43        Self { details: Default::default(), cause: None, source: Some(source) }
44    }
45    /// Constructs an error value that doesn't return input ownership, with default contents for the
46    /// "details" field and an OS cause.
47    pub fn from_cause(cause: io::Error) -> Self {
48        Self { details: Default::default(), cause: Some(cause), source: None }
49    }
50    /// Constructs an error value from a given OS cause, filling the "details" field with its
51    /// default value.
52    pub fn from_source_and_cause(source: S, cause: io::Error) -> Self {
53        Self { details: Default::default(), cause: Some(cause), source: Some(source) }
54    }
55}
56impl<S, E> ConversionError<S, E> {
57    /// Constructs an error value without an OS cause.
58    pub fn from_source_and_details(source: S, details: E) -> Self {
59        Self { details, cause: None, source: Some(source) }
60    }
61    /// Constructs an error value that doesn't return input ownership.
62    pub fn from_cause_and_details(cause: io::Error, details: E) -> Self {
63        Self { details, cause: Some(cause), source: None }
64    }
65    /// Maps the type with which ownership over the input is returned using the given closure.
66    ///
67    /// This utility is mostly used in the crate's internals.
68    pub fn map_source<Sb>(self, f: impl FnOnce(S) -> Sb) -> ConversionError<Sb, E> {
69        ConversionError { details: self.details, cause: self.cause, source: self.source.map(f) }
70    }
71    /// Maps the type with which ownership over the input is returned using the given closure, also
72    /// allowing it to drop the ownership.
73    ///
74    /// This utility is mostly used in the crate's internals.
75    pub fn try_map_source<Sb>(self, f: impl FnOnce(S) -> Option<Sb>) -> ConversionError<Sb, E> {
76        ConversionError {
77            details: self.details,
78            cause: self.cause,
79            source: self.source.and_then(f),
80        }
81    }
82}
83impl<S, E: Display> ConversionError<S, E> {
84    /// Boxes the error into an `io::Error`.
85    pub fn to_io_error(&self) -> io::Error { io::Error::other(self.to_string()) }
86}
87/// Boxes the error into an `io::Error`, dropping the retained file descriptor in the process.
88impl<S, E: Display> From<ConversionError<S, E>> for io::Error {
89    fn from(e: ConversionError<S, E>) -> Self { e.to_io_error() }
90}
91impl<S, E: Default> Default for ConversionError<S, E> {
92    fn default() -> Self { Self { details: Default::default(), cause: None, source: None } }
93}
94impl<S, E: Display> Display for ConversionError<S, E> {
95    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
96        let mut snp = FormatSnooper::new(f);
97        write!(snp, "{}", &self.details)?;
98        if let Some(e) = &self.cause {
99            if snp.anything_written() {
100                f.write_str(": ")?;
101            }
102            Display::fmt(e, f)?;
103        }
104        Ok(())
105    }
106}
107impl<S: Debug, E: Error + 'static> Error for ConversionError<S, E> {
108    #[inline]
109    #[allow(clippy::as_conversions)]
110    fn source(&self) -> Option<&(dyn Error + 'static)> { self.cause.as_ref().map(|r| r as &_) }
111}
112
113/// Thunk type used to specialize on the type of `details`, preventing ": " from being at the
114/// beginning of the output with nothing preceding it.
115struct FormatSnooper<'a, 'b> {
116    formatter: &'b mut Formatter<'a>,
117    anything_written: bool,
118}
119impl<'a, 'b> FormatSnooper<'a, 'b> {
120    fn new(formatter: &'b mut Formatter<'a>) -> Self {
121        Self { formatter, anything_written: false }
122    }
123    fn anything_written(&self) -> bool { self.anything_written }
124}
125impl Write for FormatSnooper<'_, '_> {
126    fn write_str(&mut self, s: &str) -> fmt::Result {
127        if !s.is_empty() {
128            self.anything_written = true;
129            self.formatter.write_str(s)
130        } else {
131            Ok(())
132        }
133    }
134}
135
136/// Marker type used as the generic argument of [`ConversionError`] to denote that there are no
137/// error details.
138#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
139pub struct NoDetails;
140impl Display for NoDetails {
141    fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
142        Ok(()) //
143    }
144}
145
146/// Error type of `TryFrom<OwnedHandle>` conversions.
147#[cfg(windows)]
148#[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))]
149pub type FromHandleError<E = NoDetails> = ConversionError<std::os::windows::io::OwnedHandle, E>;
150
151/// Error type of `TryFrom<OwnedFd>` conversions.
152#[cfg(unix)]
153#[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))]
154pub type FromFdError<E = NoDetails> = ConversionError<std::os::unix::io::OwnedFd, E>;
155
156/// Error type of `.reunite()` on splittable stream types, indicating that the two halves belong to
157/// different streams.
158#[derive(Debug)]
159pub struct ReuniteError<R, S> {
160    /// Ownership of the receive half.
161    pub rh: R,
162    /// Ownership of the send half.
163    pub sh: S,
164}
165impl<R, S> ReuniteError<R, S> {
166    /// Maps the halves of the stream using the given closures.
167    #[inline]
168    pub fn map_halves<NR: From<R>, NS: From<S>>(
169        self,
170        fr: impl FnOnce(R) -> NR,
171        fs: impl FnOnce(S) -> NS,
172    ) -> ReuniteError<NR, NS> {
173        let Self { rh, sh } = self;
174        ReuniteError { rh: fr(rh), sh: fs(sh) }
175    }
176    /// Maps the halves of the stream using the [`From`] trait.
177    ///
178    /// This is useful when implementing wrappers around stream types.
179    #[inline]
180    pub fn convert_halves<NR: From<R>, NS: From<S>>(self) -> ReuniteError<NR, NS> {
181        self.map_halves(From::from, From::from)
182    }
183}
184impl<R, S> Display for ReuniteError<R, S> {
185    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
186        f.write_str("attempt to reunite stream halves that come from different streams")
187    }
188}
189impl<R: Debug, S: Debug> Error for ReuniteError<R, S> {}
190
191/// Result type of `.reunite()` on splittable stream types.
192pub type ReuniteResult<T, R, S> = Result<T, ReuniteError<R, S>>;