use thiserror::Error;
#[derive(Error, Debug)]
pub enum StoreError {
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("Item not found: {0}")]
NotFound(String),
#[error("Validation error: {0}")]
ValidationError(String),
#[error("JSON error: {0}")]
JsonError(#[from] serde_json::Error),
#[error("YAML error: {0}")]
YamlError(String),
#[error("Frontmatter error: {0}")]
FrontmatterError(String),
#[error("Item type not found: {0}")]
ItemTypeNotFound(String),
#[error("Feature not enabled: {0}")]
FeatureNotEnabled(String),
#[error("Item already deleted: {0}")]
AlreadyDeleted(String),
#[error("Item is not deleted: {0}")]
NotDeleted(String),
#[error("Invalid status '{status}'. Allowed: {allowed:?}")]
InvalidStatus {
status: String,
allowed: Vec<String>,
},
#[error("Invalid priority {priority}. Must be between 1 and {max}")]
InvalidPriority { priority: u32, max: u32 },
#[error("Item already exists: {0}")]
AlreadyExists(String),
#[error("Item is deleted: {0}")]
IsDeleted(String),
#[error("Cannot move item to same location")]
SameLocation,
#[error("{0}")]
Custom(String),
}
impl StoreError {
pub fn custom(msg: impl Into<String>) -> Self {
StoreError::Custom(msg.into())
}
pub fn not_found(id: impl Into<String>) -> Self {
StoreError::NotFound(id.into())
}
pub fn validation(msg: impl Into<String>) -> Self {
StoreError::ValidationError(msg.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_store_error_custom() {
let err = StoreError::custom("something went wrong");
assert!(matches!(err, StoreError::Custom(_)));
assert_eq!(format!("{err}"), "something went wrong");
}
#[test]
fn test_store_error_not_found() {
let err = StoreError::not_found("issue-123");
assert!(matches!(err, StoreError::NotFound(_)));
assert_eq!(format!("{err}"), "Item not found: issue-123");
}
#[test]
fn test_store_error_validation() {
let err = StoreError::validation("title is required");
assert!(matches!(err, StoreError::ValidationError(_)));
assert_eq!(format!("{err}"), "Validation error: title is required");
}
#[test]
fn test_store_error_invalid_status() {
let err = StoreError::InvalidStatus {
status: "invalid".to_string(),
allowed: vec!["open".to_string(), "closed".to_string()],
};
let display = format!("{err}");
assert!(display.contains("Invalid status 'invalid'"));
assert!(display.contains("open"));
assert!(display.contains("closed"));
}
#[test]
fn test_store_error_invalid_priority() {
let err = StoreError::InvalidPriority {
priority: 10,
max: 3,
};
let display = format!("{err}");
assert!(display.contains("Invalid priority 10"));
assert!(display.contains("between 1 and 3"));
}
#[test]
fn test_store_error_already_exists() {
let err = StoreError::AlreadyExists("abc-123".to_string());
assert_eq!(format!("{err}"), "Item already exists: abc-123");
}
#[test]
fn test_store_error_is_deleted() {
let err = StoreError::IsDeleted("abc-123".to_string());
assert_eq!(format!("{err}"), "Item is deleted: abc-123");
}
#[test]
fn test_store_error_from_io_error() {
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let err = StoreError::from(io_err);
assert!(matches!(err, StoreError::IoError(_)));
let display = format!("{err}");
assert!(display.contains("IO error"));
}
#[test]
fn test_store_error_custom_with_string() {
let err = StoreError::custom(String::from("dynamic error"));
assert_eq!(format!("{err}"), "dynamic error");
}
#[test]
fn test_store_error_not_found_with_string() {
let err = StoreError::not_found(String::from("doc-xyz"));
assert_eq!(format!("{err}"), "Item not found: doc-xyz");
}
#[test]
fn test_store_error_validation_with_string() {
let err = StoreError::validation(String::from("bad input"));
assert_eq!(format!("{err}"), "Validation error: bad input");
}
}