From 91bf01a9775ac1d2e2633f8702481d023d42fb24 Mon Sep 17 00:00:00 2001 From: Chet Nichols III Date: Sat, 22 Nov 2025 00:06:45 -0800 Subject: [PATCH] feat for issue #809: impl std::error::Error for cursive::theme::Error https://github.com/gyscos/cursive/issues/809 had been opened inquiring about implementing `std::error::Error` for `cursive::theme::Error` so they could do something like this: ``` fn load_theme(siv: &mut CursiveRunnable, path: PathBuf) -> anyhow::Result<()> { siv.load_theme_file(path)?; Ok(()) } ``` Instead of having to do this: ``` fn load_theme(siv: &mut CursiveRunnable, path: PathBuf) -> anyhow::Result<()> { match siv.load_theme_file(path) { Ok(_) => Ok(()), Err(e) => match e { cursive::theme::Error::Io(e) => Err(e.into()), cursive::theme::Error::Parse(e) => Err(e.into()), } } } ``` It seemed like low hanging fruit, so I figured I'd offer up a PR. Included some tests for the different `Error::Io`, `Error::Parse` and `?` cases. Thank you for considering this change! --- cursive-core/src/theme.rs | 85 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/cursive-core/src/theme.rs b/cursive-core/src/theme.rs index a26127b9..46468756 100644 --- a/cursive-core/src/theme.rs +++ b/cursive-core/src/theme.rs @@ -181,6 +181,30 @@ impl From for Error { } } +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Error::Io(err) => write!(f, "An error occured when reading the theme file: {}", err), + #[cfg(feature = "toml")] + Error::Parse(err) => write!( + f, + "An error occured while parsing the theme TOML content: {}", + err + ), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Error::Io(err) => Some(err), + #[cfg(feature = "toml")] + Error::Parse(err) => Some(err), + } + } +} + /// Loads a theme from file. /// /// Must have the `toml` feature enabled. @@ -215,3 +239,64 @@ pub fn load_toml(content: &str) -> Result { pub fn load_default() -> Theme { Theme::default() } + +#[cfg(test)] +mod tests { + use super::*; + use std::error::Error as _; + + #[test] + // Make sure Error::Io correctly implements std::error::Error. + fn test_io_error_implements_std_error() { + let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found"); + let theme_error = Error::Io(io_error); + + // Should be able to use as std::error::Error. + let _error_trait: &dyn std::error::Error = &theme_error; + + // Should have a Display implementation. + let display_str = format!("{}", theme_error); + assert!(display_str.contains("when reading the theme file")); + + // And a source. + assert!(theme_error.source().is_some()); + } + + #[cfg(feature = "toml")] + #[test] + // Make sure Error::Parse correctly implements std::error::Error. + fn test_parse_error_implements_std_error() { + let parse_result = load_toml("invalid toml {{{"); + assert!(parse_result.is_err()); + + let theme_error = parse_result.unwrap_err(); + + // Should be able to use as std::error::Error. + let _error_trait: &dyn std::error::Error = &theme_error; + + // Should have a Display implementation. + let display_str = format!("{}", theme_error); + assert!(display_str.contains("while parsing the theme")); + + // And a source. + assert!(theme_error.source().is_some()); + } + + #[cfg(feature = "toml")] + #[test] + // Make sure ? works for returning Error results. + fn test_error_works_with_question_mark_operator() { + fn load_theme_simple(content: &str) -> Result> { + let theme = load_toml(content)?; + Ok(theme) + } + + // Test with "invalid" TOML. + let result = load_theme_simple("invalid toml {{{"); + assert!(result.is_err()); + + // Test with valid TOML. + let result = load_theme_simple("shadow = false"); + assert!(result.is_ok()); + } +}