1#[cfg(any(feature = "blocking", feature = "tokio", test))]
8use std::ffi::OsString;
9use std::fmt;
10use std::io;
11use std::path::PathBuf;
12
13pub type Result<T> = std::result::Result<T, Error>;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum ErrorKind {
25 Backend,
27 CreateConsole,
29 Spawn,
31 Resize,
33 Clear,
35 UnsupportedFeature,
37 InvalidSize,
39 Wait,
41 Kill,
43 Io,
45}
46
47pub struct Error {
53 repr: ErrorRepr,
54}
55
56#[derive(Debug, thiserror::Error)]
57enum ErrorRepr {
58 #[error("failed to initialize the ConPTY backend")]
59 Backend(#[source] BackendError),
60 #[cfg(any(feature = "blocking", feature = "tokio", test))]
61 #[error("failed to create pseudoconsole")]
62 CreateConsole(#[source] io::Error),
63 #[cfg(any(feature = "blocking", feature = "tokio", test))]
64 #[error("failed to spawn `{}`", .program.to_string_lossy())]
65 Spawn {
66 program: OsString,
67 source: io::Error,
68 },
69 #[cfg(any(feature = "blocking", feature = "tokio", test))]
70 #[error("failed to resize pseudoconsole")]
71 Resize(#[source] io::Error),
72 #[cfg(any(feature = "blocking", feature = "tokio", test))]
73 #[error("failed to clear pseudoconsole")]
74 Clear(#[source] io::Error),
75 #[cfg(any(feature = "blocking", feature = "tokio", test))]
76 #[error("the ConPTY backend does not support {feature}")]
77 UnsupportedFeature { feature: &'static str },
78 #[error(
79 "invalid pseudoconsole size: {rows} rows x {cols} cols \
80 (each dimension must be 1..={max})",
81 max = crate::Size::MAX_DIMENSION
82 )]
83 InvalidSize { rows: u16, cols: u16 },
84 #[cfg(any(feature = "blocking", feature = "tokio", test))]
85 #[error("failed to wait for child process")]
86 Wait(#[source] io::Error),
87 #[cfg(any(feature = "blocking", feature = "tokio", test))]
88 #[error("failed to kill child process")]
89 Kill(#[source] io::Error),
90 #[error("{0}")]
91 Io(
92 #[from]
93 #[source]
94 io::Error,
95 ),
96}
97
98impl Error {
99 #[cfg_attr(
102 feature = "blocking",
103 doc = "# Examples",
104 doc = "",
105 doc = "```no_run",
106 doc = "use conpty_oxide::blocking::Command;",
107 doc = "",
108 doc = "if let Err(error) = Command::new(\"does-not-exist.exe\").spawn() {",
109 doc = " eprintln!(\"{:?}: {error}\", error.kind());",
110 doc = "}",
111 doc = "```"
112 )]
113 #[must_use]
114 pub const fn kind(&self) -> ErrorKind {
115 match self.repr {
116 ErrorRepr::Backend(_) => ErrorKind::Backend,
117 #[cfg(any(feature = "blocking", feature = "tokio", test))]
118 ErrorRepr::CreateConsole(_) => ErrorKind::CreateConsole,
119 #[cfg(any(feature = "blocking", feature = "tokio", test))]
120 ErrorRepr::Spawn { .. } => ErrorKind::Spawn,
121 #[cfg(any(feature = "blocking", feature = "tokio", test))]
122 ErrorRepr::Resize(_) => ErrorKind::Resize,
123 #[cfg(any(feature = "blocking", feature = "tokio", test))]
124 ErrorRepr::Clear(_) => ErrorKind::Clear,
125 #[cfg(any(feature = "blocking", feature = "tokio", test))]
126 ErrorRepr::UnsupportedFeature { .. } => ErrorKind::UnsupportedFeature,
127 ErrorRepr::InvalidSize { .. } => ErrorKind::InvalidSize,
128 #[cfg(any(feature = "blocking", feature = "tokio", test))]
129 ErrorRepr::Wait(_) => ErrorKind::Wait,
130 #[cfg(any(feature = "blocking", feature = "tokio", test))]
131 ErrorRepr::Kill(_) => ErrorKind::Kill,
132 ErrorRepr::Io(_) => ErrorKind::Io,
133 }
134 }
135
136 #[must_use]
142 pub const fn io_error(&self) -> Option<&io::Error> {
143 match &self.repr {
144 #[cfg(any(feature = "blocking", feature = "tokio", test))]
145 ErrorRepr::CreateConsole(source)
146 | ErrorRepr::Resize(source)
147 | ErrorRepr::Clear(source)
148 | ErrorRepr::Wait(source)
149 | ErrorRepr::Kill(source)
150 | ErrorRepr::Spawn { source, .. } => Some(source),
151 ErrorRepr::Io(source) => Some(source),
152 #[cfg(any(feature = "blocking", feature = "tokio", test))]
153 ErrorRepr::UnsupportedFeature { .. } => None,
154 ErrorRepr::Backend(_) | ErrorRepr::InvalidSize { .. } => None,
155 }
156 }
157
158 #[must_use]
160 pub const fn backend_error(&self) -> Option<&BackendError> {
161 match &self.repr {
162 ErrorRepr::Backend(source) => Some(source),
163 #[cfg(any(feature = "blocking", feature = "tokio", test))]
164 ErrorRepr::CreateConsole(_)
165 | ErrorRepr::Spawn { .. }
166 | ErrorRepr::Resize(_)
167 | ErrorRepr::Clear(_)
168 | ErrorRepr::UnsupportedFeature { .. }
169 | ErrorRepr::Wait(_)
170 | ErrorRepr::Kill(_) => None,
171 ErrorRepr::InvalidSize { .. } | ErrorRepr::Io(_) => None,
172 }
173 }
174
175 #[cfg(any(feature = "blocking", feature = "tokio", test))]
176 pub(crate) const fn create_console(source: io::Error) -> Self {
177 Self {
178 repr: ErrorRepr::CreateConsole(source),
179 }
180 }
181
182 #[cfg(any(feature = "blocking", feature = "tokio", test))]
183 pub(crate) const fn spawn(program: OsString, source: io::Error) -> Self {
184 Self {
185 repr: ErrorRepr::Spawn { program, source },
186 }
187 }
188
189 #[cfg(any(feature = "blocking", feature = "tokio", test))]
190 pub(crate) const fn resize(source: io::Error) -> Self {
191 Self {
192 repr: ErrorRepr::Resize(source),
193 }
194 }
195
196 #[cfg(any(feature = "blocking", feature = "tokio", test))]
197 pub(crate) const fn clear(source: io::Error) -> Self {
198 Self {
199 repr: ErrorRepr::Clear(source),
200 }
201 }
202
203 #[cfg(any(feature = "blocking", feature = "tokio", test))]
204 pub(crate) const fn unsupported_feature(feature: &'static str) -> Self {
205 Self {
206 repr: ErrorRepr::UnsupportedFeature { feature },
207 }
208 }
209
210 pub(crate) const fn invalid_size(rows: u16, cols: u16) -> Self {
211 Self {
212 repr: ErrorRepr::InvalidSize { rows, cols },
213 }
214 }
215
216 #[cfg(any(feature = "blocking", feature = "tokio", test))]
217 pub(crate) const fn wait(source: io::Error) -> Self {
218 Self {
219 repr: ErrorRepr::Wait(source),
220 }
221 }
222
223 #[cfg(any(feature = "blocking", feature = "tokio", test))]
224 pub(crate) const fn kill(source: io::Error) -> Self {
225 Self {
226 repr: ErrorRepr::Kill(source),
227 }
228 }
229}
230
231impl fmt::Display for Error {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 self.repr.fmt(f)
234 }
235}
236
237impl fmt::Debug for Error {
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 f.debug_struct("Error")
240 .field("kind", &self.kind())
241 .field("context", &self.repr)
242 .finish()
243 }
244}
245
246impl std::error::Error for Error {
247 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
248 self.repr.source()
249 }
250}
251
252impl From<BackendError> for Error {
253 fn from(source: BackendError) -> Self {
254 Self {
255 repr: ErrorRepr::Backend(source),
256 }
257 }
258}
259
260impl From<io::Error> for Error {
261 fn from(source: io::Error) -> Self {
262 Self {
263 repr: ErrorRepr::Io(source),
264 }
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
270#[non_exhaustive]
271pub enum BackendErrorKind {
272 DllNotFound,
274 MissingExport,
276 OpenConsoleMissing,
278 VersionMismatch,
280 Unsupported,
282}
283
284pub struct BackendError {
289 repr: BackendErrorRepr,
290}
291
292#[derive(Debug, thiserror::Error)]
293enum BackendErrorRepr {
294 #[error("conpty.dll not found in `{}`", .dir.display())]
295 DllNotFound { dir: PathBuf, source: io::Error },
296 #[error("`{}` is missing required export `{symbol}`", .dll.display())]
297 MissingExport { dll: PathBuf, symbol: &'static str },
298 #[error("OpenConsole.exe not found next to `{}`", .dll.display())]
299 OpenConsoleMissing { dll: PathBuf },
300 #[error(
301 "version mismatch: `{}` reports {dll_version} \
302 but its OpenConsole.exe reports {exe_version}",
303 .dll.display()
304 )]
305 VersionMismatch {
306 dll: PathBuf,
307 dll_version: String,
308 exe_version: String,
309 },
310 #[error(
311 "ConPTY is not available on this version of Windows; \
312 Windows 10 1809 (build 17763) or later is required"
313 )]
314 Unsupported,
315}
316
317impl BackendError {
318 #[must_use]
320 pub const fn kind(&self) -> BackendErrorKind {
321 match self.repr {
322 BackendErrorRepr::DllNotFound { .. } => BackendErrorKind::DllNotFound,
323 BackendErrorRepr::MissingExport { .. } => BackendErrorKind::MissingExport,
324 BackendErrorRepr::OpenConsoleMissing { .. } => BackendErrorKind::OpenConsoleMissing,
325 BackendErrorRepr::VersionMismatch { .. } => BackendErrorKind::VersionMismatch,
326 BackendErrorRepr::Unsupported => BackendErrorKind::Unsupported,
327 }
328 }
329
330 #[must_use]
332 pub const fn io_error(&self) -> Option<&io::Error> {
333 match &self.repr {
334 BackendErrorRepr::DllNotFound { source, .. } => Some(source),
335 BackendErrorRepr::MissingExport { .. }
336 | BackendErrorRepr::OpenConsoleMissing { .. }
337 | BackendErrorRepr::VersionMismatch { .. }
338 | BackendErrorRepr::Unsupported => None,
339 }
340 }
341
342 pub(crate) const fn dll_not_found(dir: PathBuf, source: io::Error) -> Self {
343 Self {
344 repr: BackendErrorRepr::DllNotFound { dir, source },
345 }
346 }
347
348 pub(crate) const fn missing_export(dll: PathBuf, symbol: &'static str) -> Self {
349 Self {
350 repr: BackendErrorRepr::MissingExport { dll, symbol },
351 }
352 }
353
354 pub(crate) const fn open_console_missing(dll: PathBuf) -> Self {
355 Self {
356 repr: BackendErrorRepr::OpenConsoleMissing { dll },
357 }
358 }
359
360 pub(crate) const fn version_mismatch(
361 dll: PathBuf,
362 dll_version: String,
363 exe_version: String,
364 ) -> Self {
365 Self {
366 repr: BackendErrorRepr::VersionMismatch {
367 dll,
368 dll_version,
369 exe_version,
370 },
371 }
372 }
373
374 pub(crate) const fn unsupported() -> Self {
375 Self {
376 repr: BackendErrorRepr::Unsupported,
377 }
378 }
379}
380
381impl fmt::Display for BackendError {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 self.repr.fmt(f)
384 }
385}
386
387impl fmt::Debug for BackendError {
388 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
389 f.debug_struct("BackendError")
390 .field("kind", &self.kind())
391 .field("context", &self.repr)
392 .finish()
393 }
394}
395
396impl std::error::Error for BackendError {
397 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
398 self.repr.source()
399 }
400}
401
402#[cfg(test)]
403#[path = "error_tests.rs"]
404mod tests;