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