mdstore 1.2.0

A file-based storage engine that stores structured data as Markdown files with YAML frontmatter
Documentation
//! Unified error types for store operations.

use thiserror::Error;

/// Unified error type for store operations.
#[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 {
    /// Create a custom error with a message
    pub fn custom(msg: impl Into<String>) -> Self {
        StoreError::Custom(msg.into())
    }

    /// Create a not found error
    pub fn not_found(id: impl Into<String>) -> Self {
        StoreError::NotFound(id.into())
    }

    /// Create a validation error
    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");
    }
}