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 {
44			details: Default::default(),
45			cause: None,
46			source: Some(source),
47		}
48	}
49	/// Constructs an error value that doesn't return input ownership, with default contents for the
50	/// "details" field and an OS cause.
51	pub fn from_cause(cause: io::Error) -> Self {
52		Self {
53			details: Default::default(),
54			cause: Some(cause),
55			source: None,
56		}
57	}
58	/// Constructs an error value from a given OS cause, filling the "details" field with its
59	/// default value.
60	pub fn from_source_and_cause(source: S, cause: io::Error) -> Self {
61		Self {
62			details: Default::default(),
63			cause: Some(cause),
64			source: Some(source),
65		}
66	}
67}
68impl<S, E> ConversionError<S, E> {
69	/// Constructs an error value without an OS cause.
70	pub fn from_source_and_details(source: S, details: E) -> Self {
71		Self {
72			details,
73			cause: None,
74			source: Some(source),
75		}
76	}
77	/// Constructs an error value that doesn't return input ownership.
78	pub fn from_cause_and_details(cause: io::Error, details: E) -> Self {
79		Self {
80			details,
81			cause: Some(cause),
82			source: None,
83		}
84	}
85	/// Maps the type with which ownership over the input is returned using the given closure.
86	///
87	/// This utility is mostly used in the crate's internals.
88	pub fn map_source<Sb>(self, f: impl FnOnce(S) -> Sb) -> ConversionError<Sb, E> {
89		ConversionError {
90			details: self.details,
91			cause: self.cause,
92			source: self.source.map(f),
93		}
94	}
95	/// Maps the type with which ownership over the input is returned using the given closure, also
96	/// allowing it to drop the ownership.
97	///
98	/// This utility is mostly used in the crate's internals.
99	pub fn try_map_source<Sb>(self, f: impl FnOnce(S) -> Option<Sb>) -> ConversionError<Sb, E> {
100		ConversionError {
101			details: self.details,
102			cause: self.cause,
103			source: self.source.and_then(f),
104		}
105	}
106}
107impl<S, E: Display> ConversionError<S, E> {
108	/// Boxes the error into an `io::Error`.
109	pub fn to_io_error(&self) -> io::Error {
110		let msg = self.to_string();
111		io::Error::new(io::ErrorKind::Other, msg)
112	}
113}
114/// Boxes the error into an `io::Error`, dropping the retained file descriptor in the process.
115impl<S, E: Display> From<ConversionError<S, E>> for io::Error {
116	fn from(e: ConversionError<S, E>) -> Self {
117		e.to_io_error()
118	}
119}
120impl<S, E: Default> Default for ConversionError<S, E> {
121	fn default() -> Self {
122		Self {
123			details: Default::default(),
124			cause: None,
125			source: None,
126		}
127	}
128}
129impl<S, E: Display> Display for ConversionError<S, E> {
130	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
131		let mut snp = FormatSnooper::new(f);
132		write!(snp, "{}", &self.details)?;
133		if let Some(e) = &self.cause {
134			if snp.anything_written() {
135				f.write_str(": ")?;
136			}
137			Display::fmt(e, f)?;
138		}
139		Ok(())
140	}
141}
142impl<S: Debug, E: Error + 'static> Error for ConversionError<S, E> {
143	#[inline]
144	#[allow(clippy::as_conversions)]
145	fn source(&self) -> Option<&(dyn Error + 'static)> {
146		self.cause.as_ref().map(|r| r as &_)
147	}
148}
149
150/// Thunk type used to specialize on the type of `details`, preventing ": " from being at the
151/// beginning of the output with nothing preceding it.
152struct FormatSnooper<'a, 'b> {
153	formatter: &'b mut Formatter<'a>,
154	anything_written: bool,
155}
156impl<'a, 'b> FormatSnooper<'a, 'b> {
157	fn new(formatter: &'b mut Formatter<'a>) -> Self {
158		Self {
159			formatter,
160			anything_written: false,
161		}
162	}
163	fn anything_written(&self) -> bool {
164		self.anything_written
165	}
166}
167impl Write for FormatSnooper<'_, '_> {
168	fn write_str(&mut self, s: &str) -> fmt::Result {
169		if !s.is_empty() {
170			self.anything_written = true;
171			self.formatter.write_str(s)
172		} else {
173			Ok(())
174		}
175	}
176}
177
178/// Marker type used as the generic argument of [`ConversionError`] to denote that there are no
179/// error details.
180#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
181pub struct NoDetails;
182impl Display for NoDetails {
183	fn fmt(&self, _f: &mut Formatter<'_>) -> fmt::Result {
184		Ok(()) //
185	}
186}
187
188/// Error type of `TryFrom<OwnedHandle>` conversions.
189#[cfg(windows)]
190#[cfg_attr(feature = "doc_cfg", doc(cfg(windows)))]
191pub type FromHandleError<E = NoDetails> = ConversionError<std::os::windows::io::OwnedHandle, E>;
192
193/// Error type of `TryFrom<OwnedFd>` conversions.
194#[cfg(unix)]
195#[cfg_attr(feature = "doc_cfg", doc(cfg(unix)))]
196pub type FromFdError<E = NoDetails> = ConversionError<std::os::unix::io::OwnedFd, E>;
197
198/// Error type of `.reunite()` on splittable stream types, indicating that the two halves belong to
199/// different streams.
200#[derive(Debug)]
201pub struct ReuniteError<R, S> {
202	/// Ownership of the receive half.
203	pub rh: R,
204	/// Ownership of the send half.
205	pub sh: S,
206}
207impl<R, S> ReuniteError<R, S> {
208	/// Maps the halves of the stream using the given closures.
209	#[inline]
210	pub fn map_halves<NR: From<R>, NS: From<S>>(
211		self,
212		fr: impl FnOnce(R) -> NR,
213		fs: impl FnOnce(S) -> NS,
214	) -> ReuniteError<NR, NS> {
215		let Self { rh, sh } = self;
216		ReuniteError {
217			rh: fr(rh),
218			sh: fs(sh),
219		}
220	}
221	/// Maps the halves of the stream using the [`From`] trait.
222	///
223	/// This is useful when implementing wrappers around stream types.
224	#[inline]
225	pub fn convert_halves<NR: From<R>, NS: From<S>>(self) -> ReuniteError<NR, NS> {
226		self.map_halves(From::from, From::from)
227	}
228}
229impl<R, S> Display for ReuniteError<R, S> {
230	fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
231		f.write_str("attempt to reunite stream halves that come from different streams")
232	}
233}
234impl<R: Debug, S: Debug> Error for ReuniteError<R, S> {}
235
236/// Result type of `.reunite()` on splittable stream types.
237pub type ReuniteResult<T, R, S> = Result<T, ReuniteError<R, S>>;