1use std::cmp::Ordering;
10use std::fmt;
11use std::path::PathBuf;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
18pub enum Severity {
19 Error,
21 #[default]
23 Warning,
24 Info,
26 Style,
28 Ignore,
30}
31
32impl Severity {
33 pub fn parse(s: &str) -> Option<Self> {
35 match s.to_lowercase().as_str() {
36 "error" => Some(Self::Error),
37 "warning" => Some(Self::Warning),
38 "info" => Some(Self::Info),
39 "style" => Some(Self::Style),
40 "ignore" | "none" | "off" => Some(Self::Ignore),
41 _ => None,
42 }
43 }
44
45 pub fn as_str(&self) -> &'static str {
47 match self {
48 Self::Error => "error",
49 Self::Warning => "warning",
50 Self::Info => "info",
51 Self::Style => "style",
52 Self::Ignore => "ignore",
53 }
54 }
55}
56
57impl fmt::Display for Severity {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(f, "{}", self.as_str())
60 }
61}
62
63impl Ord for Severity {
64 fn cmp(&self, other: &Self) -> Ordering {
65 let self_val = match self {
67 Self::Error => 0,
68 Self::Warning => 1,
69 Self::Info => 2,
70 Self::Style => 3,
71 Self::Ignore => 4,
72 };
73 let other_val = match other {
74 Self::Error => 0,
75 Self::Warning => 1,
76 Self::Info => 2,
77 Self::Style => 3,
78 Self::Ignore => 4,
79 };
80 other_val.cmp(&self_val)
82 }
83}
84
85impl PartialOrd for Severity {
86 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
87 Some(self.cmp(other))
88 }
89}
90
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
93pub enum RuleCategory {
94 Structure,
96 Values,
98 Template,
100 Security,
102 BestPractice,
104}
105
106impl RuleCategory {
107 pub fn prefix(&self) -> &'static str {
109 match self {
110 Self::Structure => "HL1",
111 Self::Values => "HL2",
112 Self::Template => "HL3",
113 Self::Security => "HL4",
114 Self::BestPractice => "HL5",
115 }
116 }
117
118 pub fn display_name(&self) -> &'static str {
120 match self {
121 Self::Structure => "Chart Structure",
122 Self::Values => "Values Validation",
123 Self::Template => "Template Syntax",
124 Self::Security => "Security",
125 Self::BestPractice => "Best Practice",
126 }
127 }
128
129 pub fn from_code(code: &str) -> Option<Self> {
131 if code.starts_with("HL1") {
132 Some(Self::Structure)
133 } else if code.starts_with("HL2") {
134 Some(Self::Values)
135 } else if code.starts_with("HL3") {
136 Some(Self::Template)
137 } else if code.starts_with("HL4") {
138 Some(Self::Security)
139 } else if code.starts_with("HL5") {
140 Some(Self::BestPractice)
141 } else {
142 None
143 }
144 }
145}
146
147impl fmt::Display for RuleCategory {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "{}", self.display_name())
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Hash)]
155pub struct RuleCode(pub String);
156
157impl RuleCode {
158 pub fn new(code: impl Into<String>) -> Self {
160 Self(code.into())
161 }
162
163 pub fn as_str(&self) -> &str {
165 &self.0
166 }
167
168 pub fn category(&self) -> Option<RuleCategory> {
170 RuleCategory::from_code(&self.0)
171 }
172
173 pub fn is_structure_rule(&self) -> bool {
175 self.0.starts_with("HL1")
176 }
177
178 pub fn is_values_rule(&self) -> bool {
180 self.0.starts_with("HL2")
181 }
182
183 pub fn is_template_rule(&self) -> bool {
185 self.0.starts_with("HL3")
186 }
187
188 pub fn is_security_rule(&self) -> bool {
190 self.0.starts_with("HL4")
191 }
192
193 pub fn is_best_practice_rule(&self) -> bool {
195 self.0.starts_with("HL5")
196 }
197}
198
199impl fmt::Display for RuleCode {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 write!(f, "{}", self.0)
202 }
203}
204
205impl From<&str> for RuleCode {
206 fn from(s: &str) -> Self {
207 Self::new(s)
208 }
209}
210
211impl From<String> for RuleCode {
212 fn from(s: String) -> Self {
213 Self(s)
214 }
215}
216
217#[derive(Debug, Clone)]
219pub struct RuleMeta {
220 pub code: RuleCode,
222 pub name: &'static str,
224 pub description: &'static str,
226 pub severity: Severity,
228 pub category: RuleCategory,
230 pub fixable: bool,
232}
233
234impl RuleMeta {
235 pub const fn new(
237 _code: &'static str,
238 name: &'static str,
239 description: &'static str,
240 severity: Severity,
241 category: RuleCategory,
242 fixable: bool,
243 ) -> Self {
244 Self {
245 code: RuleCode(String::new()), name,
247 description,
248 severity,
249 category,
250 fixable,
251 }
252 }
253}
254
255#[derive(Debug, Clone, PartialEq, Eq)]
257pub struct CheckFailure {
258 pub code: RuleCode,
260 pub severity: Severity,
262 pub message: String,
264 pub file: PathBuf,
266 pub line: u32,
268 pub column: Option<u32>,
270 pub fixable: bool,
272 pub category: RuleCategory,
274}
275
276impl CheckFailure {
277 pub fn new(
279 code: impl Into<RuleCode>,
280 severity: Severity,
281 message: impl Into<String>,
282 file: impl Into<PathBuf>,
283 line: u32,
284 category: RuleCategory,
285 ) -> Self {
286 Self {
287 code: code.into(),
288 severity,
289 message: message.into(),
290 file: file.into(),
291 line,
292 column: None,
293 fixable: false,
294 category,
295 }
296 }
297
298 pub fn with_column(
300 code: impl Into<RuleCode>,
301 severity: Severity,
302 message: impl Into<String>,
303 file: impl Into<PathBuf>,
304 line: u32,
305 column: u32,
306 category: RuleCategory,
307 ) -> Self {
308 Self {
309 code: code.into(),
310 severity,
311 message: message.into(),
312 file: file.into(),
313 line,
314 column: Some(column),
315 fixable: false,
316 category,
317 }
318 }
319
320 pub fn set_fixable(mut self, fixable: bool) -> Self {
322 self.fixable = fixable;
323 self
324 }
325}
326
327impl Ord for CheckFailure {
328 fn cmp(&self, other: &Self) -> Ordering {
329 match self.file.cmp(&other.file) {
331 Ordering::Equal => self.line.cmp(&other.line),
332 other => other,
333 }
334 }
335}
336
337impl PartialOrd for CheckFailure {
338 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
339 Some(self.cmp(other))
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn test_severity_ordering() {
349 assert!(Severity::Error > Severity::Warning);
350 assert!(Severity::Warning > Severity::Info);
351 assert!(Severity::Info > Severity::Style);
352 assert!(Severity::Style > Severity::Ignore);
353 }
354
355 #[test]
356 fn test_severity_from_str() {
357 assert_eq!(Severity::parse("error"), Some(Severity::Error));
358 assert_eq!(Severity::parse("WARNING"), Some(Severity::Warning));
359 assert_eq!(Severity::parse("Info"), Some(Severity::Info));
360 assert_eq!(Severity::parse("style"), Some(Severity::Style));
361 assert_eq!(Severity::parse("ignore"), Some(Severity::Ignore));
362 assert_eq!(Severity::parse("off"), Some(Severity::Ignore));
363 assert_eq!(Severity::parse("invalid"), None);
364 }
365
366 #[test]
367 fn test_rule_code_category() {
368 assert!(RuleCode::new("HL1001").is_structure_rule());
369 assert!(RuleCode::new("HL2001").is_values_rule());
370 assert!(RuleCode::new("HL3001").is_template_rule());
371 assert!(RuleCode::new("HL4001").is_security_rule());
372 assert!(RuleCode::new("HL5001").is_best_practice_rule());
373 }
374
375 #[test]
376 fn test_rule_category_from_code() {
377 assert_eq!(
378 RuleCategory::from_code("HL1001"),
379 Some(RuleCategory::Structure)
380 );
381 assert_eq!(
382 RuleCategory::from_code("HL2001"),
383 Some(RuleCategory::Values)
384 );
385 assert_eq!(
386 RuleCategory::from_code("HL3001"),
387 Some(RuleCategory::Template)
388 );
389 assert_eq!(
390 RuleCategory::from_code("HL4001"),
391 Some(RuleCategory::Security)
392 );
393 assert_eq!(
394 RuleCategory::from_code("HL5001"),
395 Some(RuleCategory::BestPractice)
396 );
397 assert_eq!(RuleCategory::from_code("XX1001"), None);
398 }
399
400 #[test]
401 fn test_check_failure_ordering() {
402 let f1 = CheckFailure::new(
403 "HL1001",
404 Severity::Warning,
405 "msg1",
406 "Chart.yaml",
407 5,
408 RuleCategory::Structure,
409 );
410 let f2 = CheckFailure::new(
411 "HL1002",
412 Severity::Info,
413 "msg2",
414 "Chart.yaml",
415 10,
416 RuleCategory::Structure,
417 );
418 let f3 = CheckFailure::new(
419 "HL1003",
420 Severity::Error,
421 "msg3",
422 "Chart.yaml",
423 3,
424 RuleCategory::Structure,
425 );
426 let f4 = CheckFailure::new(
427 "HL3001",
428 Severity::Error,
429 "msg4",
430 "templates/deployment.yaml",
431 1,
432 RuleCategory::Template,
433 );
434
435 let mut failures = vec![f1.clone(), f2.clone(), f3.clone(), f4.clone()];
436 failures.sort();
437
438 assert_eq!(failures[0].line, 3);
439 assert_eq!(failures[1].line, 5);
440 assert_eq!(failures[2].line, 10);
441 assert_eq!(
442 failures[3].file.to_str().unwrap(),
443 "templates/deployment.yaml"
444 );
445 }
446}