1use std::fmt::Formatter;
7
8use nvim_oxi::Dictionary;
9use serde::de::Deserializer;
10use serde::de::Visitor;
11use strum::EnumCount;
12use strum::EnumIter;
13
14mod config;
15mod filter;
16mod filters;
17mod formatter;
18mod sorter;
19
20pub fn dict() -> Dictionary {
22 dict! {
23 "filter": fn_from!(filter::filter),
24 "sort": fn_from!(sorter::sort),
25 "format": fn_from!(formatter::format),
26 "config": config::get()
27 }
28}
29
30#[derive(Clone, Copy, Debug, EnumCount, EnumIter, Eq, Hash, PartialEq)]
34pub enum DiagnosticSeverity {
35 Error,
36 Warn,
37 Info,
38 Hint,
39 Other,
40}
41
42impl DiagnosticSeverity {
43 pub const VARIANT_COUNT: usize = <Self as strum::EnumCount>::COUNT;
45
46 pub const fn to_number(self) -> u8 {
48 match self {
49 Self::Error => 1,
50 Self::Warn => 2,
51 Self::Info => 3,
52 Self::Hint => 4,
53 Self::Other => 0,
54 }
55 }
56
57 pub const fn symbol(self) -> &'static str {
59 match self {
60 Self::Error => "E",
61 Self::Warn => "W",
62 Self::Info => "I",
63 Self::Hint => "H",
64 Self::Other => "",
65 }
66 }
67}
68
69impl<'de> serde::Deserialize<'de> for DiagnosticSeverity {
71 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
72 where
73 D: Deserializer<'de>,
74 {
75 struct SevVisitor;
76
77 impl Visitor<'_> for SevVisitor {
78 type Value = DiagnosticSeverity;
79
80 fn expecting(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
81 write!(f, "a severity: 1-4 or (error|warn|info|hint) or short alias")
82 }
83
84 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
85 where
86 E: serde::de::Error,
87 {
88 Ok(match v {
89 1 => DiagnosticSeverity::Error,
90 2 => DiagnosticSeverity::Warn,
91 3 => DiagnosticSeverity::Info,
92 4 => DiagnosticSeverity::Hint,
93 _ => DiagnosticSeverity::Other,
94 })
95 }
96
97 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
98 where
99 E: serde::de::Error,
100 {
101 if v < 0 {
102 return Ok(DiagnosticSeverity::Other);
103 }
104 self.visit_u64(v.cast_unsigned())
105 }
106
107 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
108 where
109 E: serde::de::Error,
110 {
111 let norm = s.trim().to_ascii_lowercase();
112 if let Ok(n) = norm.parse::<u64>() {
113 return self.visit_u64(n);
114 }
115 Ok(match norm.as_str() {
116 "error" | "err" | "e" => DiagnosticSeverity::Error,
117 "warn" | "warning" | "w" => DiagnosticSeverity::Warn,
118 "info" | "information" | "i" => DiagnosticSeverity::Info,
119 "hint" | "h" => DiagnosticSeverity::Hint,
120 _ => DiagnosticSeverity::Other,
121 })
122 }
123 }
124
125 deserializer.deserialize_any(SevVisitor)
126 }
127}
128
129#[cfg(test)]
130mod tests {
131 use rstest::rstest;
132 use strum::IntoEnumIterator;
133
134 use super::*;
135
136 #[rstest]
137 #[case("1", DiagnosticSeverity::Error)]
138 #[case("2", DiagnosticSeverity::Warn)]
139 #[case("3", DiagnosticSeverity::Info)]
140 #[case("4", DiagnosticSeverity::Hint)]
141 #[case("\"1\"", DiagnosticSeverity::Error)]
142 #[case("\"2\"", DiagnosticSeverity::Warn)]
143 #[case("\"3\"", DiagnosticSeverity::Info)]
144 #[case("\"4\"", DiagnosticSeverity::Hint)]
145 #[case("\"error\"", DiagnosticSeverity::Error)]
146 #[case("\"err\"", DiagnosticSeverity::Error)]
147 #[case("\"e\"", DiagnosticSeverity::Error)]
148 #[case("\"warn\"", DiagnosticSeverity::Warn)]
149 #[case("\"warning\"", DiagnosticSeverity::Warn)]
150 #[case("\"w\"", DiagnosticSeverity::Warn)]
151 #[case("\"info\"", DiagnosticSeverity::Info)]
152 #[case("\"information\"", DiagnosticSeverity::Info)]
153 #[case("\"i\"", DiagnosticSeverity::Info)]
154 #[case("\"hint\"", DiagnosticSeverity::Hint)]
155 #[case("\"h\"", DiagnosticSeverity::Hint)]
156 #[case("\" Error \"", DiagnosticSeverity::Error)]
157 #[case("\"WARNING\"", DiagnosticSeverity::Warn)]
158 #[case("\" Info \"", DiagnosticSeverity::Info)]
159 #[case("\"H\"", DiagnosticSeverity::Hint)]
160 #[case("-1", DiagnosticSeverity::Other)]
161 #[case("0", DiagnosticSeverity::Other)]
162 #[case("5", DiagnosticSeverity::Other)]
163 #[case("\"unknown\"", DiagnosticSeverity::Other)]
164 fn test_diagnostic_severity_deserializes_strings_as_expected(
165 #[case] input: &str,
166 #[case] expected: DiagnosticSeverity,
167 ) {
168 assert2::assert!(let Ok(sev) = serde_json::from_str::<DiagnosticSeverity>(input));
169 assert_eq!(sev, expected);
170 }
171
172 #[test]
173 fn test_diagnostic_severity_when_iterated_via_enumiter_yields_declared_order_and_matches_variant_count() {
174 let expected = [
175 DiagnosticSeverity::Error,
176 DiagnosticSeverity::Warn,
177 DiagnosticSeverity::Info,
178 DiagnosticSeverity::Hint,
179 DiagnosticSeverity::Other,
180 ];
181 let collected: Vec<DiagnosticSeverity> = DiagnosticSeverity::iter().collect();
182 pretty_assertions::assert_eq!(collected.as_slice(), expected.as_slice());
183 pretty_assertions::assert_eq!(collected.len(), DiagnosticSeverity::VARIANT_COUNT);
184 }
185
186 #[rstest]
187 #[case(1_i64, DiagnosticSeverity::Error)]
188 #[case(2_i64, DiagnosticSeverity::Warn)]
189 #[case(3_i64, DiagnosticSeverity::Info)]
190 #[case(4_i64, DiagnosticSeverity::Hint)]
191 #[case(-1_i64, DiagnosticSeverity::Other)]
192 #[case(0_i64, DiagnosticSeverity::Other)]
193 #[case(5_i64, DiagnosticSeverity::Other)]
194 fn test_diagnostic_severity_deserializes_numeric_values_as_expected(
195 #[case] input: i64,
196 #[case] expected: DiagnosticSeverity,
197 ) {
198 let json = input.to_string();
199 assert2::assert!(let Ok(sev) = serde_json::from_str::<DiagnosticSeverity>(&json));
200 assert_eq!(sev, expected);
201 }
202
203 #[test]
204 fn test_diagnostic_severity_deserializes_invalid_json_errors() {
205 assert2::assert!(let Err(err) = serde_json::from_str::<DiagnosticSeverity>("error"));
206 let msg = err.to_string();
207 assert!(msg.contains("expected value"), "unexpected error message: {msg}");
208 }
209}