#[cfg(any(feature = "blocking", feature = "tokio", test))]
use std::ffi::OsString;
use std::fmt;
use std::io;
use std::path::PathBuf;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum ErrorKind {
Backend,
CreateConsole,
Spawn,
Resize,
Clear,
UnsupportedFeature,
InvalidSize,
Wait,
Kill,
Io,
}
pub struct Error {
repr: ErrorRepr,
}
#[derive(Debug, thiserror::Error)]
enum ErrorRepr {
#[error("failed to initialize the ConPTY backend")]
Backend(#[source] BackendError),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to create pseudoconsole")]
CreateConsole(#[source] io::Error),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to spawn `{}`", .program.to_string_lossy())]
Spawn {
program: OsString,
source: io::Error,
},
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to resize pseudoconsole")]
Resize(#[source] io::Error),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to clear pseudoconsole")]
Clear(#[source] io::Error),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("the ConPTY backend does not support {feature}")]
UnsupportedFeature { feature: &'static str },
#[error(
"invalid pseudoconsole size: {rows} rows x {cols} cols \
(each dimension must be 1..={max})",
max = crate::Size::MAX_DIMENSION
)]
InvalidSize { rows: u16, cols: u16 },
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to wait for child process")]
Wait(#[source] io::Error),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
#[error("failed to kill child process")]
Kill(#[source] io::Error),
#[error("{0}")]
Io(
#[from]
#[source]
io::Error,
),
}
impl Error {
#[cfg_attr(
feature = "blocking",
doc = "# Examples",
doc = "",
doc = "```no_run",
doc = "use conpty_oxide::blocking::Command;",
doc = "",
doc = "if let Err(error) = Command::new(\"does-not-exist.exe\").spawn() {",
doc = " eprintln!(\"{:?}: {error}\", error.kind());",
doc = "}",
doc = "```"
)]
#[must_use]
pub const fn kind(&self) -> ErrorKind {
match self.repr {
ErrorRepr::Backend(_) => ErrorKind::Backend,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::CreateConsole(_) => ErrorKind::CreateConsole,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::Spawn { .. } => ErrorKind::Spawn,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::Resize(_) => ErrorKind::Resize,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::Clear(_) => ErrorKind::Clear,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::UnsupportedFeature { .. } => ErrorKind::UnsupportedFeature,
ErrorRepr::InvalidSize { .. } => ErrorKind::InvalidSize,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::Wait(_) => ErrorKind::Wait,
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::Kill(_) => ErrorKind::Kill,
ErrorRepr::Io(_) => ErrorKind::Io,
}
}
#[must_use]
pub const fn io_error(&self) -> Option<&io::Error> {
match &self.repr {
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::CreateConsole(source)
| ErrorRepr::Resize(source)
| ErrorRepr::Clear(source)
| ErrorRepr::Wait(source)
| ErrorRepr::Kill(source)
| ErrorRepr::Spawn { source, .. } => Some(source),
ErrorRepr::Io(source) => Some(source),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::UnsupportedFeature { .. } => None,
ErrorRepr::Backend(_) | ErrorRepr::InvalidSize { .. } => None,
}
}
#[must_use]
pub const fn backend_error(&self) -> Option<&BackendError> {
match &self.repr {
ErrorRepr::Backend(source) => Some(source),
#[cfg(any(feature = "blocking", feature = "tokio", test))]
ErrorRepr::CreateConsole(_)
| ErrorRepr::Spawn { .. }
| ErrorRepr::Resize(_)
| ErrorRepr::Clear(_)
| ErrorRepr::UnsupportedFeature { .. }
| ErrorRepr::Wait(_)
| ErrorRepr::Kill(_) => None,
ErrorRepr::InvalidSize { .. } | ErrorRepr::Io(_) => None,
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn create_console(source: io::Error) -> Self {
Self {
repr: ErrorRepr::CreateConsole(source),
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn spawn(program: OsString, source: io::Error) -> Self {
Self {
repr: ErrorRepr::Spawn { program, source },
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn resize(source: io::Error) -> Self {
Self {
repr: ErrorRepr::Resize(source),
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn clear(source: io::Error) -> Self {
Self {
repr: ErrorRepr::Clear(source),
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn unsupported_feature(feature: &'static str) -> Self {
Self {
repr: ErrorRepr::UnsupportedFeature { feature },
}
}
pub(crate) const fn invalid_size(rows: u16, cols: u16) -> Self {
Self {
repr: ErrorRepr::InvalidSize { rows, cols },
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn wait(source: io::Error) -> Self {
Self {
repr: ErrorRepr::Wait(source),
}
}
#[cfg(any(feature = "blocking", feature = "tokio", test))]
pub(crate) const fn kill(source: io::Error) -> Self {
Self {
repr: ErrorRepr::Kill(source),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.repr.fmt(f)
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Error")
.field("kind", &self.kind())
.field("context", &self.repr)
.finish()
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.repr.source()
}
}
impl From<BackendError> for Error {
fn from(source: BackendError) -> Self {
Self {
repr: ErrorRepr::Backend(source),
}
}
}
impl From<io::Error> for Error {
fn from(source: io::Error) -> Self {
Self {
repr: ErrorRepr::Io(source),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum BackendErrorKind {
DllNotFound,
MissingExport,
OpenConsoleMissing,
VersionMismatch,
Unsupported,
}
pub struct BackendError {
repr: BackendErrorRepr,
}
#[derive(Debug, thiserror::Error)]
enum BackendErrorRepr {
#[error("conpty.dll not found in `{}`", .dir.display())]
DllNotFound { dir: PathBuf, source: io::Error },
#[error("`{}` is missing required export `{symbol}`", .dll.display())]
MissingExport { dll: PathBuf, symbol: &'static str },
#[error("OpenConsole.exe not found next to `{}`", .dll.display())]
OpenConsoleMissing { dll: PathBuf },
#[error(
"version mismatch: `{}` reports {dll_version} \
but its OpenConsole.exe reports {exe_version}",
.dll.display()
)]
VersionMismatch {
dll: PathBuf,
dll_version: String,
exe_version: String,
},
#[error(
"ConPTY is not available on this version of Windows; \
Windows 10 1809 (build 17763) or later is required"
)]
Unsupported,
}
impl BackendError {
#[must_use]
pub const fn kind(&self) -> BackendErrorKind {
match self.repr {
BackendErrorRepr::DllNotFound { .. } => BackendErrorKind::DllNotFound,
BackendErrorRepr::MissingExport { .. } => BackendErrorKind::MissingExport,
BackendErrorRepr::OpenConsoleMissing { .. } => BackendErrorKind::OpenConsoleMissing,
BackendErrorRepr::VersionMismatch { .. } => BackendErrorKind::VersionMismatch,
BackendErrorRepr::Unsupported => BackendErrorKind::Unsupported,
}
}
#[must_use]
pub const fn io_error(&self) -> Option<&io::Error> {
match &self.repr {
BackendErrorRepr::DllNotFound { source, .. } => Some(source),
BackendErrorRepr::MissingExport { .. }
| BackendErrorRepr::OpenConsoleMissing { .. }
| BackendErrorRepr::VersionMismatch { .. }
| BackendErrorRepr::Unsupported => None,
}
}
pub(crate) const fn dll_not_found(dir: PathBuf, source: io::Error) -> Self {
Self {
repr: BackendErrorRepr::DllNotFound { dir, source },
}
}
pub(crate) const fn missing_export(dll: PathBuf, symbol: &'static str) -> Self {
Self {
repr: BackendErrorRepr::MissingExport { dll, symbol },
}
}
pub(crate) const fn open_console_missing(dll: PathBuf) -> Self {
Self {
repr: BackendErrorRepr::OpenConsoleMissing { dll },
}
}
pub(crate) const fn version_mismatch(
dll: PathBuf,
dll_version: String,
exe_version: String,
) -> Self {
Self {
repr: BackendErrorRepr::VersionMismatch {
dll,
dll_version,
exe_version,
},
}
}
pub(crate) const fn unsupported() -> Self {
Self {
repr: BackendErrorRepr::Unsupported,
}
}
}
impl fmt::Display for BackendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.repr.fmt(f)
}
}
impl fmt::Debug for BackendError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("BackendError")
.field("kind", &self.kind())
.field("context", &self.repr)
.finish()
}
}
impl std::error::Error for BackendError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.repr.source()
}
}
#[cfg(test)]
#[path = "error_tests.rs"]
mod tests;