facet_yaml/
error.rs

1//! Error types for YAML serialization and deserialization.
2
3extern crate alloc;
4
5use alloc::boxed::Box;
6use alloc::format;
7use alloc::string::{String, ToString};
8use alloc::vec::Vec;
9use core::fmt::{self, Display};
10
11use facet_reflect::ReflectError;
12use miette::NamedSource;
13
14// Re-export Span from facet-reflect for consistency across format crates
15pub use facet_reflect::Span;
16
17/// Helper functions for creating Span from saphyr-parser types
18pub(crate) trait SpanExt {
19    /// Create a span from a saphyr-parser Span
20    fn from_saphyr_span(span: &saphyr_parser::Span) -> Span {
21        let start = span.start.index();
22        let end = span.end.index();
23        Span::new(start, end.saturating_sub(start))
24    }
25}
26
27impl SpanExt for Span {}
28
29/// Error type for YAML operations.
30#[derive(Debug)]
31pub struct YamlError {
32    /// The specific kind of error
33    pub kind: YamlErrorKind,
34    /// Source span where the error occurred
35    pub span: Option<Span>,
36    /// The source input (for diagnostics) - wrapped in NamedSource for syntax highlighting
37    /// Boxed to reduce the size of the error type
38    pub source_code: Option<Box<NamedSource<String>>>,
39}
40
41impl Display for YamlError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        write!(f, "{}", self.kind)
44    }
45}
46
47impl core::error::Error for YamlError {}
48
49impl miette::Diagnostic for YamlError {
50    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
51        Some(Box::new(self.kind.code()))
52    }
53
54    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
55        self.source_code
56            .as_ref()
57            .map(|s| s.as_ref() as &dyn miette::SourceCode)
58    }
59
60    fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
61        let span = self.span?;
62        Some(Box::new(core::iter::once(miette::LabeledSpan::new(
63            Some(self.kind.label()),
64            span.offset,
65            span.len.max(1), // Ensure at least 1 character span for visibility
66        ))))
67    }
68}
69
70impl YamlError {
71    /// Create a new error with span information
72    pub fn new(kind: YamlErrorKind, span: Span) -> Self {
73        YamlError {
74            kind,
75            span: Some(span),
76            source_code: None,
77        }
78    }
79
80    /// Create an error without span information
81    pub fn without_span(kind: YamlErrorKind) -> Self {
82        YamlError {
83            kind,
84            span: None,
85            source_code: None,
86        }
87    }
88
89    /// Attach source code for rich diagnostics with syntax highlighting
90    pub fn with_source(mut self, source: &str) -> Self {
91        self.source_code = Some(Box::new(NamedSource::new("input.yaml", source.to_string())));
92        self
93    }
94}
95
96/// Specific error kinds for YAML operations
97#[derive(Debug)]
98pub enum YamlErrorKind {
99    /// YAML parser error
100    Parse(String),
101    /// Unexpected YAML event
102    UnexpectedEvent {
103        /// The event that was found
104        got: String,
105        /// What was expected instead
106        expected: &'static str,
107    },
108    /// Unexpected end of input
109    UnexpectedEof {
110        /// What was expected before EOF
111        expected: &'static str,
112    },
113    /// Type mismatch
114    TypeMismatch {
115        /// The expected type
116        expected: &'static str,
117        /// The actual type found
118        got: &'static str,
119    },
120    /// Unknown field in struct
121    UnknownField {
122        /// The unknown field name
123        field: String,
124        /// List of valid field names
125        expected: Vec<&'static str>,
126        /// Suggested field name (if similar to an expected field)
127        suggestion: Option<&'static str>,
128    },
129    /// Missing required field
130    MissingField {
131        /// The name of the missing field
132        field: &'static str,
133    },
134    /// Invalid value for type
135    InvalidValue {
136        /// Description of why the value is invalid
137        message: String,
138    },
139    /// Reflection error from facet-reflect
140    Reflect(ReflectError),
141    /// Number out of range
142    NumberOutOfRange {
143        /// The numeric value that was out of range
144        value: String,
145        /// The target type that couldn't hold the value
146        target_type: &'static str,
147    },
148    /// Duplicate key in mapping
149    DuplicateKey {
150        /// The key that appeared more than once
151        key: String,
152    },
153    /// Invalid UTF-8 in string
154    InvalidUtf8(core::str::Utf8Error),
155    /// Solver error (for flattened types)
156    Solver(String),
157    /// Unsupported YAML feature
158    Unsupported(String),
159    /// IO error during serialization
160    Io(String),
161}
162
163impl Display for YamlErrorKind {
164    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165        match self {
166            YamlErrorKind::Parse(e) => write!(f, "YAML parse error: {e}"),
167            YamlErrorKind::UnexpectedEvent { got, expected } => {
168                write!(f, "unexpected YAML event: got {got}, expected {expected}")
169            }
170            YamlErrorKind::UnexpectedEof { expected } => {
171                write!(f, "unexpected end of input, expected {expected}")
172            }
173            YamlErrorKind::TypeMismatch { expected, got } => {
174                write!(f, "type mismatch: expected {expected}, got {got}")
175            }
176            YamlErrorKind::UnknownField {
177                field, expected, ..
178            } => {
179                write!(f, "unknown field `{field}`, expected one of: {expected:?}")
180            }
181            YamlErrorKind::MissingField { field } => {
182                write!(f, "missing required field `{field}`")
183            }
184            YamlErrorKind::InvalidValue { message } => {
185                write!(f, "invalid value: {message}")
186            }
187            YamlErrorKind::Reflect(e) => write!(f, "reflection error: {e}"),
188            YamlErrorKind::NumberOutOfRange { value, target_type } => {
189                write!(f, "number `{value}` out of range for {target_type}")
190            }
191            YamlErrorKind::DuplicateKey { key } => {
192                write!(f, "duplicate key `{key}`")
193            }
194            YamlErrorKind::InvalidUtf8(e) => write!(f, "invalid UTF-8 sequence: {e}"),
195            YamlErrorKind::Solver(msg) => write!(f, "solver error: {msg}"),
196            YamlErrorKind::Unsupported(msg) => write!(f, "unsupported: {msg}"),
197            YamlErrorKind::Io(msg) => write!(f, "IO error: {msg}"),
198        }
199    }
200}
201
202impl YamlErrorKind {
203    /// Get an error code for this kind of error.
204    pub fn code(&self) -> &'static str {
205        match self {
206            YamlErrorKind::Parse(_) => "yaml::parse",
207            YamlErrorKind::UnexpectedEvent { .. } => "yaml::unexpected_event",
208            YamlErrorKind::UnexpectedEof { .. } => "yaml::unexpected_eof",
209            YamlErrorKind::TypeMismatch { .. } => "yaml::type_mismatch",
210            YamlErrorKind::UnknownField { .. } => "yaml::unknown_field",
211            YamlErrorKind::MissingField { .. } => "yaml::missing_field",
212            YamlErrorKind::InvalidValue { .. } => "yaml::invalid_value",
213            YamlErrorKind::Reflect(_) => "yaml::reflect",
214            YamlErrorKind::NumberOutOfRange { .. } => "yaml::number_out_of_range",
215            YamlErrorKind::DuplicateKey { .. } => "yaml::duplicate_key",
216            YamlErrorKind::InvalidUtf8(_) => "yaml::invalid_utf8",
217            YamlErrorKind::Solver(_) => "yaml::solver",
218            YamlErrorKind::Unsupported(_) => "yaml::unsupported",
219            YamlErrorKind::Io(_) => "yaml::io",
220        }
221    }
222
223    /// Get a label for diagnostic display
224    pub fn label(&self) -> String {
225        match self {
226            YamlErrorKind::Parse(_) => "parse error here".to_string(),
227            YamlErrorKind::UnexpectedEvent { got, .. } => format!("unexpected {got}"),
228            YamlErrorKind::UnexpectedEof { expected } => format!("expected {expected}"),
229            YamlErrorKind::TypeMismatch { expected, got } => {
230                format!("expected {expected}, got {got}")
231            }
232            YamlErrorKind::UnknownField { field, .. } => format!("unknown field `{field}`"),
233            YamlErrorKind::MissingField { field } => format!("missing `{field}`"),
234            YamlErrorKind::InvalidValue { message } => message.clone(),
235            YamlErrorKind::Reflect(e) => format!("{e}"),
236            YamlErrorKind::NumberOutOfRange { target_type, .. } => {
237                format!("out of range for {target_type}")
238            }
239            YamlErrorKind::DuplicateKey { key } => format!("duplicate key `{key}`"),
240            YamlErrorKind::InvalidUtf8(_) => "invalid UTF-8".to_string(),
241            YamlErrorKind::Solver(msg) => msg.clone(),
242            YamlErrorKind::Unsupported(msg) => msg.clone(),
243            YamlErrorKind::Io(msg) => msg.clone(),
244        }
245    }
246}
247
248impl From<ReflectError> for YamlError {
249    fn from(e: ReflectError) -> Self {
250        YamlError::without_span(YamlErrorKind::Reflect(e))
251    }
252}
253
254impl From<ReflectError> for YamlErrorKind {
255    fn from(e: ReflectError) -> Self {
256        YamlErrorKind::Reflect(e)
257    }
258}