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}