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 #[must_use]
101 pub const fn kind(&self) -> ErrorKind {
102 match self.repr {
103 ErrorRepr::Backend(_) => ErrorKind::Backend,
104 #[cfg(any(feature = "blocking", feature = "tokio", test))]
105 ErrorRepr::CreateConsole(_) => ErrorKind::CreateConsole,
106 #[cfg(any(feature = "blocking", feature = "tokio", test))]
107 ErrorRepr::Spawn { .. } => ErrorKind::Spawn,
108 #[cfg(any(feature = "blocking", feature = "tokio", test))]
109 ErrorRepr::Resize(_) => ErrorKind::Resize,
110 #[cfg(any(feature = "blocking", feature = "tokio", test))]
111 ErrorRepr::Clear(_) => ErrorKind::Clear,
112 #[cfg(any(feature = "blocking", feature = "tokio", test))]
113 ErrorRepr::UnsupportedFeature { .. } => ErrorKind::UnsupportedFeature,
114 ErrorRepr::InvalidSize { .. } => ErrorKind::InvalidSize,
115 #[cfg(any(feature = "blocking", feature = "tokio", test))]
116 ErrorRepr::Wait(_) => ErrorKind::Wait,
117 #[cfg(any(feature = "blocking", feature = "tokio", test))]
118 ErrorRepr::Kill(_) => ErrorKind::Kill,
119 ErrorRepr::Io(_) => ErrorKind::Io,
120 }
121 }
122
123 #[must_use]
129 pub const fn io_error(&self) -> Option<&io::Error> {
130 match &self.repr {
131 #[cfg(any(feature = "blocking", feature = "tokio", test))]
132 ErrorRepr::CreateConsole(source)
133 | ErrorRepr::Resize(source)
134 | ErrorRepr::Clear(source)
135 | ErrorRepr::Wait(source)
136 | ErrorRepr::Kill(source)
137 | ErrorRepr::Spawn { source, .. } => Some(source),
138 ErrorRepr::Io(source) => Some(source),
139 #[cfg(any(feature = "blocking", feature = "tokio", test))]
140 ErrorRepr::UnsupportedFeature { .. } => None,
141 ErrorRepr::Backend(_) | ErrorRepr::InvalidSize { .. } => None,
142 }
143 }
144
145 #[must_use]
147 pub const fn backend_error(&self) -> Option<&BackendError> {
148 match &self.repr {
149 ErrorRepr::Backend(source) => Some(source),
150 #[cfg(any(feature = "blocking", feature = "tokio", test))]
151 ErrorRepr::CreateConsole(_)
152 | ErrorRepr::Spawn { .. }
153 | ErrorRepr::Resize(_)
154 | ErrorRepr::Clear(_)
155 | ErrorRepr::UnsupportedFeature { .. }
156 | ErrorRepr::Wait(_)
157 | ErrorRepr::Kill(_) => None,
158 ErrorRepr::InvalidSize { .. } | ErrorRepr::Io(_) => None,
159 }
160 }
161
162 #[cfg(any(feature = "blocking", feature = "tokio", test))]
163 pub(crate) const fn create_console(source: io::Error) -> Self {
164 Self {
165 repr: ErrorRepr::CreateConsole(source),
166 }
167 }
168
169 #[cfg(any(feature = "blocking", feature = "tokio", test))]
170 pub(crate) const fn spawn(program: OsString, source: io::Error) -> Self {
171 Self {
172 repr: ErrorRepr::Spawn { program, source },
173 }
174 }
175
176 #[cfg(any(feature = "blocking", feature = "tokio", test))]
177 pub(crate) const fn resize(source: io::Error) -> Self {
178 Self {
179 repr: ErrorRepr::Resize(source),
180 }
181 }
182
183 #[cfg(any(feature = "blocking", feature = "tokio", test))]
184 pub(crate) const fn clear(source: io::Error) -> Self {
185 Self {
186 repr: ErrorRepr::Clear(source),
187 }
188 }
189
190 #[cfg(any(feature = "blocking", feature = "tokio", test))]
191 pub(crate) const fn unsupported_feature(feature: &'static str) -> Self {
192 Self {
193 repr: ErrorRepr::UnsupportedFeature { feature },
194 }
195 }
196
197 pub(crate) const fn invalid_size(rows: u16, cols: u16) -> Self {
198 Self {
199 repr: ErrorRepr::InvalidSize { rows, cols },
200 }
201 }
202
203 #[cfg(any(feature = "blocking", feature = "tokio", test))]
204 pub(crate) const fn wait(source: io::Error) -> Self {
205 Self {
206 repr: ErrorRepr::Wait(source),
207 }
208 }
209
210 #[cfg(any(feature = "blocking", feature = "tokio", test))]
211 pub(crate) const fn kill(source: io::Error) -> Self {
212 Self {
213 repr: ErrorRepr::Kill(source),
214 }
215 }
216}
217
218impl fmt::Display for Error {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 self.repr.fmt(f)
221 }
222}
223
224impl fmt::Debug for Error {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.debug_struct("Error")
227 .field("kind", &self.kind())
228 .field("context", &self.repr)
229 .finish()
230 }
231}
232
233impl std::error::Error for Error {
234 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
235 self.repr.source()
236 }
237}
238
239impl From<BackendError> for Error {
240 fn from(source: BackendError) -> Self {
241 Self {
242 repr: ErrorRepr::Backend(source),
243 }
244 }
245}
246
247impl From<io::Error> for Error {
248 fn from(source: io::Error) -> Self {
249 Self {
250 repr: ErrorRepr::Io(source),
251 }
252 }
253}
254
255#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
257#[non_exhaustive]
258pub enum BackendErrorKind {
259 DllNotFound,
261 MissingExport,
263 OpenConsoleMissing,
265 VersionMismatch,
267 Unsupported,
269}
270
271pub struct BackendError {
276 repr: BackendErrorRepr,
277}
278
279#[derive(Debug, thiserror::Error)]
280enum BackendErrorRepr {
281 #[error("conpty.dll not found in `{}`", .dir.display())]
282 DllNotFound { dir: PathBuf, source: io::Error },
283 #[error("`{}` is missing required export `{symbol}`", .dll.display())]
284 MissingExport { dll: PathBuf, symbol: &'static str },
285 #[error("OpenConsole.exe not found next to `{}`", .dll.display())]
286 OpenConsoleMissing { dll: PathBuf },
287 #[error(
288 "version mismatch: `{}` reports {dll_version} \
289 but its OpenConsole.exe reports {exe_version}",
290 .dll.display()
291 )]
292 VersionMismatch {
293 dll: PathBuf,
294 dll_version: String,
295 exe_version: String,
296 },
297 #[error(
298 "ConPTY is not available on this version of Windows; \
299 Windows 10 1809 (build 17763) or later is required"
300 )]
301 Unsupported,
302}
303
304impl BackendError {
305 #[must_use]
307 pub const fn kind(&self) -> BackendErrorKind {
308 match self.repr {
309 BackendErrorRepr::DllNotFound { .. } => BackendErrorKind::DllNotFound,
310 BackendErrorRepr::MissingExport { .. } => BackendErrorKind::MissingExport,
311 BackendErrorRepr::OpenConsoleMissing { .. } => BackendErrorKind::OpenConsoleMissing,
312 BackendErrorRepr::VersionMismatch { .. } => BackendErrorKind::VersionMismatch,
313 BackendErrorRepr::Unsupported => BackendErrorKind::Unsupported,
314 }
315 }
316
317 #[must_use]
319 pub const fn io_error(&self) -> Option<&io::Error> {
320 match &self.repr {
321 BackendErrorRepr::DllNotFound { source, .. } => Some(source),
322 BackendErrorRepr::MissingExport { .. }
323 | BackendErrorRepr::OpenConsoleMissing { .. }
324 | BackendErrorRepr::VersionMismatch { .. }
325 | BackendErrorRepr::Unsupported => None,
326 }
327 }
328
329 pub(crate) const fn dll_not_found(dir: PathBuf, source: io::Error) -> Self {
330 Self {
331 repr: BackendErrorRepr::DllNotFound { dir, source },
332 }
333 }
334
335 pub(crate) const fn missing_export(dll: PathBuf, symbol: &'static str) -> Self {
336 Self {
337 repr: BackendErrorRepr::MissingExport { dll, symbol },
338 }
339 }
340
341 pub(crate) const fn open_console_missing(dll: PathBuf) -> Self {
342 Self {
343 repr: BackendErrorRepr::OpenConsoleMissing { dll },
344 }
345 }
346
347 pub(crate) const fn version_mismatch(
348 dll: PathBuf,
349 dll_version: String,
350 exe_version: String,
351 ) -> Self {
352 Self {
353 repr: BackendErrorRepr::VersionMismatch {
354 dll,
355 dll_version,
356 exe_version,
357 },
358 }
359 }
360
361 pub(crate) const fn unsupported() -> Self {
362 Self {
363 repr: BackendErrorRepr::Unsupported,
364 }
365 }
366}
367
368impl fmt::Display for BackendError {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 self.repr.fmt(f)
371 }
372}
373
374impl fmt::Debug for BackendError {
375 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
376 f.debug_struct("BackendError")
377 .field("kind", &self.kind())
378 .field("context", &self.repr)
379 .finish()
380 }
381}
382
383impl std::error::Error for BackendError {
384 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
385 self.repr.source()
386 }
387}
388
389#[cfg(test)]
390#[path = "error_tests.rs"]
391mod tests;