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}