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}