bubbletea_rs/
error.rs

1//! Error types for bubbletea-rs.
2//!
3//! This module defines the custom error types used throughout the `bubbletea-rs` library.
4//! All errors are unified under the `Error` enum, providing a consistent way to handle
5//! various failure conditions, from I/O issues to program-specific panics.
6//!
7//! # Error Handling Philosophy
8//!
9//! The `bubbletea-rs` library follows Rust's idiomatic error handling patterns. All
10//! fallible operations return `Result<T, Error>` where `Error` is the main error type
11//! defined in this module. The library uses the `thiserror` crate to provide clear
12//! error messages and convenient error conversions.
13//!
14//! # Common Usage Patterns
15//!
16//! ## Basic Error Handling
17//!
18//! ```no_run
19//! use bubbletea_rs::{Program, Model, Msg, Error, Cmd};
20//!
21//! # struct MyModel;
22//! # impl Model for MyModel {
23//! #     fn init() -> (Self, Option<Cmd>) { (MyModel, None) }
24//! #     fn update(&mut self, _msg: Msg) -> Option<Cmd> { None }
25//! #     fn view(&self) -> String { String::new() }
26//! # }
27//! async fn run_program() -> Result<(), Error> {
28//!     let program = Program::<MyModel>::builder().build()?;
29//!     program.run().await?;
30//!     Ok(())
31//! }
32//! ```
33//!
34//! ## Pattern Matching on Errors
35//!
36//! ```no_run
37//! use bubbletea_rs::Error;
38//!
39//! fn handle_error(err: Error) {
40//!     match err {
41//!         Error::Interrupted => {
42//!             println!("Program was interrupted by user");
43//!         }
44//!         Error::ProgramKilled => {
45//!             println!("Program was explicitly killed");
46//!         }
47//!         Error::Io(io_err) => {
48//!             eprintln!("I/O error occurred: {}", io_err);
49//!         }
50//!         _ => {
51//!             eprintln!("Unexpected error: {}", err);
52//!         }
53//!     }
54//! }
55//! ```
56//!
57//! ## Converting Between Error Types
58//!
59//! The library provides automatic conversions from common error types:
60//!
61//! ```no_run
62//! use bubbletea_rs::Error;
63//! use std::io;
64//!
65//! fn io_operation() -> Result<String, Error> {
66//!     // std::io::Error is automatically converted to Error::Io
67//!     let contents = std::fs::read_to_string("file.txt")?;
68//!     Ok(contents)
69//! }
70//! ```
71
72use thiserror::Error;
73
74/// The main error type for `bubbletea-rs` operations.
75///
76/// This enum encapsulates all possible errors that can occur within the library,
77/// providing detailed information about the cause of the error.
78///
79/// # Examples
80///
81/// ## Creating errors manually
82///
83/// ```
84/// use bubbletea_rs::Error;
85///
86/// // Create a configuration error
87/// let err = Error::Configuration("Invalid input device".to_string());
88///
89/// // Create a program panic error
90/// let panic_err = Error::ProgramPanic("Model update failed".to_string());
91/// ```
92///
93/// ## Working with Results
94///
95/// ```no_run
96/// use bubbletea_rs::{Error, Program, Model, Msg, Cmd};
97///
98/// # struct MyModel;
99/// # impl Model for MyModel {
100/// #     fn init() -> (Self, Option<Cmd>) { (MyModel, None) }
101/// #     fn update(&mut self, _msg: Msg) -> Option<Cmd> { None }
102/// #     fn view(&self) -> String { String::new() }
103/// # }
104/// fn create_program() -> Result<Program<MyModel>, Error> {
105///     Program::<MyModel>::builder().build()
106/// }
107/// ```
108#[derive(Debug, Error)]
109pub enum Error {
110    /// Represents a program panic, similar to Go's `ErrProgramPanic`.
111    /// This error is typically caught by the `Program` and indicates an unrecoverable
112    /// error within the model's `update` or `view` methods.
113    #[error("Program panic: {0}")]
114    ProgramPanic(String),
115
116    /// Indicates that the program was explicitly killed, similar to Go's `ErrProgramKilled`.
117    /// This can happen if the `kill()` method is called on the `Program`.
118    #[error("Program was killed")]
119    ProgramKilled,
120
121    /// Indicates that the program was interrupted, similar to Go's `ErrInterrupted`.
122    /// This typically occurs when an interrupt signal (e.g., Ctrl+C) is received.
123    #[error("Program was interrupted")]
124    Interrupted,
125
126    /// Represents an I/O error, wrapping `std::io::Error`.
127    /// This can occur during terminal operations, file access, or network communication.
128    #[error("I/O error: {0}")]
129    Io(#[from] std::io::Error),
130
131    /// Represents an error specifically related to terminal operations.
132    /// This can include issues with raw mode, alternate screen, or cursor manipulation.
133    #[error("Terminal error: {0}")]
134    Terminal(String),
135
136    /// Indicates a failure when sending a message through an MPSC channel.
137    /// This usually means the receiving end of the channel has been dropped.
138    #[error("Channel send error")]
139    ChannelSend,
140
141    /// Indicates a failure when receiving a message from an MPSC channel.
142    /// This usually means the sending end of the channel has been dropped.
143    #[error("Channel receive error")]
144    ChannelReceive,
145
146    /// Represents a configuration error, typically from the `ProgramBuilder`.
147    /// This can occur if invalid or inconsistent configuration options are provided.
148    #[error("Configuration error: {0}")]
149    Configuration(String),
150
151    /// Represents an error during the execution of a command.
152    /// This can include failures when spawning external processes or issues with command output.
153    #[error("Command execution error: {0}")]
154    CommandExecution(String),
155
156    /// Represents a generic send error, used when a message fails to be sent.
157    #[error("Send error")]
158    SendError,
159
160    /// Bounded channel is full (backpressure). The message could not be enqueued.
161    #[error("Channel is full")]
162    ChannelFull,
163
164    /// Channel is closed; no receivers (or senders) are available.
165    #[error("Channel is closed")]
166    ChannelClosed,
167}
168
169/// Implements conversion from `tokio::sync::mpsc::error::SendError<T>` to `Error::ChannelSend`.
170///
171/// This allows `?` operator to be used with Tokio MPSC send operations, making error
172/// propagation seamless when working with channels.
173///
174/// # Examples
175///
176/// ```no_run
177/// use bubbletea_rs::{Error, Msg};
178/// use tokio::sync::mpsc;
179///
180/// async fn send_message(sender: mpsc::Sender<Msg>, msg: Msg) -> Result<(), Error> {
181///     // The ? operator automatically converts SendError to Error::ChannelSend
182///     sender.send(msg).await?;
183///     Ok(())
184/// }
185/// ```
186impl<T> From<tokio::sync::mpsc::error::SendError<T>> for Error {
187    fn from(_: tokio::sync::mpsc::error::SendError<T>) -> Self {
188        Error::ChannelSend
189    }
190}
191
192/// Implements conversion from `tokio::sync::mpsc::error::TryRecvError` to `Error::ChannelReceive`.
193///
194/// This allows `?` operator to be used with Tokio MPSC try_recv operations.
195///
196/// # Examples
197///
198/// ```no_run
199/// use bubbletea_rs::{Error, Msg};
200/// use tokio::sync::mpsc;
201///
202/// fn try_receive(receiver: &mut mpsc::Receiver<Msg>) -> Result<Option<Msg>, Error> {
203///     // The ? operator automatically converts TryRecvError to Error::ChannelReceive
204///     match receiver.try_recv() {
205///         Ok(msg) => Ok(Some(msg)),
206///         Err(mpsc::error::TryRecvError::Empty) => Ok(None),
207///         Err(e) => Err(e.into()), // Converts to Error::ChannelReceive
208///     }
209/// }
210/// ```
211impl From<tokio::sync::mpsc::error::TryRecvError> for Error {
212    fn from(_: tokio::sync::mpsc::error::TryRecvError) -> Self {
213        Error::ChannelReceive
214    }
215}
216
217/// Implements conversion from `tokio::sync::oneshot::error::RecvError` to `Error::ChannelReceive`.
218///
219/// This allows `?` operator to be used with Tokio Oneshot receive operations.
220///
221/// # Examples
222///
223/// ```no_run
224/// use bubbletea_rs::Error;
225/// use tokio::sync::oneshot;
226///
227/// async fn receive_once(receiver: oneshot::Receiver<String>) -> Result<String, Error> {
228///     // The ? operator automatically converts RecvError to Error::ChannelReceive
229///     let value = receiver.await?;
230///     Ok(value)
231/// }
232/// ```
233impl From<tokio::sync::oneshot::error::RecvError> for Error {
234    fn from(_: tokio::sync::oneshot::error::RecvError) -> Self {
235        Error::ChannelReceive
236    }
237}
238
239/// Implements conversion from `tokio::sync::mpsc::error::TrySendError<T>` to
240/// channel-related errors that preserve whether the channel was full or closed.
241///
242/// This conversion distinguishes between `ChannelFull` (backpressure) and `ChannelClosed`
243/// errors, providing more specific error information than the generic `ChannelSend`.
244///
245/// # Examples
246///
247/// ```no_run
248/// use bubbletea_rs::{Error, Msg};
249/// use tokio::sync::mpsc;
250///
251/// fn try_send(sender: &mpsc::Sender<Msg>, msg: Msg) -> Result<(), Error> {
252///     // Automatically converts to Error::ChannelFull or Error::ChannelClosed
253///     sender.try_send(msg)?;
254///     Ok(())
255/// }
256/// ```
257impl<T> From<tokio::sync::mpsc::error::TrySendError<T>> for Error {
258    fn from(err: tokio::sync::mpsc::error::TrySendError<T>) -> Self {
259        use tokio::sync::mpsc::error::TrySendError;
260        match err {
261            TrySendError::Full(_) => Error::ChannelFull,
262            TrySendError::Closed(_) => Error::ChannelClosed,
263        }
264    }
265}
266
267/// Implements conversion from `String` to `Error::Configuration`.
268///
269/// This provides a convenient way to create configuration errors from string messages.
270///
271/// # Examples
272///
273/// ```
274/// use bubbletea_rs::Error;
275///
276/// fn validate_config(value: u32) -> Result<(), Error> {
277///     if value > 100 {
278///         return Err(format!("Value {} exceeds maximum of 100", value).into());
279///     }
280///     Ok(())
281/// }
282/// ```
283impl From<String> for Error {
284    fn from(msg: String) -> Self {
285        Error::Configuration(msg)
286    }
287}
288
289/// Implements conversion from `&str` to `Error::Configuration`.
290///
291/// This provides a convenient way to create configuration errors from string slices.
292///
293/// # Examples
294///
295/// ```
296/// use bubbletea_rs::Error;
297///
298/// fn check_name(name: &str) -> Result<(), Error> {
299///     if name.is_empty() {
300///         return Err("Name cannot be empty".into());
301///     }
302///     Ok(())
303/// }
304/// ```
305impl From<&str> for Error {
306    fn from(msg: &str) -> Self {
307        Error::Configuration(msg.to_string())
308    }
309}