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