1use crate::span::Span;
4use std::fmt;
5
6#[derive(Debug, Clone, PartialEq)]
8pub enum SchemaError {
9 Lexer(String, Span),
11 UnterminatedString(Span),
13 InvalidNumber(String, Span),
15 UnexpectedCharacter(char, Span),
17 Parse(String, Span),
19 Validation(String, Span),
21 Warning(String, Span),
23 Other(String),
25}
26
27impl SchemaError {
28 pub fn format_with_source(&self, source: &str) -> String {
30 match self {
31 SchemaError::Lexer(msg, span)
32 | SchemaError::Parse(msg, span)
33 | SchemaError::Validation(msg, span)
34 | SchemaError::Warning(msg, span) => {
35 let (start_pos, _) = span.to_positions(source);
36 format!("{} at {}", msg, start_pos)
37 }
38 SchemaError::UnterminatedString(span) => {
39 let (start_pos, _) = span.to_positions(source);
40 format!("Unterminated string literal at {}", start_pos)
41 }
42 SchemaError::InvalidNumber(num, span) => {
43 let (start_pos, _) = span.to_positions(source);
44 format!("Invalid number '{}' at {}", num, start_pos)
45 }
46 SchemaError::UnexpectedCharacter(ch, span) => {
47 let (start_pos, _) = span.to_positions(source);
48 format!("Unexpected character '{}' at {}", ch, start_pos)
49 }
50 SchemaError::Other(msg) => msg.clone(),
51 }
52 }
53
54 pub fn format_with_file(&self, filepath: &str, source: &str) -> String {
58 match self {
59 SchemaError::Lexer(msg, span)
60 | SchemaError::Parse(msg, span)
61 | SchemaError::Validation(msg, span)
62 | SchemaError::Warning(msg, span) => {
63 let (start_pos, _) = span.to_positions(source);
64 format!(
65 "{}:{}:{}: {}",
66 filepath, start_pos.line, start_pos.column, msg
67 )
68 }
69 SchemaError::UnterminatedString(span) => {
70 let (start_pos, _) = span.to_positions(source);
71 format!(
72 "{}:{}:{}: Unterminated string literal",
73 filepath, start_pos.line, start_pos.column
74 )
75 }
76 SchemaError::InvalidNumber(num, span) => {
77 let (start_pos, _) = span.to_positions(source);
78 format!(
79 "{}:{}:{}: Invalid number '{}'",
80 filepath, start_pos.line, start_pos.column, num
81 )
82 }
83 SchemaError::UnexpectedCharacter(ch, span) => {
84 let (start_pos, _) = span.to_positions(source);
85 format!(
86 "{}:{}:{}: Unexpected character '{}'",
87 filepath, start_pos.line, start_pos.column, ch
88 )
89 }
90 SchemaError::Other(msg) => msg.clone(),
91 }
92 }
93
94 pub fn span(&self) -> Option<Span> {
96 match self {
97 SchemaError::Lexer(_, span)
98 | SchemaError::Parse(_, span)
99 | SchemaError::Validation(_, span)
100 | SchemaError::Warning(_, span)
101 | SchemaError::UnterminatedString(span)
102 | SchemaError::InvalidNumber(_, span)
103 | SchemaError::UnexpectedCharacter(_, span) => Some(*span),
104 SchemaError::Other(_) => None,
105 }
106 }
107}
108
109impl fmt::Display for SchemaError {
110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111 match self {
112 SchemaError::Lexer(msg, _) => write!(f, "Lexer error: {}", msg),
113 SchemaError::UnterminatedString(_) => write!(f, "Unterminated string literal"),
114 SchemaError::InvalidNumber(num, _) => write!(f, "Invalid number: {}", num),
115 SchemaError::UnexpectedCharacter(ch, _) => write!(f, "Unexpected character: '{}'", ch),
116 SchemaError::Parse(msg, _) => write!(f, "Parse error: {}", msg),
117 SchemaError::Validation(msg, _) => write!(f, "Validation error: {}", msg),
118 SchemaError::Warning(msg, _) => write!(f, "Warning: {}", msg),
119 SchemaError::Other(msg) => write!(f, "{}", msg),
120 }
121 }
122}
123
124impl std::error::Error for SchemaError {}
125
126pub type Result<T> = std::result::Result<T, SchemaError>;
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_error_display() {
135 let span = Span::new(0, 5);
136 let err = SchemaError::Lexer("test error".to_string(), span);
137 assert_eq!(err.to_string(), "Lexer error: test error");
138 }
139
140 #[test]
141 fn test_error_span() {
142 let span = Span::new(10, 15);
143 let err = SchemaError::UnexpectedCharacter('#', span);
144 assert_eq!(err.span(), Some(span));
145
146 let err_no_span = SchemaError::Other("generic".to_string());
147 assert_eq!(err_no_span.span(), None);
148 }
149
150 #[test]
151 fn test_format_with_source() {
152 let source = "hello\nworld";
153 let span = Span::new(6, 11); let err = SchemaError::Lexer("unexpected token".to_string(), span);
155 let formatted = err.format_with_source(source);
156 assert!(formatted.contains("2:1")); }
158
159 #[test]
160 fn test_format_with_file() {
161 let source = "hello\nworld";
162 let span = Span::new(6, 11); let err = SchemaError::Lexer("unexpected token".to_string(), span);
164 let formatted = err.format_with_file("schema.nautilus", source);
165 assert_eq!(formatted, "schema.nautilus:2:1: unexpected token");
166 }
167}