archunit/metrics/fluentapi/
custom_metric_condition.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
use crate::checkable::execute_logged_check;
use crate::{
    checkable::{CheckResult, Checkable},
    common::{CheckOptions, gather_empty_test_violations},
    metrics::{CustomMetricViolation, TypeInfo},
    violation::Violation,
};

use super::CustomMetricSelection;

/// Executable user-defined metric predicate over selected Rust types.
#[derive(Debug, Clone)]
#[must_use = "an architecture rule has no effect until it is checked"]
pub struct CustomMetricCondition<Calculation, Predicate> {
    selection: CustomMetricSelection<Calculation>,
    predicate: Predicate,
}

impl<Calculation, Predicate> CustomMetricCondition<Calculation, Predicate>
where
    Calculation: Fn(&TypeInfo) -> f64,
{
    pub(super) const fn new(
        selection: CustomMetricSelection<Calculation>,
        predicate: Predicate,
    ) -> Self {
        Self {
            selection,
            predicate,
        }
    }

    /// Returns the custom metric name.
    #[must_use]
    pub fn metric_name(&self) -> &str {
        self.selection.name()
    }

    /// Returns the custom metric description.
    #[must_use]
    pub fn description(&self) -> &str {
        self.selection.description()
    }
}

impl<Calculation, Predicate> Checkable for CustomMetricCondition<Calculation, Predicate>
where
    Calculation: Fn(&TypeInfo) -> f64,
    Predicate: Fn(f64, &TypeInfo) -> bool,
{
    fn check_with(&self, options: &CheckOptions) -> CheckResult {
        execute_logged_check("metrics.custom-predicate", options, |logger| {
            logger.log_progress("selecting custom metric types")?;
            let types = self.selection.selected_types_with(options)?;
            logger.log_progress(format!("metric types={}", types.len()))?;
            let empty = gather_empty_test_violations(
                &types,
                "metric types",
                self.selection.filters(),
                false,
                options.allows_empty_tests(),
            );
            if let Some(violation) = empty.into_iter().next() {
                return Ok(vec![Violation::from(violation)]);
            }

            let mut violations = Vec::new();
            for type_info in &types {
                let value = (self.selection.calculation())(type_info);
                logger.log_metric(self.selection.name(), type_info.name(), value, None)?;
                if !(self.predicate)(value, type_info) {
                    violations.push(Violation::from(CustomMetricViolation::new(
                        type_info.clone(),
                        self.selection.name(),
                        self.selection.description(),
                        value,
                    )));
                }
            }
            Ok(violations)
        })
    }
}