castle_types/
error.rs

1
2use std::fmt;
3
4use castle_input_cursor::{Position, Span};
5use serde::{Deserialize, Serialize};
6
7
8#[derive(Debug, Deserialize, Serialize)]
9pub enum CastleError {
10    IO(Box<str>),
11    AbruptEOF(Box<str>),
12    Syntax(Box<str>, Position),
13    Parser(Box<str>, Span),
14    Other(Box<str>),
15    Schema(Box<str>, Span),
16    Validation(Box<str>),
17    MissingDirective(Box<str>),
18    MissingResolver(Box<str>),
19    Unimplemented,
20}
21
22impl std::error::Error for CastleError {}
23
24impl From<std::io::Error> for CastleError {
25    fn from(err: std::io::Error) -> Self {
26        Self::IO(err.to_string().into())
27    }
28}
29
30impl CastleError {
31    pub fn syntax<Msg, Pos>(msg: Msg, pos: Pos) -> Self
32    where
33        Msg: Into<Box<str>>,
34        Pos: Into<Position>,
35    {
36        Self::Syntax(msg.into(), pos.into())
37    }
38
39    pub fn parse<Msg, S>(msg: Msg, span: S) -> Self
40    where
41        Msg: Into<Box<str>>,
42        S: Into<Span>,
43    {
44        Self::Parser(msg.into(), span.into())
45    }
46}
47
48impl fmt::Display for CastleError {
49    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50        match self {
51            Self::IO(msg) => write!(f, "IO error: {}", msg),
52            Self::AbruptEOF(msg) => write!(f, "Unexpected EOF: {}", msg),
53            Self::Syntax(msg, pos) => write!(f, "Syntax error: {} at {}", msg, pos),
54            Self::Parser(msg, span) => write!(f, "Parser error: {} at {}", msg, span),
55            Self::Other(msg) => write!(f, "Error: {}", msg),
56            Self::Schema(msg, span) => write!(f, "Schema error: {} at {}", msg, span),
57            Self::Validation(msg) => write!(f, "Schema validation error: {}", msg),
58            Self::MissingDirective(msg) => write!(f, "Missing directive: {}", msg),
59            Self::MissingResolver(msg) => write!(f, "Missing resolver: {}", msg),
60            Self::Unimplemented => write!(f, "Unimplemented"),
61        }
62    }
63}
64
65pub trait ExtendedErrorDisplay {
66    fn extended_error(&self, src: &str) -> String;
67}
68
69impl ExtendedErrorDisplay for CastleError {
70    fn extended_error(&self, src: &str) -> String {
71        match self {
72            Self::IO(msg) => msg.to_string(),
73            Self::AbruptEOF(msg) =>  msg.to_string(),
74            Self::Other(msg) =>  msg.to_string(),
75            Self::Syntax(msg, pos) => pretty_print_lexer_error(msg, pos, src),
76            Self::Parser(msg, span) => pretty_print_parser_error(msg, span, src),
77            Self::Schema(msg, span) => pretty_print_parser_error(msg, span, src),
78            Self::Validation(msg) => msg.to_string(),
79            Self::MissingDirective(msg) => msg.to_string(),
80            Self::MissingResolver(msg) => msg.to_string(),
81            Self::Unimplemented => "Unimplemented".to_string(),
82        }
83    }
84}
85
86
87/// ## Get pretty errors that look like this
88/// ```text
89/// error: expected valid identifier, found keyword: type
90///
91/// 45 | type type {
92///    |      ^^^^ expected valid identifier, found keyword: type
93/// 46 |    first_name: String
94/// 47 |    last_name: String
95/// ```
96///
97/// or
98///
99/// ```text
100/// error: expected valid identifier, found string literal: "hello\n\nhello"
101///
102/// 45 | type "hello
103/// 46 | hello"
104///    | ^^^^^^^^^^^^ expected valid identifier, found string literal: "hello\n\nhello"
105/// 47 |     first_name: String
106fn pretty_print_parser_error(msg: &str, span: &Span, src: &str) -> String {
107    let src_lines: Vec<String> = src.lines().map(|line| line.to_string()).collect();
108    let mut result_lines = vec![];
109
110    result_lines.push(format!("error: {}\n", msg));
111
112    let start_line_index = span.start().line_number() - 1;
113    let end_line_index = span.end().line_number() - 1;
114    let is_multiple_lines = start_line_index != end_line_index;
115
116    for line_index in start_line_index..=end_line_index {
117        result_lines.push(get_line_text(&line_index.to_string(), src_lines.get(line_index as usize).unwrap()));
118    }
119
120    let space_before_arrow = match is_multiple_lines {
121        true => 0,
122        false => span.start().column_number(),
123    } as usize;
124
125    let spaces = " ".repeat(space_before_arrow);
126    let arrows = "^".repeat(span.end().column_number() as usize - space_before_arrow);
127
128    result_lines.push(get_line_text(&"", &format!("{spaces}{arrows} {msg}")));
129
130    for line_index in end_line_index+1..end_line_index+3 {
131        if let Some(line) = src_lines.get(line_index as usize) {
132            result_lines.push(get_line_text(&line_index.to_string(), line));
133        }
134    }
135
136    result_lines.join("\n")
137}
138
139fn get_line_text(index: &str, line: &str) -> String {
140    format!("{:>4} | {}", index, line)
141}
142
143
144fn pretty_print_lexer_error(msg: &str, pos: &Position, src: &str) -> String {
145    let src_lines: Vec<String> = src.lines().map(|line| line.to_string()).collect();
146    let mut result_lines = vec![];
147
148    result_lines.push(format!("ERROR: {}\n", msg));
149
150    let start_line_index = pos.line_number() - 1;
151
152    result_lines.push(get_line_text(&start_line_index.to_string(), src_lines.get(start_line_index as usize).unwrap()));
153
154    let space_before_arrow = " ".repeat(pos.column_number() as usize);
155
156    result_lines.push(get_line_text(&"", &format!("{space_before_arrow}^ {msg}")));
157
158    for line_index in start_line_index+1..start_line_index+3 {
159        if let Some(line) = src_lines.get(line_index as usize)  {
160            result_lines.push(get_line_text(&line_index.to_string(), line));
161        }
162    }
163
164    result_lines.join("\n")
165}
166
167
168#[ignore]
169#[test]
170fn test_pretty_print_lexer_error() {
171    let src = r#"
172    type {
173
174    }
175
176    "#;
177    let err = CastleError::syntax("got unexpected string", Position::new(2, 5));
178
179    let result = err.extended_error(src);
180    println!("{}", result);
181}
182
183#[ignore]
184#[test]
185fn test_pretty_print_parser_error() {
186    let src = r#"
187type "sdsdds
188sdsd" {
189    first_name: String
190    last_name: String
191}
192
193    "#;
194    let err = CastleError::parse("got unexpected string", Span::new(Position::new(2, 5), Position::new(3, 5)));
195
196    let result = err.extended_error(src);
197    println!("{}", result);
198}
199
200#[ignore]
201#[test]
202fn test_pretty_print_parser_error_with_one_line() {
203    let src = r#"
204type type {
205    first_name: String
206    last_name: String
207}
208
209    "#;
210    let err = CastleError::parse("got unexpected keyword 'type', but expected identifier", Span::new(Position::new(2, 5), Position::new(2, 5 + 4)));
211
212    let result = err.extended_error(src);
213    println!("{}", result);
214}