Skip to main content

turul_a2a_patterns/
error.rs

1//! Structured error surfaces shared across the patterns crate.
2//!
3//! `SkillError` is the handler-facing error returned by `SkillHandler::run`;
4//! `SinkError` is the sink-side counterpart used by `SkillProgressSink`;
5//! and `ManifestError` / `RenderError` / `ValidationError` carry structured
6//! diagnostics from SKILL.md parsing, prompt rendering, and JSON Schema
7//! validation respectively.
8
9use thiserror::Error;
10
11/// Skill error surface. Exactly three variants; richer error shapes are
12/// the adopter's concern at the call site.
13#[non_exhaustive]
14#[derive(Debug, Error)]
15pub enum SkillError {
16    /// Maps to A2A InvalidRequest.
17    #[error("invalid request: {0}")]
18    InvalidRequest(String),
19    /// Maps to A2A Internal.
20    #[error("internal: {0}")]
21    Internal(String),
22    /// A pre-terminal gate refused a successful output. Maps to A2A
23    /// Internal, like `Internal`, but names the gate so a veto is
24    /// distinguishable from a handler fault.
25    #[error("rejected by {gate}: {reason}")]
26    Rejected { gate: String, reason: String },
27}
28
29/// Sink-side error variants.
30#[non_exhaustive]
31#[derive(Debug, Error)]
32pub enum SinkError {
33    /// The sink's underlying task is closed.
34    #[error("sink closed")]
35    Closed,
36    /// Transient backend failure.
37    #[error("sink backend: {0}")]
38    Backend(String),
39}
40
41/// Structured manifest parse error.
42#[non_exhaustive]
43#[derive(Debug, Error)]
44pub enum ManifestError {
45    #[error("manifest parse error at {location}: {reason}")]
46    Parse { location: String, reason: String },
47    #[error("schema validation error at {location}: {reason}")]
48    Schema { location: String, reason: String },
49}
50
51/// Structured template render error.
52#[non_exhaustive]
53#[derive(Debug, Error)]
54pub enum RenderError {
55    #[error("missing template variable `{path}` at offset {offset}")]
56    MissingVariable { path: String, offset: usize },
57    #[error("template syntax error at offset {offset}: {reason}")]
58    Syntax { offset: usize, reason: String },
59}
60
61/// Structured I/O schema validation error.
62#[non_exhaustive]
63#[derive(Debug, Error)]
64pub enum ValidationError {
65    #[error("schema validation failed at {location}: {reason}")]
66    Invalid { location: String, reason: String },
67    #[error("unsupported JSON Schema keyword `{keyword}`")]
68    UnsupportedKeyword { keyword: String },
69}
70
71#[cfg(test)]
72mod tests {
73    use super::SkillError;
74
75    /// `SkillError` has exactly three variants. Inside the defining crate
76    /// `#[non_exhaustive]` does not force a wildcard arm, so this match
77    /// has none: adding a variant fails to compile here until the
78    /// binding contract is deliberately amended.
79    #[test]
80    fn skill_error_variant_set_is_fixed() {
81        fn label(e: &SkillError) -> &'static str {
82            match e {
83                SkillError::InvalidRequest(_) => "invalid-request",
84                SkillError::Internal(_) => "internal",
85                SkillError::Rejected { .. } => "rejected",
86            }
87        }
88        assert_eq!(
89            label(&SkillError::InvalidRequest("x".into())),
90            "invalid-request"
91        );
92        assert_eq!(label(&SkillError::Internal("x".into())), "internal");
93        assert_eq!(
94            label(&SkillError::Rejected {
95                gate: "g".into(),
96                reason: "r".into()
97            }),
98            "rejected"
99        );
100    }
101}