aethellib 0.9.6

Composable text generation primitives over target-specific TOML corpora with provenance tracking.
Documentation
//! unified loader error types used across all load entrypoints.

use std::path::Path;

use thiserror::Error;

use crate::corpus::types::Target;

#[derive(Debug, Error)]
/// errors returned by all load entrypoints.
pub enum CorpusLoaderError {
    /// file system read failure with explicit source path context.
    #[error("unable to read toml file '{path}': {source}")]
    Read {
        /// source file path when available.
        path: String,
        /// underlying io source error.
        source: std::io::Error,
    },
    /// toml deserialization or structural parse failure with explicit source path context.
    #[error("unable to parse toml file '{path}': {source}")]
    Parse {
        /// source file path when available.
        path: String,
        /// underlying parse source error.
        source: toml::de::Error,
    },
    /// file target does not match the expected target.
    #[error("target mismatch: expected '{expected}', got '{found}'")]
    TargetMismatch { expected: Target, found: Target },
    /// a load option constraint was violated.
    #[error("{0}")]
    OptionViolation(String),
    /// invalid arguments supplied to a load entrypoint.
    #[error("{0}")]
    InvalidInput(String),
}

impl CorpusLoaderError {
    pub(crate) fn read_for_path(path: impl AsRef<Path>, source: std::io::Error) -> Self {
        CorpusLoaderError::Read {
            path: path.as_ref().to_string_lossy().to_string(),
            source,
        }
    }

    pub(crate) fn parse_for_path(path: impl AsRef<Path>, source: toml::de::Error) -> Self {
        CorpusLoaderError::Parse {
            path: path.as_ref().to_string_lossy().to_string(),
            source,
        }
    }
}

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

    #[test]
    fn test_display_formats_target_mismatch() {
        let error = CorpusLoaderError::TargetMismatch {
            expected: "weapon".to_string(),
            found: "person".to_string(),
        };

        assert_eq!(
            error.to_string(),
            "target mismatch: expected 'weapon', got 'person'"
        );
        assert!(matches!(error, CorpusLoaderError::TargetMismatch { .. }));
    }

    #[test]
    fn test_read_for_path_includes_path_in_message() {
        let error = CorpusLoaderError::read_for_path(
            "custom-file.toml",
            std::io::Error::new(std::io::ErrorKind::NotFound, "missing"),
        );

        assert!(error.to_string().contains("custom-file.toml"));
        assert!(matches!(error, CorpusLoaderError::Read { .. }));
    }

    #[test]
    fn test_option_violation_variant() {
        let error = CorpusLoaderError::OptionViolation("some constraint".to_string());
        assert_eq!(error.to_string(), "some constraint");
        assert!(matches!(error, CorpusLoaderError::OptionViolation(_)));
    }

    #[test]
    fn test_invalid_input_variant() {
        let error = CorpusLoaderError::InvalidInput("bad input".to_string());
        assert_eq!(error.to_string(), "bad input");
        assert!(matches!(error, CorpusLoaderError::InvalidInput(_)));
    }
}