Skip to main content

agentcast/
error.rs

1use thiserror::Error;
2
3/// One concrete validation issue.
4#[derive(Debug, Clone)]
5pub struct CastIssue {
6    /// JSON pointer to the offending field, e.g. `/label`.
7    pub pointer: String,
8    /// Short human-readable description.
9    pub message: String,
10}
11
12/// Error returned by [`Caster::parse`](crate::Caster::parse) and friends.
13#[derive(Debug, Error)]
14pub enum CastError {
15    /// Could not parse as JSON even after repair.
16    #[error("invalid JSON after repair: {0}")]
17    InvalidJson(String),
18    /// Parsed but failed schema validation.
19    #[error("schema validation failed ({} issue(s))", issues.len())]
20    Invalid {
21        /// All discovered issues.
22        issues: Vec<CastIssue>,
23    },
24    /// `max_retries` exhausted without producing a valid response.
25    #[error("retry budget exhausted after {attempts} attempts; last error: {last}")]
26    RetryExhausted {
27        /// Number of attempts made.
28        attempts: usize,
29        /// Last error message.
30        last: String,
31    },
32    /// User-supplied retry function returned an error.
33    #[error("retry function failed: {0}")]
34    RetryFailed(String),
35    /// Compiling the schema failed.
36    #[error("invalid schema: {0}")]
37    InvalidSchema(String),
38}
39
40impl CastError {
41    /// Render the issues (if any) as a short hint suitable for handing to
42    /// the LLM on the next retry turn. Returns `None` for non-validation errors.
43    pub fn for_llm(&self) -> Option<String> {
44        match self {
45            CastError::Invalid { issues } => {
46                let mut s = String::from("Output rejected. Fix and try again:\n");
47                for i in issues {
48                    s.push_str("  - ");
49                    if !i.pointer.is_empty() {
50                        s.push_str(&i.pointer);
51                        s.push_str(": ");
52                    }
53                    s.push_str(&i.message);
54                    s.push('\n');
55                }
56                Some(s.trim_end().to_string())
57            }
58            CastError::InvalidJson(msg) => Some(format!(
59                "Output was not valid JSON. Return only a JSON object matching the schema. Parser said: {msg}"
60            )),
61            _ => None,
62        }
63    }
64}