1use crate::{
4 comments::CommentError,
5 docs::{ColumnDoc, TableDoc},
6};
7use core::fmt;
8use sqlparser::parser::ParserError;
9use std::{error, fmt::Debug};
10
11#[non_exhaustive]
13#[derive(Debug)]
14pub enum DocError {
15 FileReadError(std::io::Error),
17 CommentError(CommentError),
19 SqlParserError(ParserError),
21 InvalidObjectName {
23 message: String,
25 line: u64,
27 column: u64,
29 },
30 TableNotFound {
32 name: String,
34 },
35 ColumnNotFound {
37 name: String,
39 },
40 DuplicateTablesFound {
42 tables: Vec<TableDoc>,
44 },
45 DuplicateColumnsFound {
47 columns: Vec<ColumnDoc>,
49 },
50 TableWithSchemaNotFound {
52 name: String,
54 schema: String,
56 },
57}
58
59impl fmt::Display for DocError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 Self::FileReadError(error) => write!(f, "file read error: {error}"),
63 Self::CommentError(comment_error) => {
64 write!(f, "comment parse error: {comment_error}")
65 }
66 Self::SqlParserError(parser_error) => write!(f, "SQL parse error {parser_error}"),
67 Self::InvalidObjectName { message, line, column } => {
68 write!(f, "{message} at line {line}, column {column}")
69 }
70 Self::TableNotFound { name } => write!(f, "Table not found in SqlDoc: {name}"),
71 Self::ColumnNotFound { name } => write!(f, "Column not found in TableDoc: {name}"),
72 Self::DuplicateTablesFound { tables } => {
73 writeln!(f, "Duplicate tables found:")?;
74 for t in tables {
75 writeln!(f, "{t}")?;
76 }
77 Ok(())
78 }
79 Self::DuplicateColumnsFound { columns } => {
80 writeln!(f, "Duplicate columns found:")?;
81 for t in columns {
82 writeln!(f, "{t}")?;
83 }
84 Ok(())
85 }
86 Self::TableWithSchemaNotFound { name, schema } => {
87 writeln!(f, "Table: {name} with schema: {schema} not found in SqlDoc")
88 }
89 }
90 }
91}
92
93impl error::Error for DocError {
94 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
95 match self {
96 Self::FileReadError(e) => Some(e),
97 Self::CommentError(e) => Some(e),
98 Self::SqlParserError(e) => Some(e),
99 Self::InvalidObjectName { .. }
100 | Self::TableNotFound { .. }
101 | Self::ColumnNotFound { .. }
102 | Self::DuplicateTablesFound { .. }
103 | Self::DuplicateColumnsFound { .. }
104 | Self::TableWithSchemaNotFound { .. } => None,
105 }
106 }
107}
108
109impl From<std::io::Error> for DocError {
110 fn from(e: std::io::Error) -> Self {
111 Self::FileReadError(e)
112 }
113}
114
115impl From<CommentError> for DocError {
116 fn from(e: CommentError) -> Self {
117 Self::CommentError(e)
118 }
119}
120
121impl From<ParserError> for DocError {
122 fn from(e: ParserError) -> Self {
123 Self::SqlParserError(e)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use sqlparser::parser::ParserError;
130
131 use crate::{comments::CommentError, docs::TableDoc, error::DocError};
132
133 #[test]
134 fn test_doc_errors() {
135 use std::fs;
136
137 use crate::comments::Location;
138 let Err(io_error) = fs::read_dir("INVALID") else {
139 panic!("there should not be a directory called INVALID")
140 };
141 let io_error_str = io_error.to_string();
142 let read_error = DocError::FileReadError(io_error);
143 let expected_read_error = "file read error: ".to_owned() + &io_error_str;
144 assert!(read_error.to_string().contains(&expected_read_error));
145
146 let comment_error = DocError::CommentError(CommentError::UnmatchedMultilineCommentStart {
147 location: Location::default(),
148 });
149 let expected_comment_error =
150 "comment parse error: unmatched block comment start at line 1, column 1";
151 assert_eq!(comment_error.to_string(), expected_comment_error);
152
153 let sql_error = DocError::SqlParserError(ParserError::RecursionLimitExceeded);
154 let expected_sql_error = "SQL parse error sql parser error: recursion limit exceeded";
155 assert_eq!(sql_error.to_string(), expected_sql_error);
156 }
157
158 #[test]
159 fn test_doc_errors_from() {
160 use crate::comments::Location;
161 use std::fs;
162
163 let Err(io_error) = fs::read_dir("INVALID") else {
164 panic!("there should not be a directory called INVALID")
165 };
166 let io_kind = io_error.kind();
167 let doc_io_error: DocError = io_error.into();
168 assert!(matches!(doc_io_error, DocError::FileReadError(_)));
169 if let DocError::FileReadError(inner) = doc_io_error {
170 assert_eq!(inner.kind(), io_kind);
171 }
172
173 let comment_error =
174 CommentError::UnmatchedMultilineCommentStart { location: Location::default() };
175 let comment_error_str = comment_error.to_string();
176 let doc_comment_error: DocError = comment_error.into();
177 assert!(matches!(doc_comment_error, DocError::CommentError(_)));
178 if let DocError::CommentError(inner) = doc_comment_error {
179 assert_eq!(inner.to_string(), comment_error_str);
180 }
181
182 let parser_error = ParserError::RecursionLimitExceeded;
183 let parser_error_str = parser_error.to_string();
184 let doc_parser_error: DocError = parser_error.into();
185 assert!(matches!(doc_parser_error, DocError::SqlParserError(_)));
186 if let DocError::SqlParserError(inner) = doc_parser_error {
187 assert_eq!(inner.to_string(), parser_error_str);
188 }
189 }
190
191 #[test]
192 fn test_doc_error_source() {
193 use std::{error::Error, fs};
194
195 use crate::comments::Location;
196
197 let Err(io_err) = fs::read_dir("INVALID") else {
198 panic!("there should not be a directory called INVALID")
199 };
200 let io_err_str = io_err.to_string();
201 let doc_io = DocError::FileReadError(io_err);
202 let src =
203 doc_io.source().unwrap_or_else(|| panic!("expected Some(source) for FileReadError"));
204 assert_eq!(src.to_string(), io_err_str);
205
206 let comment =
207 CommentError::UnmatchedMultilineCommentStart { location: Location::default() };
208 let comment_str = comment.to_string();
209 let doc_comment = DocError::CommentError(comment);
210 let src = doc_comment
211 .source()
212 .unwrap_or_else(|| panic!("expected Some(source) for CommentError"));
213 assert_eq!(src.to_string(), comment_str);
214
215 let parser = ParserError::RecursionLimitExceeded;
216 let parser_str = parser.to_string();
217 let doc_parser = DocError::SqlParserError(parser);
218 let src = doc_parser
219 .source()
220 .unwrap_or_else(|| panic!("expected Some(source) for SqlParserError"));
221 assert_eq!(src.to_string(), parser_str);
222 }
223
224 fn table_doc_for_test(name: &str) -> TableDoc {
225 TableDoc::new(None, name.to_string(), None, vec![], None)
226 }
227 #[test]
228 fn test_doc_error_display_invalid_object_name() {
229 let e =
230 DocError::InvalidObjectName { message: "bad object".to_string(), line: 12, column: 34 };
231 assert_eq!(e.to_string(), "bad object at line 12, column 34");
232 }
233
234 #[test]
235 fn test_doc_error_display_table_not_found() {
236 let e = DocError::TableNotFound { name: "users".to_string() };
237 assert_eq!(e.to_string(), "Table not found in SqlDoc: users");
238 }
239
240 #[test]
241 fn test_doc_error_display_duplicate_tables_found() {
242 let t1 = table_doc_for_test("dup_table");
243 let t2 = table_doc_for_test("dup_table");
244 let e = DocError::DuplicateTablesFound { tables: vec![t1, t2] };
245 let s = e.to_string();
246 assert!(s.contains("Duplicate tables found:"));
247 assert!(s.contains("dup_table"), "output was: {s}");
248 }
249
250 #[test]
251 fn test_doc_error_source_none_for_non_wrapped_variants() {
252 use std::error::Error as _;
253 let invalid = DocError::InvalidObjectName { message: "x".to_string(), line: 1, column: 1 };
254 assert!(invalid.source().is_none());
255 let not_found = DocError::TableNotFound { name: "x".to_string() };
256 assert!(not_found.source().is_none());
257 let dup = DocError::DuplicateTablesFound { tables: vec![] };
258 assert!(dup.source().is_none());
259 }
260
261 #[test]
262 fn test_doc_error_display_column_not_found() {
263 let e = DocError::ColumnNotFound { name: "id".to_string() };
264 assert_eq!(e.to_string(), "Column not found in TableDoc: id");
265 }
266
267 #[test]
268 fn test_doc_error_display_duplicate_columns_found() {
269 use crate::docs::ColumnDoc;
270
271 let c1 = ColumnDoc::new("dup_col".to_string(), None);
272 let c2 = ColumnDoc::new("dup_col".to_string(), None);
273 let e = DocError::DuplicateColumnsFound { columns: vec![c1, c2] };
274
275 let s = e.to_string();
276 assert!(s.contains("Duplicate columns found:"), "output was: {s}");
277 assert!(s.contains("dup_col"), "output was: {s}");
278 }
279
280 #[test]
281 fn test_doc_error_source_none_for_column_variants() {
282 use std::error::Error as _;
283
284 let not_found = DocError::ColumnNotFound { name: "x".to_string() };
285 assert!(not_found.source().is_none());
286
287 let dup = DocError::DuplicateColumnsFound { columns: vec![] };
288 assert!(dup.source().is_none());
289 }
290
291 #[test]
292 fn test_doc_error_display_table_with_schema_not_found() {
293 let e = DocError::TableWithSchemaNotFound {
294 name: "events".to_string(),
295 schema: "analytics".to_string(),
296 };
297 assert_eq!(e.to_string(), "Table: events with schema: analytics not found in SqlDoc\n");
298 }
299
300 #[test]
301 fn test_doc_error_source_none_for_table_with_schema_not_found() {
302 use std::error::Error as _;
303 let e = DocError::TableWithSchemaNotFound {
304 name: "events".to_string(),
305 schema: "analytics".to_string(),
306 };
307 assert!(e.source().is_none());
308 }
309}