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}