1use std::path::PathBuf;
2
3use crate::model::SourceRange;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize)]
6#[non_exhaustive]
7pub enum Severity {
8 Warning,
9 Error,
10}
11
12#[derive(Debug, Clone, serde::Serialize)]
13pub struct Diagnostic {
14 pub path: PathBuf,
15 pub severity: Severity,
16 pub message: String,
17 pub source_range: Option<SourceRange>,
18}
19
20#[derive(Debug, thiserror::Error)]
21pub enum Error {
22 #[error("IO: {0}")]
23 Io(#[from] std::io::Error),
24
25 #[error("parse error in {path}: {message}")]
26 Parse { path: PathBuf, message: String },
27
28 #[error("query error ({language}): {message}")]
29 Query {
30 language: crate::language::LangId,
31 message: String,
32 },
33
34 #[error("config: {0}")]
35 Config(String),
36
37 #[error("graph error: {0}")]
38 Graph(String),
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44 use crate::language::LangId;
45 use std::path::PathBuf;
46
47 #[test]
48 fn io_error_from_std_io() {
49 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
50 let err: Error = io_err.into();
51 assert!(matches!(err, Error::Io(_)));
52 assert!(err.to_string().contains("file not found"));
53 }
54
55 #[test]
56 fn parse_error_display_format() {
57 let err = Error::Parse {
58 path: PathBuf::from("foo.bar"),
59 message: "bad syntax".into(),
60 };
61 let displayed = err.to_string();
62 assert!(displayed.contains("foo.bar"), "{displayed}");
63 assert!(displayed.contains("bad syntax"), "{displayed}");
64 assert!(displayed.starts_with("parse error in"), "{displayed}");
65 }
66
67 #[test]
68 fn query_error_display_format() {
69 let err = Error::Query {
70 language: LangId::Rust,
71 message: "no match".into(),
72 };
73 let displayed = err.to_string();
74 assert!(displayed.contains("rust"), "{displayed}");
75 assert!(displayed.contains("no match"), "{displayed}");
76 assert!(displayed.starts_with("query error"), "{displayed}");
77 }
78
79 #[test]
80 fn config_error_display() {
81 let err = Error::Config("bad setting".into());
82 let displayed = err.to_string();
83 assert!(displayed.starts_with("config:"), "{displayed}");
84 assert!(displayed.contains("bad setting"), "{displayed}");
85 }
86
87 #[test]
88 fn diagnostic_construction() {
89 let sr = SourceRange {
90 byte_start: 0,
91 byte_end: 10,
92 start: crate::model::LineColumn { line: 1, column: 0 },
93 end: crate::model::LineColumn {
94 line: 1,
95 column: 10,
96 },
97 };
98 let d = Diagnostic {
99 path: PathBuf::from("test.rs"),
100 severity: Severity::Error,
101 message: "undefined variable".into(),
102 source_range: Some(sr.clone()),
103 };
104 assert_eq!(d.path, PathBuf::from("test.rs"));
105 assert_eq!(d.severity, Severity::Error);
106 assert_eq!(d.message, "undefined variable");
107 assert!(d.source_range.is_some());
108 let range = d.source_range.unwrap();
109 assert_eq!(range.byte_start, 0);
110 assert_eq!(range.byte_end, 10);
111 }
112
113 #[test]
114 fn severity_variants() {
115 assert_ne!(Severity::Warning, Severity::Error);
116 let v = [Severity::Warning, Severity::Error];
117 assert_eq!(v.len(), 2);
118 }
119
120 #[test]
121 fn graph_error_display_format() {
122 let err = Error::Graph("cycle detected".into());
123 let displayed = err.to_string();
124 assert!(displayed.starts_with("graph error:"), "{displayed}");
125 assert!(displayed.contains("cycle detected"), "{displayed}");
126 }
127
128 #[test]
129 fn graph_error_matches() {
130 let err = Error::Graph("test".into());
131 assert!(matches!(err, Error::Graph(_)));
132 }
133}