archunit/metrics/fluentapi/
metric_zone_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
use std::path::Path;

use crate::checkable::execute_logged_check;
use crate::{
    checkable::{CheckResult, Checkable},
    common::{CheckOptions, ProjectLocator, gather_empty_test_violations},
    metrics::{ArchitecturalZone, gather_metric_zone_violations},
    violation::Violation,
};

use super::{MetricsBuilder, logging::log_measurements};

/// Executable distance rule that rejects one architectural zone.
#[derive(Debug, Clone)]
#[must_use = "an architecture rule has no effect until it is checked"]
pub struct MetricZoneCondition {
    query: MetricsBuilder,
    zone: ArchitecturalZone,
}

impl MetricZoneCondition {
    pub(super) const fn new(query: MetricsBuilder, zone: ArchitecturalZone) -> Self {
        Self { query, zone }
    }

    /// Returns the zone rejected by this rule.
    #[must_use]
    pub const fn zone(&self) -> ArchitecturalZone {
        self.zone
    }

    /// Returns the explicit discovery path, or `None` for automatic discovery.
    #[must_use]
    pub fn project_path(&self) -> Option<&Path> {
        self.query.project_path()
    }

    /// Returns where project discovery starts.
    #[must_use]
    pub fn project_locator(&self) -> ProjectLocator {
        self.query
            .project_path()
            .map_or_else(ProjectLocator::auto_detect, ProjectLocator::from_path)
    }
}

impl Checkable for MetricZoneCondition {
    fn check_with(&self, options: &CheckOptions) -> CheckResult {
        execute_logged_check("metrics.zone", options, |logger| {
            logger.log_progress("calculating component distance values")?;
            let infos = self.query.distance_infos_with(options)?;
            logger.log_progress(format!("metric components={}", infos.len()))?;
            let empty = gather_empty_test_violations(
                &infos,
                "metric components",
                self.query.filters(),
                false,
                options.allows_empty_tests(),
            );
            if let Some(violation) = empty.into_iter().next() {
                return Ok(vec![Violation::from(violation)]);
            }

            let measurements = [
                crate::metrics::DistanceMetric::Abstractness,
                crate::metrics::DistanceMetric::Instability,
            ]
            .into_iter()
            .flat_map(|metric| metric.measurements(&infos))
            .collect::<Vec<_>>();
            log_measurements(logger, &measurements, None)?;
            Ok(gather_metric_zone_violations(&infos, self.zone)
                .into_iter()
                .map(Violation::from)
                .collect())
        })
    }
}