heroforge-core 0.2.2

Pure Rust core library for reading and writing Fossil SCM repositories
Documentation
//! Filesystem-specific error types
//!
//! This module defines error types specific to filesystem operations.
//! All errors wrap the underlying FossilError but provide additional context
//! specific to filesystem operations.

use crate::error::FossilError;
use std::fmt;

/// Filesystem operation result type
pub type FsResult<T> = std::result::Result<T, FsError>;

/// Filesystem-specific errors
#[derive(Debug)]
pub enum FsError {
    /// Path does not exist
    PathNotFound(String),

    /// Alias for PathNotFound (matches spec naming)
    NotFound(String),

    /// Path is a file but directory was expected
    NotADirectory(String),

    /// Path is a directory but file was expected
    NotAFile(String),

    /// Path already exists (when it shouldn't)
    PathAlreadyExists(String),

    /// Permission denied for operation
    PermissionDenied(String),

    /// Invalid path (e.g., contains null bytes)
    InvalidPath(String),

    /// Operation requires write access but repository is read-only
    ReadOnlyRepository,

    /// Cannot modify file at this commit/branch
    CannotModify { path: String, reason: String },

    /// Symlink target is invalid or circular
    SymlinkError(String),

    /// Pattern matching failed
    PatternError(String),

    /// Transaction-related error
    TransactionError(String),

    /// Underlying Fossil repository error
    Repository(FossilError),

    /// IO error
    Io(std::io::Error),

    /// UTF-8 encoding error
    Encoding(String),

    /// Operation not supported
    NotSupported(String),

    /// File exceeds maximum size limit
    FileTooLarge { path: String, size: u64, max: u64 },

    /// Lock acquisition timeout
    LockTimeout,

    /// Commit is currently in progress
    CommitInProgress,

    /// Database/storage error
    DatabaseError(String),

    /// Staging directory error
    StagingError(String),

    /// Other errors
    Other(String),
}

impl From<FossilError> for FsError {
    fn from(err: FossilError) -> Self {
        FsError::Repository(err)
    }
}

impl From<std::io::Error> for FsError {
    fn from(err: std::io::Error) -> Self {
        FsError::Io(err)
    }
}

impl From<std::string::FromUtf8Error> for FsError {
    fn from(err: std::string::FromUtf8Error) -> Self {
        FsError::Encoding(format!("UTF-8 conversion failed: {}", err))
    }
}

impl fmt::Display for FsError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FsError::PathNotFound(path) => write!(f, "Path not found: {}", path),
            FsError::NotFound(path) => write!(f, "Not found: {}", path),
            FsError::NotADirectory(path) => write!(f, "Not a directory: {}", path),
            FsError::NotAFile(path) => write!(f, "Not a file: {}", path),
            FsError::PathAlreadyExists(path) => write!(f, "Path already exists: {}", path),
            FsError::PermissionDenied(msg) => write!(f, "Permission denied: {}", msg),
            FsError::InvalidPath(path) => write!(f, "Invalid path: {}", path),
            FsError::ReadOnlyRepository => {
                write!(
                    f,
                    "Repository is read-only. Use Repository::open_rw() for write access."
                )
            }
            FsError::CannotModify { path, reason } => {
                write!(f, "Cannot modify {}: {}", path, reason)
            }
            FsError::SymlinkError(msg) => write!(f, "Symlink error: {}", msg),
            FsError::PatternError(msg) => write!(f, "Pattern error: {}", msg),
            FsError::TransactionError(msg) => write!(f, "Transaction error: {}", msg),
            FsError::Repository(err) => write!(f, "Repository error: {}", err),
            FsError::Io(err) => write!(f, "IO error: {}", err),
            FsError::Encoding(msg) => write!(f, "Encoding error: {}", msg),
            FsError::NotSupported(msg) => write!(f, "Not supported: {}", msg),
            FsError::FileTooLarge { path, size, max } => {
                write!(
                    f,
                    "File too large: {} ({} bytes, max {} bytes)",
                    path, size, max
                )
            }
            FsError::LockTimeout => write!(f, "Lock acquisition timeout"),
            FsError::CommitInProgress => write!(f, "Commit is currently in progress"),
            FsError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
            FsError::StagingError(msg) => write!(f, "Staging error: {}", msg),
            FsError::Other(msg) => write!(f, "{}", msg),
        }
    }
}

impl std::error::Error for FsError {}

/// Convert FsError to FossilError for compatibility
impl From<FsError> for FossilError {
    fn from(err: FsError) -> Self {
        match err {
            FsError::Repository(fossil_err) => fossil_err,
            other => FossilError::Io(std::io::Error::new(
                std::io::ErrorKind::Other,
                other.to_string(),
            )),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_error_display() {
        let err = FsError::PathNotFound("/tmp/test.txt".to_string());
        assert_eq!(err.to_string(), "Path not found: /tmp/test.txt");

        let err = FsError::ReadOnlyRepository;
        assert!(err.to_string().contains("read-only"));
    }

    #[test]
    fn test_error_conversion() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
        let fs_err = FsError::from(io_err);
        assert!(matches!(fs_err, FsError::Io(_)));
    }
}