Skip to main content

eval_core/
error.rs

1//! The crate's public error type, [`EvalError`].
2//!
3//! `eval-core` is built to lift into a standalone, publishable crate, so its PUBLIC fallible paths
4//! ([`load_cases`](crate::load_cases), [`Agent::run`](crate::Agent::run), regex compilation in the
5//! built-in scorer) surface a concrete, `std::error::Error`-implementing type rather than `anyhow`.
6//! Internal helpers (e.g. the HTML report generator) may still use `anyhow` for convenience; only the
7//! public signatures expose [`EvalError`].
8//!
9//! Every fallible third-party source has a `From` impl ([`std::io::Error`], [`ron::error::SpannedError`],
10//! [`regex::Error`]) so the `?` operator threads cleanly, and the agent-side variant
11//! ([`EvalError::Agent`]) lets a host wrap whatever its own run failure was as a string.
12
13use std::path::PathBuf;
14
15/// The error type for `eval-core`'s public API.
16///
17/// `#[non_exhaustive]` so new fallible paths can add variants without a breaking change. Construct the
18/// host-facing variant via [`EvalError::agent`]; the I/O / parse / regex variants are produced by `?`
19/// through the [`From`] impls below.
20#[derive(Debug, thiserror::Error)]
21#[non_exhaustive]
22pub enum EvalError {
23    /// An I/O error reading a case directory or file. Carries the offending path (when known) for a
24    /// fail-loud message that names what couldn't be read.
25    #[error("i/o error{}: {source}", .path.as_ref().map(|p| format!(" for {}", p.display())).unwrap_or_default())]
26    Io {
27        /// The path being read when the error occurred, if known.
28        path: Option<PathBuf>,
29        /// The underlying I/O error.
30        source: std::io::Error,
31    },
32
33    /// A `.ron` case file failed to parse. Carries the offending file path so a typo fails loud.
34    #[error("failed to parse RON case {}: {source}", .path.display())]
35    RonParse {
36        /// The `.ron` file that failed to deserialize.
37        path: PathBuf,
38        /// The underlying RON deserialization error (with span info).
39        source: ron::error::SpannedError,
40    },
41
42    /// A regex in a [`FinalTextMatches`](crate::expect::Expectation::FinalTextMatches) expectation
43    /// failed to compile.
44    #[error("invalid regex `{pattern}`: {source}")]
45    Regex {
46        /// The pattern that failed to compile.
47        pattern: String,
48        /// The underlying regex compilation error.
49        source: regex::Error,
50    },
51
52    /// The host's [`Agent::run`](crate::Agent::run) (or another host-supplied step) failed. Carries the
53    /// host's own error rendered to a string — `eval-core` stays free of the host's error types.
54    #[error("agent run failed: {0}")]
55    Agent(String),
56}
57
58impl EvalError {
59    /// Wrap a host-side run failure (anything `Display`) as an [`EvalError::Agent`]. The canonical way a
60    /// host's [`Agent::run`](crate::Agent::run) reports a backend/loop failure to the generic runner.
61    pub fn agent(error: impl std::fmt::Display) -> Self {
62        EvalError::Agent(error.to_string())
63    }
64}
65
66impl From<std::io::Error> for EvalError {
67    fn from(source: std::io::Error) -> Self {
68        EvalError::Io { path: None, source }
69    }
70}
71
72impl From<ron::error::SpannedError> for EvalError {
73    /// A bare `ron` error with no associated path. [`load_cases`](crate::load_cases) builds the
74    /// path-carrying [`EvalError::RonParse`] directly, so this is the fallback for any other call site.
75    fn from(source: ron::error::SpannedError) -> Self {
76        EvalError::RonParse {
77            path: PathBuf::new(),
78            source,
79        }
80    }
81}
82
83impl From<regex::Error> for EvalError {
84    fn from(source: regex::Error) -> Self {
85        EvalError::Regex {
86            pattern: String::new(),
87            source,
88        }
89    }
90}