Skip to main content

bylaw_core/
rule.rs

1use crate::{
2    ArchitectureGraph, ComponentId, ConditionEvent, DescribedSelector, EvaluationReport,
3    RuleResult, Severity, Violation,
4};
5use serde::{Deserialize, Serialize};
6use std::fmt;
7use std::sync::Arc;
8
9pub trait Condition: Send + Sync {
10    fn description(&self) -> &str;
11    fn evaluate(&self, graph: &ArchitectureGraph, selected: &[ComponentId]) -> Vec<ConditionEvent>;
12}
13
14#[derive(Clone)]
15pub struct DescribedCondition {
16    inner: Arc<dyn Condition>,
17}
18
19impl fmt::Debug for DescribedCondition {
20    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
21        formatter
22            .debug_struct("DescribedCondition")
23            .field("description", &self.description())
24            .finish_non_exhaustive()
25    }
26}
27
28impl DescribedCondition {
29    pub fn new<F>(description: impl Into<String>, evaluator: F) -> Self
30    where
31        F: Fn(&ArchitectureGraph, &[ComponentId]) -> Vec<ConditionEvent> + Send + Sync + 'static,
32    {
33        Self {
34            inner: Arc::new(FunctionCondition {
35                description: description.into(),
36                evaluator,
37            }),
38        }
39    }
40
41    pub fn description(&self) -> &str {
42        self.inner.description()
43    }
44
45    pub fn evaluate(
46        &self,
47        graph: &ArchitectureGraph,
48        selected: &[ComponentId],
49    ) -> Vec<ConditionEvent> {
50        self.inner.evaluate(graph, selected)
51    }
52}
53
54impl Condition for DescribedCondition {
55    fn description(&self) -> &str {
56        self.description()
57    }
58
59    fn evaluate(&self, graph: &ArchitectureGraph, selected: &[ComponentId]) -> Vec<ConditionEvent> {
60        self.evaluate(graph, selected)
61    }
62}
63
64struct FunctionCondition<F> {
65    description: String,
66    evaluator: F,
67}
68
69impl<F> Condition for FunctionCondition<F>
70where
71    F: Fn(&ArchitectureGraph, &[ComponentId]) -> Vec<ConditionEvent> + Send + Sync,
72{
73    fn description(&self) -> &str {
74        &self.description
75    }
76
77    fn evaluate(&self, graph: &ArchitectureGraph, selected: &[ComponentId]) -> Vec<ConditionEvent> {
78        (self.evaluator)(graph, selected)
79    }
80}
81
82#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
83pub struct RuleMetadata {
84    pub id: String,
85    pub description: Option<String>,
86    pub because: Option<String>,
87    pub severity: Severity,
88}
89
90impl RuleMetadata {
91    pub fn new(id: impl Into<String>) -> Self {
92        let id = id.into();
93        assert!(!id.trim().is_empty(), "rule IDs cannot be empty");
94        Self {
95            id,
96            description: None,
97            because: None,
98            severity: Severity::Error,
99        }
100    }
101
102    pub fn described_as(mut self, description: impl Into<String>) -> Self {
103        self.description = Some(description.into());
104        self
105    }
106
107    pub fn because(mut self, rationale: impl Into<String>) -> Self {
108        self.because = Some(rationale.into());
109        self
110    }
111
112    pub fn with_severity(mut self, severity: Severity) -> Self {
113        self.severity = severity;
114        self
115    }
116}
117
118pub trait ArchitectureRule: Send + Sync {
119    fn metadata(&self) -> &RuleMetadata;
120    fn evaluate(&self, graph: &ArchitectureGraph) -> RuleResult;
121}
122
123#[derive(Clone, Debug)]
124pub struct Rule {
125    metadata: RuleMetadata,
126    selector: DescribedSelector,
127    condition: DescribedCondition,
128}
129
130impl Rule {
131    pub fn new(
132        metadata: RuleMetadata,
133        selector: DescribedSelector,
134        condition: DescribedCondition,
135    ) -> Self {
136        Self {
137            metadata,
138            selector,
139            condition,
140        }
141    }
142}
143
144impl ArchitectureRule for Rule {
145    fn metadata(&self) -> &RuleMetadata {
146        &self.metadata
147    }
148
149    fn evaluate(&self, graph: &ArchitectureGraph) -> RuleResult {
150        let selected = graph
151            .components()
152            .filter(|component| {
153                self.selector
154                    .matches(crate::Candidate::new(graph, component))
155            })
156            .map(crate::Component::id)
157            .collect::<Vec<_>>();
158
159        let description = self.metadata.description.clone().unwrap_or_else(|| {
160            format!(
161                "{} should {}",
162                self.selector.description(),
163                self.condition.description()
164            )
165        });
166        let mut violations = self
167            .condition
168            .evaluate(graph, &selected)
169            .into_iter()
170            .map(|event| Violation {
171                rule_id: self.metadata.id.clone(),
172                severity: self.metadata.severity,
173                message: event.message,
174                origin: event.origin,
175                target: event.target,
176                evidence: event.evidence,
177                cycle: event.cycle,
178                help: event.help,
179            })
180            .collect::<Vec<_>>();
181        violations.sort_by(|left, right| {
182            (&left.message, &left.origin, &left.target).cmp(&(
183                &right.message,
184                &right.origin,
185                &right.target,
186            ))
187        });
188
189        RuleResult {
190            rule_id: self.metadata.id.clone(),
191            description,
192            because: self.metadata.because.clone(),
193            severity: self.metadata.severity,
194            violations,
195        }
196    }
197}
198
199#[derive(Default)]
200pub struct RuleSet {
201    rules: Vec<Arc<dyn ArchitectureRule>>,
202}
203
204impl fmt::Debug for RuleSet {
205    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
206        formatter
207            .debug_struct("RuleSet")
208            .field("rule_count", &self.rules.len())
209            .finish()
210    }
211}
212
213impl RuleSet {
214    pub fn new() -> Self {
215        Self::default()
216    }
217
218    pub fn with_rule<R>(mut self, rule: R) -> Self
219    where
220        R: ArchitectureRule + 'static,
221    {
222        self.rules.push(Arc::new(rule));
223        self
224    }
225
226    pub fn push<R>(&mut self, rule: R)
227    where
228        R: ArchitectureRule + 'static,
229    {
230        self.rules.push(Arc::new(rule));
231    }
232
233    pub fn push_shared(&mut self, rule: Arc<dyn ArchitectureRule>) {
234        self.rules.push(rule);
235    }
236
237    pub fn evaluate(&self, graph: &ArchitectureGraph) -> EvaluationReport {
238        let mut report = EvaluationReport {
239            analysis_diagnostics: graph.diagnostics().to_vec(),
240            rule_results: self.rules.iter().map(|rule| rule.evaluate(graph)).collect(),
241        };
242        report.sort_deterministically();
243        report
244    }
245}