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