Skip to main content

diagnostics/
filter.rs

1use log::debug;
2use rules::FilterConfig;
3use template_model::EntityType;
4
5/// Trait for types that can be filtered by rule ID, category, resource ID,
6/// resource type, or entity (logical ID and entity type).
7pub 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
16/// Remove diagnostics that do not pass the include/exclude filter configuration.
17pub 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}