cfgmatic-source 2.2.0

Configuration sources (file, env, remote) for cfgmatic framework
Documentation
//! Error types for source operations.
//!
//! This module provides error types used throughout the crate:
//!
//! - [`SourceError`] - Main error type for all source operations
//! - [`Result`] - Result type alias using [`SourceError`]

use std::path::PathBuf;

/// Error type for source operations.
///
/// This enum covers all possible errors that can occur
/// during source loading, parsing, and processing.
#[derive(Debug, thiserror::Error)]
pub enum SourceError {
    /// Source not found.
    #[error("Source not found: {0}")]
    NotFound(String),

    /// Failed to read source.
    #[error("Failed to read source: {0}")]
    ReadFailed(String),

    /// Failed to parse source content.
    #[error("Parse error in {format} at {path}: {message}")]
    ParseFailed {
        /// Path or identifier of the source.
        path: String,
        /// Format that was being parsed.
        format: String,
        /// Error message.
        message: String,
    },

    /// Invalid format detected.
    #[error("Invalid format: expected {expected}, found {found}")]
    InvalidFormat {
        /// Expected format.
        expected: String,
        /// Found format.
        found: String,
    },

    /// IO error.
    #[error("IO error: {0}")]
    Io(String),

    /// Environment variable error.
    #[error("Environment variable error: {0}")]
    EnvVar(String),

    /// Network error for remote sources.
    #[error("Network error: {0}")]
    Network(String),

    /// Serialization/deserialization error.
    #[error("Serialization error: {0}")]
    Serialization(String),

    /// Invalid source path.
    #[error("Invalid path: {0}")]
    InvalidPath(PathBuf),

    /// Unsupported operation.
    #[error("Unsupported operation: {0}")]
    Unsupported(String),

    /// Source validation failed.
    #[error("Validation failed: {0}")]
    Validation(String),

    /// Custom error.
    #[error("{0}")]
    Custom(String),
}

/// Result type alias.
pub type Result<T> = std::result::Result<T, SourceError>;

impl SourceError {
    /// Create a not found error.
    #[must_use]
    pub fn not_found(source: &str) -> Self {
        Self::NotFound(source.to_string())
    }

    /// Create a read failed error.
    #[must_use]
    pub fn read_failed(source: &str) -> Self {
        Self::ReadFailed(source.to_string())
    }

    /// Create a parse failed error.
    #[must_use]
    pub fn parse_failed(path: &str, format: &str, message: &str) -> Self {
        Self::ParseFailed {
            path: path.to_string(),
            format: format.to_string(),
            message: message.to_string(),
        }
    }

    /// Create an invalid format error.
    #[must_use]
    pub fn invalid_format(expected: &str, found: &str) -> Self {
        Self::InvalidFormat {
            expected: expected.to_string(),
            found: found.to_string(),
        }
    }

    /// Create an IO error.
    #[must_use]
    pub fn io(message: &str) -> Self {
        Self::Io(message.to_string())
    }

    /// Create an environment variable error.
    #[must_use]
    pub fn env_var(message: &str) -> Self {
        Self::EnvVar(message.to_string())
    }

    /// Create a network error.
    #[must_use]
    pub fn network(message: &str) -> Self {
        Self::Network(message.to_string())
    }

    /// Create a serialization error.
    #[must_use]
    pub fn serialization(message: &str) -> Self {
        Self::Serialization(message.to_string())
    }

    /// Create an invalid path error.
    #[must_use]
    pub fn invalid_path(path: PathBuf) -> Self {
        Self::InvalidPath(path)
    }

    /// Create an unsupported operation error.
    #[must_use]
    pub fn unsupported(operation: &str) -> Self {
        Self::Unsupported(operation.to_string())
    }

    /// Create a validation error.
    #[must_use]
    pub fn validation(message: &str) -> Self {
        Self::Validation(message.to_string())
    }

    /// Create a custom error.
    #[must_use]
    pub fn custom(message: &str) -> Self {
        Self::Custom(message.to_string())
    }

    /// Check if this is a not found error.
    #[must_use]
    pub const fn is_not_found(&self) -> bool {
        matches!(self, Self::NotFound(_))
    }

    /// Check if this is a parse error.
    #[must_use]
    pub const fn is_parse_failed(&self) -> bool {
        matches!(self, Self::ParseFailed { .. })
    }

    /// Check if this is a network error.
    #[must_use]
    pub const fn is_network(&self) -> bool {
        matches!(self, Self::Network(_))
    }

    /// Check if this is an IO error.
    #[must_use]
    pub const fn is_io(&self) -> bool {
        matches!(self, Self::Io(_))
    }
}

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

#[cfg(feature = "json")]
impl From<serde_json::Error> for SourceError {
    fn from(err: serde_json::Error) -> Self {
        Self::Serialization(err.to_string())
    }
}

impl From<std::env::VarError> for SourceError {
    fn from(err: std::env::VarError) -> Self {
        Self::EnvVar(err.to_string())
    }
}

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

    #[test]
    fn test_error_not_found() {
        let err = SourceError::not_found("config.toml");
        assert!(err.is_not_found());
        assert!(err.to_string().contains("not found"));
    }

    #[test]
    fn test_error_parse_failed() {
        let err = SourceError::parse_failed("/etc/config.toml", "toml", "invalid syntax");
        assert!(err.is_parse_failed());
        assert!(err.to_string().contains("Parse error"));
    }

    #[test]
    fn test_error_invalid_format() {
        let err = SourceError::invalid_format("toml", "yaml");
        assert!(err.to_string().contains("Invalid format"));
    }

    #[test]
    fn test_error_io() {
        let err = SourceError::io("permission denied");
        assert!(err.is_io());
    }

    #[test]
    fn test_error_invalid_path() {
        let path = PathBuf::from("/invalid/path");
        let err = SourceError::invalid_path(path.clone());
        assert!(matches!(err, SourceError::InvalidPath(p) if p == path));
    }

    #[test]
    fn test_error_from_io() {
        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
        let err: SourceError = io_err.into();
        assert!(err.is_io());
    }

    #[test]
    fn test_error_from_var_error() {
        let var_err = std::env::VarError::NotPresent;
        let err: SourceError = var_err.into();
        assert!(matches!(err, SourceError::EnvVar(_)));
    }
}