turul-a2a-patterns 0.2.6

Reusable A2A skill-authoring patterns: SKILL.md manifest, SkillHandler / SkillRegistry / SkillProgressSink / TerminalHook.
Documentation
//! Structured error surfaces shared across the patterns crate.
//!
//! `SkillError` is the handler-facing error returned by `SkillHandler::run`;
//! `SinkError` is the sink-side counterpart used by `SkillProgressSink`;
//! and `ManifestError` / `RenderError` / `ValidationError` carry structured
//! diagnostics from SKILL.md parsing, prompt rendering, and JSON Schema
//! validation respectively.

use thiserror::Error;

/// Skill error surface. Exactly two variants; richer error shapes are
/// the adopter's concern at the call site.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SkillError {
    /// Maps to A2A InvalidRequest.
    #[error("invalid request: {0}")]
    InvalidRequest(String),
    /// Maps to A2A Internal.
    #[error("internal: {0}")]
    Internal(String),
}

/// Sink-side error variants.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SinkError {
    /// The sink's underlying task is closed.
    #[error("sink closed")]
    Closed,
    /// Transient backend failure.
    #[error("sink backend: {0}")]
    Backend(String),
}

/// Structured manifest parse error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ManifestError {
    #[error("manifest parse error at {location}: {reason}")]
    Parse { location: String, reason: String },
    #[error("schema validation error at {location}: {reason}")]
    Schema { location: String, reason: String },
}

/// Structured template render error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum RenderError {
    #[error("missing template variable `{path}` at offset {offset}")]
    MissingVariable { path: String, offset: usize },
    #[error("template syntax error at offset {offset}: {reason}")]
    Syntax { offset: usize, reason: String },
}

/// Structured I/O schema validation error.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum ValidationError {
    #[error("schema validation failed at {location}: {reason}")]
    Invalid { location: String, reason: String },
    #[error("unsupported JSON Schema keyword `{keyword}`")]
    UnsupportedKeyword { keyword: String },
}

#[cfg(test)]
mod tests {
    use super::SkillError;

    /// `SkillError` has exactly two variants. Inside the defining crate
    /// `#[non_exhaustive]` does not force a wildcard arm, so this match
    /// has none: adding a variant fails to compile here until the
    /// binding contract is deliberately amended.
    #[test]
    fn skill_error_variant_set_is_fixed() {
        fn label(e: &SkillError) -> &'static str {
            match e {
                SkillError::InvalidRequest(_) => "invalid-request",
                SkillError::Internal(_) => "internal",
            }
        }
        assert_eq!(
            label(&SkillError::InvalidRequest("x".into())),
            "invalid-request"
        );
        assert_eq!(label(&SkillError::Internal("x".into())), "internal");
    }
}