1use log::debug;
2use rules::FilterConfig;
3use template_model::EntityType;
4
5pub trait Filterable {
8 fn rule_id(&self) -> &str;
9 fn category(&self) -> Option<&str>;
10 fn resource_id(&self) -> Option<&str>;
11 fn resource_type(&self) -> Option<&str>;
12 fn logical_id(&self) -> Option<&str>;
13 fn entity_type(&self) -> Option<EntityType>;
14}
15
16pub fn apply_filters<T: Filterable>(diagnostics: &mut Vec<T>, filters: &FilterConfig) {
18 if filters.is_empty() {
19 return;
20 }
21 let before = diagnostics.len();
22 diagnostics.retain(|d| {
23 filters.matches_rule(
24 d.rule_id(),
25 d.category(),
26 d.resource_id(),
27 d.resource_type(),
28 d.logical_id(),
29 d.entity_type(),
30 )
31 });
32 let removed = before - diagnostics.len();
33 if removed > 0 {
34 debug!("Filters removed {} diagnostics ({} -> {})", removed, before, diagnostics.len());
35 }
36}
37
38#[cfg(test)]
39mod tests {
40 use super::*;
41 use rules::{ResourceTypeFilter, RuleFilterConfig};
42
43 struct TestDiagnostic {
44 rule_id: String,
45 category: Option<String>,
46 resource_id: Option<String>,
47 resource_type: Option<String>,
48 logical_id: Option<String>,
49 entity_type: Option<EntityType>,
50 }
51
52 impl Filterable for TestDiagnostic {
53 fn rule_id(&self) -> &str {
54 &self.rule_id
55 }
56 fn category(&self) -> Option<&str> {
57 self.category.as_deref()
58 }
59 fn resource_id(&self) -> Option<&str> {
60 self.resource_id.as_deref()
61 }
62 fn resource_type(&self) -> Option<&str> {
63 self.resource_type.as_deref()
64 }
65 fn logical_id(&self) -> Option<&str> {
66 self.logical_id.as_deref()
67 }
68 fn entity_type(&self) -> Option<EntityType> {
69 self.entity_type
70 }
71 }
72
73 fn diag(rule_id: &str, category: &str) -> TestDiagnostic {
74 TestDiagnostic {
75 rule_id: rule_id.into(),
76 category: Some(category.into()),
77 resource_id: None,
78 resource_type: None,
79 logical_id: None,
80 entity_type: None,
81 }
82 }
83
84 fn diag_with_resource(rule_id: &str, category: &str, rid: &str, rtype: &str) -> TestDiagnostic {
85 TestDiagnostic {
86 rule_id: rule_id.into(),
87 category: Some(category.into()),
88 resource_id: Some(rid.into()),
89 resource_type: Some(rtype.into()),
90 logical_id: Some(rid.into()),
91 entity_type: Some(EntityType::Resource),
92 }
93 }
94
95 #[test]
96 fn apply_filters_removes_excluded_diagnostics() {
97 let filters = FilterConfig::new(
98 RuleFilterConfig::default(),
99 RuleFilterConfig { ids: vec!["E3012".into()], ..Default::default() },
100 );
101 let mut diagnostics = vec![diag("E3012", "schema"), diag("W9037", "security"), diag("E3013", "schema")];
102 apply_filters(&mut diagnostics, &filters);
103 let ids: Vec<&str> = diagnostics.iter().map(|d| d.rule_id()).collect();
104 assert_eq!(ids, vec!["W9037", "E3013"]);
105 }
106
107 #[test]
108 fn apply_filters_is_noop_when_filters_empty() {
109 let filters = FilterConfig::default();
110 let mut diagnostics = vec![diag("E3012", "schema"), diag("W9037", "security")];
111 apply_filters(&mut diagnostics, &filters);
112 assert_eq!(diagnostics.len(), 2);
113 }
114
115 #[test]
116 fn apply_filters_excludes_by_resource_type() {
117 let filters = FilterConfig::new(
118 RuleFilterConfig::default(),
119 RuleFilterConfig {
120 resource_types: vec![ResourceTypeFilter {
121 rule_id: Some("E3012".into()),
122 resource_type: "AWS::S3::Bucket".into(),
123 }],
124 ..Default::default()
125 },
126 );
127 let mut diagnostics = vec![
128 diag_with_resource("E3012", "schema", "MyBucket", "AWS::S3::Bucket"),
129 diag_with_resource("E3012", "schema", "MyInstance", "AWS::EC2::Instance"),
130 ];
131 apply_filters(&mut diagnostics, &filters);
132 assert_eq!(diagnostics.len(), 1);
133 assert_eq!(diagnostics[0].resource_type(), Some("AWS::EC2::Instance"));
134 }
135}