1#[derive(Debug, Clone, PartialEq)]
5pub struct ParseError {
6 pub line: Option<usize>,
8 pub column: Option<usize>,
10 pub message: String,
12}
13
14impl ParseError {
15 pub fn new(message: impl Into<String>) -> Self {
17 Self {
18 line: None,
19 column: None,
20 message: message.into(),
21 }
22 }
23
24 pub fn at_line(line: usize, message: impl Into<String>) -> Self {
26 Self {
27 line: Some(line),
28 column: None,
29 message: message.into(),
30 }
31 }
32
33 pub fn at(line: usize, column: usize, message: impl Into<String>) -> Self {
35 Self {
36 line: Some(line),
37 column: Some(column),
38 message: message.into(),
39 }
40 }
41}
42
43impl std::fmt::Display for ParseError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match (self.line, self.column) {
46 (Some(l), Some(c)) => write!(f, "line {l}, col {c}: {}", self.message),
47 (Some(l), None) => write!(f, "line {l}: {}", self.message),
48 _ => write!(f, "{}", self.message),
49 }
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct RenderError {
56 pub diagram_type: String,
58 pub message: String,
60 pub parse_errors: Vec<ParseError>,
62}
63
64impl RenderError {
65 pub fn unknown_type() -> Self {
67 Self {
68 diagram_type: "unknown".into(),
69 message: "Unrecognized diagram type.".into(),
70 parse_errors: vec![],
71 }
72 }
73
74 pub fn from_panic(diagram_type: impl Into<String>, message: impl Into<String>) -> Self {
76 Self {
77 diagram_type: diagram_type.into(),
78 message: message.into(),
79 parse_errors: vec![],
80 }
81 }
82}
83
84impl std::fmt::Display for RenderError {
85 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86 write!(f, "{} render error: {}", self.diagram_type, self.message)?;
87 for e in &self.parse_errors {
88 write!(f, "\n - {e}")?;
89 }
90 Ok(())
91 }
92}
93
94impl std::error::Error for RenderError {}
95
96#[derive(Debug)]
99pub struct ParseResult<T> {
100 pub diagram: T,
102 pub errors: Vec<ParseError>,
104}
105
106impl<T> ParseResult<T> {
107 pub fn ok(diagram: T) -> Self {
109 Self {
110 diagram,
111 errors: vec![],
112 }
113 }
114
115 pub fn with_errors(diagram: T, errors: Vec<ParseError>) -> Self {
117 Self { diagram, errors }
118 }
119
120 pub fn is_ok(&self) -> bool {
122 self.errors.is_empty()
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use std::error::Error;
130
131 #[test]
134 fn parse_error_new_no_location() {
135 let e = ParseError::new("oops");
136 assert_eq!(e.message, "oops");
137 assert_eq!(e.line, None);
138 assert_eq!(e.column, None);
139 assert_eq!(e.to_string(), "oops");
140 }
141
142 #[test]
143 fn parse_error_at_line() {
144 let e = ParseError::at_line(3, "bad token");
145 assert_eq!(e.line, Some(3));
146 assert_eq!(e.column, None);
147 assert_eq!(e.to_string(), "line 3: bad token");
148 }
149
150 #[test]
151 fn parse_error_at_full_location() {
152 let e = ParseError::at(5, 12, "unexpected '}'");
153 assert_eq!(e.line, Some(5));
154 assert_eq!(e.column, Some(12));
155 assert_eq!(e.to_string(), "line 5, col 12: unexpected '}'");
156 }
157
158 #[test]
161 fn render_error_unknown_type() {
162 let e = RenderError::unknown_type();
163 assert_eq!(e.diagram_type, "unknown");
164 assert!(e.message.contains("Unrecognized"));
165 assert!(e.parse_errors.is_empty());
166 }
167
168 #[test]
169 fn render_error_from_panic() {
170 let e = RenderError::from_panic("flowchart", "index out of bounds");
171 assert_eq!(e.diagram_type, "flowchart");
172 assert_eq!(e.message, "index out of bounds");
173 assert!(e.parse_errors.is_empty());
174 }
175
176 #[test]
179 fn render_error_display_no_parse_errors() {
180 let e = RenderError::from_panic("pie", "something failed");
181 let s = e.to_string();
182 assert!(s.contains("pie"));
183 assert!(s.contains("something failed"));
184 }
185
186 #[test]
187 fn render_error_display_with_parse_errors() {
188 let mut e = RenderError::unknown_type();
189 e.parse_errors.push(ParseError::at_line(2, "bad input"));
190 let s = e.to_string();
191 assert!(s.contains("line 2: bad input"));
192 }
193
194 #[test]
197 fn render_error_source_is_none() {
198 let e = RenderError::unknown_type();
199 assert!(e.source().is_none());
200 }
201
202 #[test]
205 fn parse_result_ok_is_ok() {
206 let r: ParseResult<i32> = ParseResult::ok(42);
207 assert!(r.is_ok());
208 assert_eq!(r.diagram, 42);
209 assert!(r.errors.is_empty());
210 }
211
212 #[test]
213 fn parse_result_with_errors_not_ok() {
214 let errs = vec![ParseError::new("err1"), ParseError::new("err2")];
215 let r: ParseResult<&str> = ParseResult::with_errors("partial", errs);
216 assert!(!r.is_ok());
217 assert_eq!(r.errors.len(), 2);
218 assert_eq!(r.diagram, "partial");
219 }
220}