syncable_cli/analyzer/hadolint/
types.rs1use std::cmp::Ordering;
10use std::fmt;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
17pub enum Severity {
18 Error,
20 Warning,
22 #[default]
24 Info,
25 Style,
27 Ignore,
29}
30
31impl Severity {
32 pub fn parse(s: &str) -> Option<Self> {
34 match s.to_lowercase().as_str() {
35 "error" => Some(Self::Error),
36 "warning" => Some(Self::Warning),
37 "info" => Some(Self::Info),
38 "style" => Some(Self::Style),
39 "ignore" | "none" => Some(Self::Ignore),
40 _ => None,
41 }
42 }
43
44 pub fn as_str(&self) -> &'static str {
46 match self {
47 Self::Error => "error",
48 Self::Warning => "warning",
49 Self::Info => "info",
50 Self::Style => "style",
51 Self::Ignore => "ignore",
52 }
53 }
54}
55
56impl fmt::Display for Severity {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "{}", self.as_str())
59 }
60}
61
62impl Ord for Severity {
63 fn cmp(&self, other: &Self) -> Ordering {
64 let self_val = match self {
66 Self::Error => 0,
67 Self::Warning => 1,
68 Self::Info => 2,
69 Self::Style => 3,
70 Self::Ignore => 4,
71 };
72 let other_val = match other {
73 Self::Error => 0,
74 Self::Warning => 1,
75 Self::Info => 2,
76 Self::Style => 3,
77 Self::Ignore => 4,
78 };
79 other_val.cmp(&self_val)
81 }
82}
83
84impl PartialOrd for Severity {
85 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
86 Some(self.cmp(other))
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash)]
92pub struct RuleCode(pub String);
93
94impl RuleCode {
95 pub fn new(code: impl Into<String>) -> Self {
97 Self(code.into())
98 }
99
100 pub fn as_str(&self) -> &str {
102 &self.0
103 }
104
105 pub fn is_dockerfile_rule(&self) -> bool {
107 self.0.starts_with("DL")
108 }
109
110 pub fn is_shellcheck_rule(&self) -> bool {
112 self.0.starts_with("SC")
113 }
114}
115
116impl fmt::Display for RuleCode {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 write!(f, "{}", self.0)
119 }
120}
121
122impl From<&str> for RuleCode {
123 fn from(s: &str) -> Self {
124 Self::new(s)
125 }
126}
127
128impl From<String> for RuleCode {
129 fn from(s: String) -> Self {
130 Self(s)
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
136pub struct CheckFailure {
137 pub code: RuleCode,
139 pub severity: Severity,
141 pub message: String,
143 pub line: u32,
145 pub column: Option<u32>,
147}
148
149impl CheckFailure {
150 pub fn new(
152 code: impl Into<RuleCode>,
153 severity: Severity,
154 message: impl Into<String>,
155 line: u32,
156 ) -> Self {
157 Self {
158 code: code.into(),
159 severity,
160 message: message.into(),
161 line,
162 column: None,
163 }
164 }
165
166 pub fn with_column(
168 code: impl Into<RuleCode>,
169 severity: Severity,
170 message: impl Into<String>,
171 line: u32,
172 column: u32,
173 ) -> Self {
174 Self {
175 code: code.into(),
176 severity,
177 message: message.into(),
178 line,
179 column: Some(column),
180 }
181 }
182}
183
184impl Ord for CheckFailure {
185 fn cmp(&self, other: &Self) -> Ordering {
186 self.line.cmp(&other.line)
188 }
189}
190
191impl PartialOrd for CheckFailure {
192 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
193 Some(self.cmp(other))
194 }
195}
196
197#[derive(Debug, Clone)]
202pub struct State<T> {
203 pub failures: Vec<CheckFailure>,
205 pub state: T,
207}
208
209impl<T: Default> Default for State<T> {
210 fn default() -> Self {
211 Self {
212 failures: Vec::new(),
213 state: T::default(),
214 }
215 }
216}
217
218impl<T> State<T> {
219 pub fn new(state: T) -> Self {
221 Self {
222 failures: Vec::new(),
223 state,
224 }
225 }
226
227 pub fn add_failure(&mut self, failure: CheckFailure) {
229 self.failures.push(failure);
230 }
231
232 pub fn modify<F>(&mut self, f: F)
234 where
235 F: FnOnce(&mut T),
236 {
237 f(&mut self.state);
238 }
239
240 pub fn replace_state(&mut self, new_state: T) {
242 self.state = new_state;
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249
250 #[test]
251 fn test_severity_ordering() {
252 assert!(Severity::Error > Severity::Warning);
253 assert!(Severity::Warning > Severity::Info);
254 assert!(Severity::Info > Severity::Style);
255 assert!(Severity::Style > Severity::Ignore);
256 }
257
258 #[test]
259 fn test_severity_from_str() {
260 assert_eq!(Severity::parse("error"), Some(Severity::Error));
261 assert_eq!(Severity::parse("WARNING"), Some(Severity::Warning));
262 assert_eq!(Severity::parse("Info"), Some(Severity::Info));
263 assert_eq!(Severity::parse("style"), Some(Severity::Style));
264 assert_eq!(Severity::parse("ignore"), Some(Severity::Ignore));
265 assert_eq!(Severity::parse("none"), Some(Severity::Ignore));
266 assert_eq!(Severity::parse("invalid"), None);
267 }
268
269 #[test]
270 fn test_rule_code() {
271 let dl_code = RuleCode::new("DL3008");
272 assert!(dl_code.is_dockerfile_rule());
273 assert!(!dl_code.is_shellcheck_rule());
274
275 let sc_code = RuleCode::new("SC2086");
276 assert!(!sc_code.is_dockerfile_rule());
277 assert!(sc_code.is_shellcheck_rule());
278 }
279
280 #[test]
281 fn test_check_failure_ordering() {
282 let f1 = CheckFailure::new("DL3008", Severity::Warning, "msg1", 5);
283 let f2 = CheckFailure::new("DL3009", Severity::Info, "msg2", 10);
284 let f3 = CheckFailure::new("DL3010", Severity::Error, "msg3", 3);
285
286 let mut failures = vec![f1.clone(), f2.clone(), f3.clone()];
287 failures.sort();
288
289 assert_eq!(failures[0].line, 3);
290 assert_eq!(failures[1].line, 5);
291 assert_eq!(failures[2].line, 10);
292 }
293
294 #[test]
295 fn test_state() {
296 let mut state: State<i32> = State::new(0);
297 assert_eq!(state.state, 0);
298 assert!(state.failures.is_empty());
299
300 state.modify(|s| *s += 10);
301 assert_eq!(state.state, 10);
302
303 state.add_failure(CheckFailure::new("DL3008", Severity::Warning, "test", 1));
304 assert_eq!(state.failures.len(), 1);
305 }
306}