tron 2.1.0

A rust based template system built for speed and simplicity.
Documentation
//! Error handling for the Tron template engine.
//!
//! This module provides comprehensive error types and utilities for handling
//! template parsing, rendering, and execution errors.

use thiserror::Error;

/// The result type for Tron operations.
///
/// This is a convenience type that wraps `std::result::Result` with [`TronError`]
/// as the error type.
///
/// # Examples
///
/// ```
/// use tron::{Result, TronTemplate};
///
/// fn create_template() -> Result<TronTemplate> {
///     TronTemplate::new("fn @[name]@() { @[body]@ }")
/// }
/// ```
pub type Result<T> = std::result::Result<T, TronError>;

/// Comprehensive error type for all Tron operations.
///
/// This enum covers all possible error conditions that can occur when working
/// with Tron templates, from basic I/O errors to template-specific issues
/// like missing placeholders or invalid syntax.
///
/// # Examples
///
/// ```
/// use tron::{TronError, TronTemplate};
///
/// let result = TronTemplate::new("invalid @[syntax");
/// match result {
///     Err(TronError::InvalidSyntax(msg)) => {
///         println!("Template syntax error: {}", msg);
///     }
///     _ => {}
/// }
/// ```
#[derive(Error, Debug)]
pub enum TronError {
    /// Wraps standard I/O errors that occur during file operations.
    ///
    /// This typically happens when loading templates from files or
    /// writing temporary files for execution.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::{TronTemplate, TronError};
    ///
    /// let result = TronTemplate::from_file("nonexistent.tpl");
    /// match result {
    ///     Err(TronError::Io(io_err)) => {
    ///         println!("File error: {}", io_err);
    ///     }
    ///     _ => {}
    /// }
    /// ```
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),

    /// Indicates a parsing error when processing template content.
    ///
    /// This can occur when template syntax is malformed or when
    /// regex patterns fail to compile.
    #[error("Parse error: {0}")]
    Parse(String),

    /// Indicates that a placeholder was referenced but never defined.
    ///
    /// This error occurs when trying to render a template that has
    /// placeholders without corresponding values.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::{TronTemplate, TronError};
    ///
    /// let mut template = TronTemplate::new("Hello @[name]@!").unwrap();
    /// // template.set("name", "World"); // <- This line is commented out
    /// 
    /// match template.render() {
    ///     Err(TronError::MissingPlaceholder(placeholder)) => {
    ///         println!("Missing placeholder: {}", placeholder);
    ///     }
    ///     _ => {}
    /// }
    /// ```
    #[error("Missing placeholder: {0}")]
    MissingPlaceholder(String),

    /// Indicates invalid template syntax.
    ///
    /// This error occurs when template delimiters are unbalanced,
    /// placeholder names are invalid, or other syntax rules are violated.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::{TronTemplate, TronError};
    ///
    /// let result = TronTemplate::new("@[invalid placeholder name with spaces]@");
    /// match result {
    ///     Err(TronError::InvalidSyntax(msg)) => {
    ///         println!("Syntax error: {}", msg);
    ///     }
    ///     _ => {}
    /// }
    /// ```
    #[error("Invalid template syntax: {0}")]
    InvalidSyntax(String),

    /// Indicates an error during template execution.
    ///
    /// This typically occurs when using the execution feature with rust-script,
    /// such as when rust-script is not installed or when the generated code
    /// has compilation errors.
    ///
    /// # Examples
    ///
    /// ```no_run
    /// use tron::{TronTemplate, TronRef, TronError};
    ///
    /// let mut template = TronTemplate::new("fn main() { @[body]@ }").unwrap();
    /// template.set("body", "invalid rust code").unwrap();
    /// let template_ref = TronRef::new(template);
    ///
    /// # #[cfg(feature = "execute")]
    /// # tokio_test::block_on(async {
    /// match template_ref.execute().await {
    ///     Err(TronError::ExecutionError(msg)) => {
    ///         println!("Execution failed: {}", msg);
    ///     }
    ///     _ => {}
    /// }
    /// # });
    /// ```
    #[error("Execution error: {0}")]
    ExecutionError(String),

    /// Indicates a validation error when checking template correctness.
    ///
    /// This error occurs when template validation detects issues like
    /// circular references, invalid placeholder names, or other structural
    /// problems.
    #[error("Validation error: {0}")]
    ValidationError(String),

    /// Indicates an error during template caching operations.
    ///
    /// This can occur when cache directories can't be created or when
    /// cached templates become corrupted.
    #[error("Cache error: {0}")]
    CacheError(String),
}

impl TronError {
    /// Creates a new parse error with the given message.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::TronError;
    ///
    /// let error = TronError::parse("Invalid regex pattern");
    /// assert!(matches!(error, TronError::Parse(_)));
    /// ```
    pub fn parse<S: Into<String>>(msg: S) -> Self {
        TronError::Parse(msg.into())
    }

    /// Creates a new validation error with the given message.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::TronError;
    ///
    /// let error = TronError::validation("Circular reference detected");
    /// assert!(matches!(error, TronError::ValidationError(_)));
    /// ```
    pub fn validation<S: Into<String>>(msg: S) -> Self {
        TronError::ValidationError(msg.into())
    }

    /// Creates a new execution error with the given message.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::TronError;
    ///
    /// let error = TronError::execution("rust-script not found");
    /// assert!(matches!(error, TronError::ExecutionError(_)));
    /// ```
    pub fn execution<S: Into<String>>(msg: S) -> Self {
        TronError::ExecutionError(msg.into())
    }

    /// Returns `true` if this error is recoverable.
    ///
    /// Some errors, like missing placeholders, can be fixed by providing
    /// the missing values. Others, like invalid syntax, require template
    /// modification.
    ///
    /// # Examples
    ///
    /// ```
    /// use tron::TronError;
    ///
    /// let missing_placeholder = TronError::MissingPlaceholder("name".to_string());
    /// assert!(missing_placeholder.is_recoverable());
    ///
    /// let syntax_error = TronError::InvalidSyntax("Bad delimiter".to_string());
    /// assert!(!syntax_error.is_recoverable());
    /// ```
    pub fn is_recoverable(&self) -> bool {
        match self {
            TronError::MissingPlaceholder(_) => true,
            TronError::ExecutionError(_) => true,
            TronError::CacheError(_) => true,
            TronError::Io(_) => false,
            TronError::Parse(_) => false,
            TronError::InvalidSyntax(_) => false,
            TronError::ValidationError(_) => false,
        }
    }
}

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

    #[test]
    fn test_error_creation() {
        let parse_err = TronError::parse("test message");
        assert!(matches!(parse_err, TronError::Parse(_)));
        
        let validation_err = TronError::validation("validation failed");
        assert!(matches!(validation_err, TronError::ValidationError(_)));
        
        let execution_err = TronError::execution("execution failed");
        assert!(matches!(execution_err, TronError::ExecutionError(_)));
    }

    #[test]
    fn test_error_recoverability() {
        assert!(TronError::MissingPlaceholder("test".to_string()).is_recoverable());
        assert!(TronError::ExecutionError("test".to_string()).is_recoverable());
        assert!(!TronError::InvalidSyntax("test".to_string()).is_recoverable());
        assert!(!TronError::Parse("test".to_string()).is_recoverable());
    }
}