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