From 843b3db081aceb5baeb51031a3f73e51c5bbba1e Mon Sep 17 00:00:00 2001 From: dailz Date: Fri, 10 Apr 2026 21:18:20 +0800 Subject: [PATCH] feat(core): define error types with thiserror Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus --- crates/core/src/error.rs | 165 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/crates/core/src/error.rs b/crates/core/src/error.rs index ef7e631..751e172 100644 --- a/crates/core/src/error.rs +++ b/crates/core/src/error.rs @@ -1 +1,164 @@ -// Task 3 will implement error types +use std::path::PathBuf; + +#[derive(Debug, thiserror::Error)] +pub enum CoreError { + #[error("I/O error: {context}: {source}")] + Io { + #[source] + source: std::io::Error, + context: String, + }, + + #[error("parse error at line {line}: {source}")] + Parse { + #[source] + source: serde_json::Error, + line: u64, + }, + + #[error("invalid search pattern '{pattern}': {reason}")] + Search { pattern: String, reason: String }, + + #[error("index error: {message}")] + Index { message: String }, + + #[error("config error: {source}")] + Config { + #[source] + source: toml::de::Error, + }, + + #[error("encoding error at line {line}: {bytes:?}")] + Encoding { line: u64, bytes: Vec }, + + #[error("file watch error: {0}")] + Watch(String), + + #[error("file not found: {path:?}")] + FileNotFound { path: PathBuf }, + + #[error("{0}")] + Other(String), +} + +pub type Result = std::result::Result; + +impl From for CoreError { + fn from(err: std::io::Error) -> Self { + CoreError::Io { + source: err, + context: "I/O operation".into(), + } + } +} + +impl From for CoreError { + fn from(err: serde_json::Error) -> Self { + CoreError::Parse { + source: err, + line: 0, + } + } +} + +impl From for CoreError { + fn from(err: toml::de::Error) -> Self { + CoreError::Config { source: err } + } +} + +impl From for CoreError { + fn from(err: notify::Error) -> Self { + CoreError::Watch(err.to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_io_error_display() { + let err = CoreError::Io { + source: std::io::Error::new(std::io::ErrorKind::NotFound, "file missing"), + context: "reading config".into(), + }; + let msg = err.to_string(); + assert!( + msg.contains("reading config"), + "display should contain context" + ); + assert!( + msg.contains("file missing"), + "display should contain source message" + ); + } + + #[test] + fn test_parse_error_display() { + let json_err: serde_json::Error = + serde_json::from_str::("{bad}").unwrap_err(); + let err = CoreError::Parse { + source: json_err, + line: 42, + }; + let msg = err.to_string(); + assert!(msg.contains("42"), "display should contain line number"); + } + + #[test] + fn test_search_error_display() { + let err = CoreError::Search { + pattern: "[invalid".into(), + reason: "unclosed bracket".into(), + }; + let msg = err.to_string(); + assert!(msg.contains("[invalid"), "display should contain pattern"); + assert!( + msg.contains("unclosed bracket"), + "display should contain reason" + ); + } + + #[test] + fn test_from_io_error() { + let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied"); + let core_err: CoreError = io_err.into(); + match core_err { + CoreError::Io { context, .. } => assert_eq!(context, "I/O operation"), + other => panic!("expected Io variant, got {other:?}"), + } + } + + #[test] + fn test_from_notify_error() { + let notify_err = notify::Error::watch_not_found(); + let core_err: CoreError = notify_err.into(); + match core_err { + CoreError::Watch(msg) => assert!(!msg.is_empty()), + other => panic!("expected Watch variant, got {other:?}"), + } + } + + #[test] + fn test_result_type_alias() { + let ok: Result = Ok(42); + let err: Result = Err(CoreError::Other("something went wrong".into())); + assert!(ok.is_ok()); + assert!(err.is_err()); + } + + #[test] + fn test_error_source_chain() { + let io_err = std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "unexpected eof"); + let core_err = CoreError::Io { + source: io_err, + context: "test".into(), + }; + let source = std::error::Error::source(&core_err); + assert!( + source.is_some(), + "source() should return the inner io::Error" + ); + } +}