1#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
7pub struct Violation {
8 pub rule_id: String,
10 pub rule_name: String,
12 pub message: String,
14 pub line: usize,
16 pub column: usize,
18 pub severity: Severity,
20}
21
22#[derive(
24 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize,
25)]
26pub enum Severity {
27 Info,
29 Warning,
31 Error,
33}
34
35impl std::fmt::Display for Severity {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 Severity::Info => write!(f, "info"),
39 Severity::Warning => write!(f, "warning"),
40 Severity::Error => write!(f, "error"),
41 }
42 }
43}
44
45impl std::fmt::Display for Violation {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(
48 f,
49 "{}:{}:{}: {}/{}: {}",
50 self.line, self.column, self.severity, self.rule_id, self.rule_name, self.message
51 )
52 }
53}
54
55#[cfg(test)]
56mod tests {
57 use super::*;
58
59 #[test]
60 fn test_severity_display() {
61 assert_eq!(format!("{}", Severity::Info), "info");
62 assert_eq!(format!("{}", Severity::Warning), "warning");
63 assert_eq!(format!("{}", Severity::Error), "error");
64 }
65
66 #[test]
67 fn test_severity_ordering() {
68 assert!(Severity::Info < Severity::Warning);
69 assert!(Severity::Warning < Severity::Error);
70 assert!(Severity::Info < Severity::Error);
71 }
72
73 #[test]
74 fn test_violation_creation() {
75 let violation = Violation {
76 rule_id: "MD001".to_string(),
77 rule_name: "heading-increment".to_string(),
78 message: "Heading levels should only increment by one level at a time".to_string(),
79 line: 5,
80 column: 1,
81 severity: Severity::Warning,
82 };
83
84 assert_eq!(violation.rule_id, "MD001");
85 assert_eq!(violation.rule_name, "heading-increment");
86 assert_eq!(violation.line, 5);
87 assert_eq!(violation.column, 1);
88 assert_eq!(violation.severity, Severity::Warning);
89 }
90
91 #[test]
92 fn test_violation_display() {
93 let violation = Violation {
94 rule_id: "MD013".to_string(),
95 rule_name: "line-length".to_string(),
96 message: "Line too long".to_string(),
97 line: 10,
98 column: 81,
99 severity: Severity::Error,
100 };
101
102 let expected = "10:81:error: MD013/line-length: Line too long";
103 assert_eq!(format!("{violation}"), expected);
104 }
105
106 #[test]
107 fn test_violation_equality() {
108 let violation1 = Violation {
109 rule_id: "MD001".to_string(),
110 rule_name: "heading-increment".to_string(),
111 message: "Test message".to_string(),
112 line: 1,
113 column: 1,
114 severity: Severity::Warning,
115 };
116
117 let violation2 = Violation {
118 rule_id: "MD001".to_string(),
119 rule_name: "heading-increment".to_string(),
120 message: "Test message".to_string(),
121 line: 1,
122 column: 1,
123 severity: Severity::Warning,
124 };
125
126 let violation3 = Violation {
127 rule_id: "MD002".to_string(),
128 rule_name: "first-heading-h1".to_string(),
129 message: "Different message".to_string(),
130 line: 2,
131 column: 1,
132 severity: Severity::Error,
133 };
134
135 assert_eq!(violation1, violation2);
136 assert_ne!(violation1, violation3);
137 }
138
139 #[test]
140 fn test_violation_clone() {
141 let original = Violation {
142 rule_id: "MD040".to_string(),
143 rule_name: "fenced-code-language".to_string(),
144 message: "Fenced code blocks should have a language specified".to_string(),
145 line: 15,
146 column: 3,
147 severity: Severity::Info,
148 };
149
150 let cloned = original.clone();
151 assert_eq!(original, cloned);
152 }
153
154 #[test]
155 fn test_violation_debug() {
156 let violation = Violation {
157 rule_id: "MD025".to_string(),
158 rule_name: "single-h1".to_string(),
159 message: "Multiple top level headings in the same document".to_string(),
160 line: 20,
161 column: 1,
162 severity: Severity::Warning,
163 };
164
165 let debug_str = format!("{violation:?}");
166 assert!(debug_str.contains("MD025"));
167 assert!(debug_str.contains("single-h1"));
168 assert!(debug_str.contains("Multiple top level headings"));
169 assert!(debug_str.contains("line: 20"));
170 assert!(debug_str.contains("column: 1"));
171 assert!(debug_str.contains("Warning"));
172 }
173
174 #[test]
175 fn test_all_severity_variants() {
176 let severities = [Severity::Info, Severity::Warning, Severity::Error];
177
178 for severity in &severities {
179 let violation = Violation {
180 rule_id: "TEST".to_string(),
181 rule_name: "test-rule".to_string(),
182 message: "Test message".to_string(),
183 line: 1,
184 column: 1,
185 severity: *severity,
186 };
187
188 let display_str = format!("{violation}");
190 assert!(display_str.contains(&format!("{severity}")));
191 }
192 }
193}