nucleo_picker/error.rs
1//! # Errors during interactive picker usage
2//! This module contains the custom error type [`PickError`] returned by the
3//! [`Picker::pick`](crate::Picker::pick) method, and siblings `Picker::pick_*`. The error type is
4//! comprehensive and the individual picker method used may or may not result in particular error
5//! variants.
6//!
7//! See the [`PickError`] documentation for more detail.
8//!
9//! ## Example
10//! Convert a [`PickError::UserInterrupted`] silently into no choice, propagating any other error as an IO
11//! error. Use with `picker.pick().or_else(suppress_abort)`.
12//! ```
13//! # use nucleo_picker::error::PickError;
14//! # use std::io;
15//! fn suppress_abort<D: Default>(err: PickError) -> Result<D, io::Error> {
16//! match err {
17//! PickError::UserInterrupted => Ok(D::default()),
18//! e => Err(e.into()),
19//! }
20//! }
21//! ```
22
23use std::{convert::Infallible, error::Error as StdError, fmt, io};
24
25/// An error which may occur while running the picker interactively.
26///
27/// This is marked non-exhaustive since more variants may be added in the future. It is recommended
28/// to handle the errors that are relevant to your application and propagate any remaining errors
29/// as an [`io::Error`].
30///
31/// ## Type parameter for `Aborted` variant
32/// The [`PickError::Aborted`] variant can be used by the application to propagate errors to the
33/// picker; the application-defined error type is the type parameter `A`. By default, `A = !`
34/// which means this type of error will *never occur* and can be ignored during pattern matching.
35///
36/// This library will never generate an abort error directly. In order to pass errors downstream to
37/// the picker, the application can define an abort error type using the
38/// [`EventSource::AbortErr`](crate::EventSource::AbortErr) associated type. This associated type
39/// is the same as the type parameter here when used in
40/// [`Picker::pick_with_io`](crate::Picker::pick_with_io).
41///
42/// ## Relationship to `io::Error`
43/// This error type with the default type parameter is (in spirit) an [`io::Error`], but with
44/// more precise variants not present in the default [`io::Error`]. For convenience and
45/// (partial) backwards compatibility, there is a `From<PickError> for io::Error` implementation;
46/// this propagates the underlying IO error and converts any other error message to an
47/// [`io::Error`] using [`io::Error::other`].
48///
49/// There is also a `From<PickError<io::Error>> for io::Error` to handle the common use-case that
50/// the only error type which may occur during standard operation of your application is an IO
51/// error; in this case, the conversion maps both the `Aborted(io::Error)` and `IO(io::Error)`
52/// versions directly to an `io::Error`.
53///
54/// Any other abort error type `A` requires manual handling. The [`PickError::factor`] method
55/// can be used to unwind non-aborted variants into an `io::Error` and extract the
56/// error present in the `Aborted` variant.
57#[derive(Debug)]
58#[non_exhaustive]
59pub enum PickError<A = Infallible> {
60 /// A read or write resulted in an IO error.
61 IO(io::Error),
62 /// A necessary channel disconnected while the picker was still running.
63 Disconnected,
64 /// The picker quit at the user's request.
65 UserInterrupted,
66 /// The picker could not be started since the writer is not interactive.
67 NotInteractive,
68 /// The picker was aborted because of an upstream error.
69 Aborted(A),
70}
71
72impl<A> PickError<A> {
73 /// Convert a `PickError<A>` into either an `Ok(A)` or `Err(PickError<!>)`, for
74 /// convenience of error propogation.
75 ///
76 /// # Example
77 /// Use `factor` to simplify processing of custom [`PickError<A>`]s when you mainly care about
78 /// your application error.
79 /// ```
80 /// # use nucleo_picker::error::PickError;
81 /// use std::{fmt::Display, io};
82 ///
83 /// // Even though `PickError<A>` need not satisfy `Into<io::Error>`, `PickError<!>`
84 /// // always does.
85 /// fn print_or_propogate<A: Display>(pick_err: PickError<A>) -> Result<(), io::Error> {
86 /// let app_err = pick_err.factor()?;
87 /// eprintln!("{app_err}");
88 /// Ok(())
89 /// }
90 /// ```
91 pub fn factor(self) -> Result<A, PickError> {
92 match self {
93 Self::IO(error) => Err(PickError::IO(error)),
94 Self::Disconnected => Err(PickError::Disconnected),
95 Self::UserInterrupted => Err(PickError::UserInterrupted),
96 Self::NotInteractive => Err(PickError::NotInteractive),
97 Self::Aborted(a) => Ok(a),
98 }
99 }
100}
101
102impl<A: fmt::Display> fmt::Display for PickError<A> {
103 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104 match self {
105 Self::IO(error) => error.fmt(f),
106 Self::Disconnected => {
107 f.write_str("event source disconnected while picker was still active")
108 }
109 Self::Aborted(err) => write!(f, "received abort: {err}"),
110 Self::UserInterrupted => f.write_str("keyboard interrupt"),
111 Self::NotInteractive => {
112 f.write_str("picker could not start since the screen is not interactive")
113 }
114 }
115 }
116}
117
118impl<A: StdError> StdError for PickError<A> {}
119
120impl<A> From<io::Error> for PickError<A> {
121 fn from(err: io::Error) -> Self {
122 Self::IO(err)
123 }
124}
125
126// ideally we would like to replace these two conversions with a blanket implementation
127// `impl<A: Into<io::Error>> From<PickError<A>> for io::Error`; however, currently there is no
128// implementation of `From<!> for T` for a variety of reasons; so we are stuck with doing this for
129// maximal compabitility since in the vast majority of cases, `A = !`.
130impl From<PickError> for io::Error {
131 fn from(err: PickError) -> Self {
132 match err {
133 PickError::IO(io_err) => io_err,
134 _ => Self::other(err),
135 }
136 }
137}
138
139impl From<PickError<Self>> for io::Error {
140 fn from(err: PickError<Self>) -> Self {
141 match err.factor() {
142 Ok(io_err) => io_err,
143 Err(pick_err) => pick_err.into(),
144 }
145 }
146}