Skip to main content

busbar_sf_agentscript/
error.rs

1//! Error types and error reporting for AgentScript.
2//!
3//! This module provides structured error types and pretty error reporting
4//! using the [ariadne](https://crates.io/crates/ariadne) crate for colorful,
5//! context-aware error messages.
6//!
7//! # Error Types
8//!
9//! - [`AgentScriptError`] - Main error enum for parse and validation errors
10//! - [`ParseErrorInfo`] - Details about parse failures
11//! - [`ValidationError`] - Semantic validation errors
12//!
13//! # Pretty Printing
14//!
15//! Use [`ErrorReporter`] for user-friendly error output:
16//!
17//! ```rust
18//! use busbar_sf_agentscript::error::{ErrorReporter, ParseErrorInfo};
19//!
20//! let source = "config:\n   agent_name: bad";
21//! let reporter = ErrorReporter::new("example.agent", source);
22//!
23//! // Create and report an error
24//! let error = ParseErrorInfo {
25//!     message: "Expected string literal".to_string(),
26//!     span: Some(18..21),
27//!     expected: vec!["string".to_string()],
28//!     found: Some("identifier".to_string()),
29//!     contexts: vec![],
30//! };
31//! // reporter.report_parse_error(&error); // Prints colorful error
32//! ```
33
34use ariadne::{Color, Label, Report, ReportKind, Source};
35use std::fmt;
36
37/// The main error type for AgentScript operations.
38///
39/// This enum encompasses all errors that can occur during parsing
40/// and validation of AgentScript source code.
41#[derive(Debug)]
42pub enum AgentScriptError {
43    /// Parse error.
44    Parse(ParseErrorInfo),
45    /// Validation error.
46    Validation(ValidationError),
47}
48
49impl fmt::Display for AgentScriptError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        match self {
52            AgentScriptError::Parse(e) => write!(f, "Parse error: {}", e),
53            AgentScriptError::Validation(e) => write!(f, "Validation error: {}", e),
54        }
55    }
56}
57
58impl std::error::Error for AgentScriptError {}
59
60/// Information about a parse error.
61#[derive(Debug)]
62pub struct ParseErrorInfo {
63    pub message: String,
64    pub span: Option<std::ops::Range<usize>>,
65    pub expected: Vec<String>,
66    pub found: Option<String>,
67    /// Context chain from labelled parsers - shows parse tree path to failure
68    pub contexts: Vec<(String, std::ops::Range<usize>)>,
69}
70
71impl fmt::Display for ParseErrorInfo {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{}", self.message)?;
74        if let Some(ref found) = self.found {
75            write!(f, ", found '{}'", found)?;
76        }
77        if !self.expected.is_empty() {
78            write!(f, ", expected one of: {}", self.expected.join(", "))?;
79        }
80        Ok(())
81    }
82}
83
84/// Validation error for semantic issues.
85#[derive(Debug)]
86pub struct ValidationError {
87    pub message: String,
88    pub span: Option<std::ops::Range<usize>>,
89    pub hint: Option<String>,
90}
91
92impl fmt::Display for ValidationError {
93    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94        write!(f, "{}", self.message)?;
95        if let Some(ref hint) = self.hint {
96            write!(f, " (hint: {})", hint)?;
97        }
98        Ok(())
99    }
100}
101
102/// Error reporter that uses ariadne for pretty error output.
103pub struct ErrorReporter<'src> {
104    source_name: String,
105    source: &'src str,
106}
107
108impl<'src> ErrorReporter<'src> {
109    /// Create a new error reporter.
110    pub fn new(source_name: impl Into<String>, source: &'src str) -> Self {
111        Self {
112            source_name: source_name.into(),
113            source,
114        }
115    }
116
117    /// Report a parse error to stderr.
118    pub fn report_parse_error(&self, error: &ParseErrorInfo) {
119        let span = error.span.clone().unwrap_or(0..0);
120
121        let mut report = Report::build(ReportKind::Error, &self.source_name, span.start)
122            .with_message(&error.message);
123
124        let mut label = Label::new((&self.source_name, span.clone())).with_color(Color::Red);
125
126        if let Some(ref found) = error.found {
127            label = label.with_message(format!("found '{}'", found));
128        }
129
130        report = report.with_label(label);
131
132        // Add context labels for the parse tree path
133        for (i, (ctx_label, ctx_span)) in error.contexts.iter().enumerate() {
134            let color = if i == 0 { Color::Yellow } else { Color::Cyan };
135            report = report.with_label(
136                Label::new((&self.source_name, ctx_span.clone()))
137                    .with_color(color)
138                    .with_message(format!("while parsing {}", ctx_label))
139                    .with_order(i as i32 + 1), // Order labels by depth
140            );
141        }
142
143        if !error.expected.is_empty() {
144            report = report.with_note(format!("expected one of: {}", error.expected.join(", ")));
145        }
146
147        report
148            .finish()
149            .eprint((&self.source_name, Source::from(self.source)))
150            .unwrap();
151    }
152
153    /// Report a validation error to stderr.
154    pub fn report_validation_error(&self, error: &ValidationError) {
155        let span = error.span.clone().unwrap_or(0..0);
156
157        let mut report = Report::build(ReportKind::Error, &self.source_name, span.start)
158            .with_message(&error.message)
159            .with_label(
160                Label::new((&self.source_name, span))
161                    .with_color(Color::Yellow)
162                    .with_message("here"),
163            );
164
165        if let Some(ref hint) = error.hint {
166            report = report.with_help(hint);
167        }
168
169        report
170            .finish()
171            .eprint((&self.source_name, Source::from(self.source)))
172            .unwrap();
173    }
174}
175
176/// Result type for AgentScript operations.
177pub type Result<T> = std::result::Result<T, AgentScriptError>;