hen 0.10.0

Run API collections from the command line.
use std::fmt::{self, Display, Formatter};

use pest::RuleType;

/// Alias for results that return a `HenError`.
pub type HenResult<T> = Result<T, HenError>;

/// High-level classification for hen errors to aid structured reporting.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HenErrorKind {
    Cli,
    Input,
    Io,
    Parse,
    Planner,
    Execution,
    Benchmark,
    Prompt,
}

impl HenErrorKind {
    pub fn label(self) -> &'static str {
        match self {
            HenErrorKind::Cli => "CLI",
            HenErrorKind::Input => "Input",
            HenErrorKind::Io => "IO",
            HenErrorKind::Parse => "Parse",
            HenErrorKind::Planner => "Planner",
            HenErrorKind::Execution => "Execution",
            HenErrorKind::Benchmark => "Benchmark",
            HenErrorKind::Prompt => "Prompt",
        }
    }
}

/// Application error with a short summary and optional detail lines.
#[derive(Debug, Clone)]
pub struct HenError {
    kind: HenErrorKind,
    summary: String,
    details: Vec<String>,
    exit_code: i32,
}

impl HenError {
    /// Construct a new error with the provided kind and summary line.
    pub fn new(kind: HenErrorKind, summary: impl Into<String>) -> Self {
        Self {
            kind,
            summary: summary.into(),
            details: Vec::new(),
            exit_code: 1,
        }
    }

    /// Attach a detail line to the error for additional context.
    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
        self.details.push(detail.into());
        self
    }

    /// Override the process exit code returned for this error.
    pub fn with_exit_code(mut self, code: i32) -> Self {
        self.exit_code = code;
        self
    }

    pub fn kind(&self) -> HenErrorKind {
        self.kind
    }

    pub fn exit_code(&self) -> i32 {
        self.exit_code
    }
}

impl Display for HenError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        writeln!(f, "[{}] {}", self.kind.label(), self.summary)?;
        for detail in &self.details {
            writeln!(f, "  - {}", detail)?;
        }
        Ok(())
    }
}

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

/// Render an error to stderr using the structured format.
pub fn print_error(err: &HenError) {
    eprint!("{}", err);
}

impl<T> From<pest::error::Error<T>> for HenError
where
    T: RuleType + fmt::Debug,
{
    fn from(err: pest::error::Error<T>) -> Self {
        HenError::new(HenErrorKind::Parse, "Failed to parse hen file").with_detail(err.to_string())
    }
}