#![cfg_attr(test, allow(dead_code))]
use crate::adapters::analyzers::architecture::forbidden_rule::CompiledForbiddenRule;
use crate::adapters::analyzers::architecture::layer_rule::{LayerDefinitions, UnmatchedBehavior};
use crate::adapters::analyzers::architecture::trait_contract_rule::CompiledTraitContract;
use crate::config::ArchitectureConfig;
use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
use std::collections::HashMap;
type ExternalCrates = (HashMap<String, String>, Vec<(GlobMatcher, String)>);
#[derive(Debug)]
pub struct CompiledArchitecture {
pub layers: LayerDefinitions,
pub reexport_points: GlobSet,
pub unmatched_behavior: UnmatchedBehavior,
pub external_exact: HashMap<String, String>,
pub external_glob: Vec<(GlobMatcher, String)>,
pub forbidden: Vec<CompiledForbiddenRule>,
pub trait_contracts: Vec<CompiledTraitContract>,
}
pub fn compile_architecture(cfg: &ArchitectureConfig) -> Result<CompiledArchitecture, String> {
let layers = compile_layers(cfg)?;
let reexport_points = compile_reexport_points(cfg)?;
let unmatched_behavior = parse_unmatched_behavior(&cfg.layers.unmatched_behavior)?;
let (external_exact, external_glob) = compile_external_crates(&cfg.external_crates)?;
let forbidden = compile_forbidden_rules(&cfg.forbidden_rules)?;
let trait_contracts = compile_trait_contracts(&cfg.trait_contracts)?;
Ok(CompiledArchitecture {
layers,
reexport_points,
unmatched_behavior,
external_exact,
external_glob,
forbidden,
trait_contracts,
})
}
fn compile_trait_contracts(
raw: &[crate::config::architecture::TraitContract],
) -> Result<Vec<CompiledTraitContract>, String> {
raw.iter()
.map(|tc| {
let scope = Glob::new(&tc.scope)
.map_err(|e| format!("trait_contract.scope \"{}\": {e}", tc.scope))?;
let mut b = GlobSetBuilder::new();
b.add(scope);
let scope = b
.build()
.map_err(|e| format!("trait_contract.scope: {e}"))?;
Ok(CompiledTraitContract {
name: tc.name.clone(),
scope,
receiver_may_be: tc.receiver_may_be.clone(),
required_param_type_contains: tc.required_param_type_contains.clone(),
forbidden_return_type_contains: tc
.forbidden_return_type_contains
.clone()
.unwrap_or_default(),
forbidden_error_variant_contains: tc
.forbidden_error_variant_contains
.clone()
.unwrap_or_default(),
error_types: tc.error_types.clone().unwrap_or_default(),
methods_must_be_async: tc.methods_must_be_async,
must_be_object_safe: tc.must_be_object_safe,
required_supertraits_contain: tc
.required_supertraits_contain
.clone()
.unwrap_or_default(),
})
})
.collect()
}
fn compile_layers(cfg: &ArchitectureConfig) -> Result<LayerDefinitions, String> {
let order = cfg.layers.order.clone();
let mut definitions = Vec::with_capacity(order.len());
for name in &order {
let Some(paths_cfg) = cfg.layers.definitions.get(name) else {
return Err(format!("layer \"{name}\" listed in order but has no paths"));
};
let gs = build_globset(&paths_cfg.paths)
.map_err(|e| format!("layer \"{name}\" glob error: {e}"))?;
definitions.push((name.clone(), gs));
}
Ok(LayerDefinitions::new(order, definitions))
}
fn compile_reexport_points(cfg: &ArchitectureConfig) -> Result<GlobSet, String> {
build_globset(&cfg.reexport_points.paths).map_err(|e| format!("reexport_points: {e}"))
}
fn parse_unmatched_behavior(raw: &str) -> Result<UnmatchedBehavior, String> {
match raw {
"composition_root" => Ok(UnmatchedBehavior::CompositionRoot),
"strict_error" => Ok(UnmatchedBehavior::StrictError),
other => Err(format!(
"unmatched_behavior must be \"composition_root\" or \"strict_error\", got {other:?}"
)),
}
}
fn is_exact_crate_key(key: &str) -> bool {
!key.is_empty() && key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
}
fn compile_external_crates(raw: &HashMap<String, String>) -> Result<ExternalCrates, String> {
let mut exact = HashMap::new();
let mut globs = Vec::new();
for (key, layer) in raw {
if is_exact_crate_key(key) {
exact.insert(key.clone(), layer.clone());
} else {
let matcher = Glob::new(key)
.map_err(|e| format!("external_crates \"{key}\" glob error: {e}"))?
.compile_matcher();
globs.push((matcher, layer.clone()));
}
}
Ok((exact, globs))
}
fn compile_forbidden_rules(
raw: &[crate::config::architecture::ForbiddenRule],
) -> Result<Vec<CompiledForbiddenRule>, String> {
let mut out = Vec::with_capacity(raw.len());
for rule in raw {
let from = Glob::new(&rule.from)
.map_err(|e| format!("forbidden.from \"{}\": {e}", rule.from))?
.compile_matcher();
let to = Glob::new(&rule.to)
.map_err(|e| format!("forbidden.to \"{}\": {e}", rule.to))?
.compile_matcher();
let except = build_globset(&rule.except).map_err(|e| format!("forbidden.except: {e}"))?;
out.push(CompiledForbiddenRule {
from,
to,
except,
reason: rule.reason.clone(),
});
}
Ok(out)
}
fn build_globset(patterns: &[String]) -> Result<GlobSet, String> {
let mut builder = GlobSetBuilder::new();
for pat in patterns {
let g = Glob::new(pat).map_err(|e| format!("glob \"{pat}\": {e}"))?;
builder.add(g);
}
builder.build().map_err(|e| format!("globset build: {e}"))
}