archunit/metrics/fluentapi/
metric_predicate_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
84
85
86
87
88
89
90
91
use crate::checkable::execute_logged_check;
use crate::{
    checkable::{CheckResult, Checkable},
    common::{CheckOptions, Filter, gather_empty_test_violations},
    metrics::{MetricMeasurement, MetricSubject, gather_metric_predicate_violations},
    violation::Violation,
};

use super::{
    DistanceMetricSelection, LcomMetricSelection, MetricSelection, logging::log_measurements,
};

/// Executable arbitrary predicate over one selected built-in metric.
#[derive(Debug, Clone)]
#[must_use = "an architecture rule has no effect until it is checked"]
pub struct MetricPredicateCondition<Selection, Predicate> {
    selection: Selection,
    predicate: Predicate,
}

impl<Selection, Predicate> MetricPredicateCondition<Selection, Predicate> {
    pub(super) const fn new(selection: Selection, predicate: Predicate) -> Self {
        Self {
            selection,
            predicate,
        }
    }

    /// Returns the underlying metric selection.
    #[must_use]
    pub const fn selection(&self) -> &Selection {
        &self.selection
    }
}

macro_rules! impl_predicate_checkable {
    ($selection:ty) => {
        impl<Predicate> Checkable for MetricPredicateCondition<$selection, Predicate>
        where
            Predicate: Fn(f64, &MetricSubject) -> bool,
        {
            fn check_with(&self, options: &CheckOptions) -> CheckResult {
                execute_logged_check("metrics.predicate", options, |logger| {
                    self.selection.validate_configuration()?;
                    logger.log_progress("calculating metric values")?;
                    let measurements = self.selection.measure_with(options)?;
                    logger.log_progress(format!("measurements={}", measurements.len()))?;
                    log_measurements(logger, &measurements, None)?;
                    finish_predicate_check(
                        measurements,
                        self.selection.filters(),
                        self.selection.subject_label(),
                        &self.predicate,
                        options,
                    )
                })
            }
        }
    };
}

impl_predicate_checkable!(MetricSelection);
impl_predicate_checkable!(LcomMetricSelection);
impl_predicate_checkable!(DistanceMetricSelection);

fn finish_predicate_check<Predicate>(
    measurements: Vec<MetricMeasurement>,
    filters: &[Filter],
    subject_label: &str,
    predicate: &Predicate,
    options: &CheckOptions,
) -> CheckResult
where
    Predicate: Fn(f64, &MetricSubject) -> bool,
{
    let empty = gather_empty_test_violations(
        &measurements,
        subject_label,
        filters,
        false,
        options.allows_empty_tests(),
    );
    if let Some(violation) = empty.into_iter().next() {
        return Ok(vec![Violation::from(violation)]);
    }

    Ok(gather_metric_predicate_violations(&measurements, predicate)
        .into_iter()
        .map(Violation::from)
        .collect())
}