use std::collections::BTreeMap;
use serde::Deserialize;
use serde::de::IgnoredAny;
use thiserror::Error;
use super::gate::{GateRuleOverride, InvalidGateSeverity, RuleSetting, rule_override};
#[derive(Debug, Deserialize)]
#[serde(untagged)]
pub(super) enum ModuleBoundaryValue {
Bool(bool),
Integer(i64),
String(String),
Other(IgnoredAny),
}
pub(super) type ModuleBoundaryTable = BTreeMap<Box<str>, ModuleBoundaryValue>;
#[derive(Debug, Error)]
pub(super) enum ModuleBoundaryConfigError {
#[error("unknown module-boundary field '{0}'")]
UnknownField(Box<str>),
#[error("module-boundary field '{field}' must be a boolean")]
NotBoolean {
field: Box<str>,
},
#[error("module-boundary field '{field}' must be an integer")]
NotInteger {
field: Box<str>,
},
#[error("module-boundary field '{field}' must fit in u32, got {value}")]
CountOutOfRange {
field: Box<str>,
value: i64,
},
#[error("module-boundary field '{field}' must be 0..=100, got {value}")]
PercentOutOfRange {
field: Box<str>,
value: i64,
},
#[error("module-boundary rule '{rule}' must be a boolean or a severity string")]
RuleNotToggleOrSeverity {
rule: Box<str>,
},
#[error(transparent)]
Severity(#[from] InvalidGateSeverity),
}
#[derive(Debug)]
pub struct ModuleBoundaryConfig {
enabled: bool,
rules: BTreeMap<Box<str>, GateRuleOverride>,
max_nodes: u32,
max_references: u32,
max_edges: u32,
max_selected_edges: u32,
max_scc_members: u32,
misplaced_symbol_min_foreign_edges: u32,
misplaced_symbol_min_affinity_percent: u8,
low_module_cohesion_min_outgoing_edges: u32,
low_module_cohesion_min_percent: u8,
}
impl Default for ModuleBoundaryConfig {
fn default() -> Self {
Self {
enabled: true,
rules: BTreeMap::new(),
max_nodes: 250_000,
max_references: 1_000_000,
max_edges: 1_000_000,
max_selected_edges: 1_000_000,
max_scc_members: 8,
misplaced_symbol_min_foreign_edges: 3,
misplaced_symbol_min_affinity_percent: 60,
low_module_cohesion_min_outgoing_edges: 4,
low_module_cohesion_min_percent: 50,
}
}
}
impl ModuleBoundaryConfig {
pub fn enabled(&self) -> bool {
self.enabled
}
pub fn max_nodes(&self) -> u32 {
self.max_nodes
}
pub fn max_references(&self) -> u32 {
self.max_references
}
pub fn max_edges(&self) -> u32 {
self.max_edges
}
pub fn max_selected_edges(&self) -> u32 {
self.max_selected_edges
}
pub fn max_scc_members(&self) -> u32 {
self.max_scc_members
}
pub fn misplaced_symbol_min_foreign_edges(&self) -> u32 {
self.misplaced_symbol_min_foreign_edges
}
pub fn misplaced_symbol_min_affinity_percent(&self) -> u8 {
self.misplaced_symbol_min_affinity_percent
}
pub fn low_module_cohesion_min_outgoing_edges(&self) -> u32 {
self.low_module_cohesion_min_outgoing_edges
}
pub fn low_module_cohesion_min_percent(&self) -> u8 {
self.low_module_cohesion_min_percent
}
pub(crate) fn rule_overrides(&self) -> &BTreeMap<Box<str>, GateRuleOverride> {
&self.rules
}
pub(super) fn from_raw(raw: ModuleBoundaryTable) -> Result<Self, ModuleBoundaryConfigError> {
let mut config = Self::default();
for (key, value) in raw {
config.apply(key, value)?;
}
Ok(config)
}
fn apply(
&mut self,
key: Box<str>,
value: ModuleBoundaryValue,
) -> Result<(), ModuleBoundaryConfigError> {
match &*key {
"enabled" => self.enabled = expect_bool(&key, value)?,
"max-nodes" => self.max_nodes = expect_count(&key, value)?,
"max-references" => self.max_references = expect_count(&key, value)?,
"max-edges" => self.max_edges = expect_count(&key, value)?,
"max-selected-edges" => self.max_selected_edges = expect_count(&key, value)?,
"max-scc-members" => self.max_scc_members = expect_count(&key, value)?,
"misplaced-symbol-min-foreign-edges" => {
self.misplaced_symbol_min_foreign_edges = expect_count(&key, value)?;
}
"misplaced-symbol-min-affinity-percent" => {
self.misplaced_symbol_min_affinity_percent = expect_percent(&key, value)?;
}
"low-module-cohesion-min-outgoing-edges" => {
self.low_module_cohesion_min_outgoing_edges = expect_count(&key, value)?;
}
"low-module-cohesion-min-percent" => {
self.low_module_cohesion_min_percent = expect_percent(&key, value)?;
}
_ => return self.apply_rule(key, value),
}
Ok(())
}
fn apply_rule(
&mut self,
rule: Box<str>,
value: ModuleBoundaryValue,
) -> Result<(), ModuleBoundaryConfigError> {
if !crate::gate::is_module_boundary_rule(&rule) {
return Err(ModuleBoundaryConfigError::UnknownField(rule));
}
if let Some(setting) = rule_setting(&rule, value)? {
self.rules.insert(rule, setting);
}
Ok(())
}
}
fn rule_setting(
rule: &str,
value: ModuleBoundaryValue,
) -> Result<Option<GateRuleOverride>, ModuleBoundaryConfigError> {
match value {
ModuleBoundaryValue::Bool(toggle) => Ok(rule_override(RuleSetting::Toggle(toggle))?),
ModuleBoundaryValue::String(name) => Ok(rule_override(RuleSetting::Named(&name))?),
_ => Err(ModuleBoundaryConfigError::RuleNotToggleOrSeverity {
rule: Box::from(rule),
}),
}
}
fn expect_bool(field: &str, value: ModuleBoundaryValue) -> Result<bool, ModuleBoundaryConfigError> {
match value {
ModuleBoundaryValue::Bool(toggle) => Ok(toggle),
_ => Err(ModuleBoundaryConfigError::NotBoolean {
field: Box::from(field),
}),
}
}
fn expect_integer(
field: &str,
value: ModuleBoundaryValue,
) -> Result<i64, ModuleBoundaryConfigError> {
match value {
ModuleBoundaryValue::Integer(number) => Ok(number),
_ => Err(ModuleBoundaryConfigError::NotInteger {
field: Box::from(field),
}),
}
}
fn expect_count(field: &str, value: ModuleBoundaryValue) -> Result<u32, ModuleBoundaryConfigError> {
let raw = expect_integer(field, value)?;
u32::try_from(raw).map_err(|_| ModuleBoundaryConfigError::CountOutOfRange {
field: Box::from(field),
value: raw,
})
}
fn expect_percent(
field: &str,
value: ModuleBoundaryValue,
) -> Result<u8, ModuleBoundaryConfigError> {
let raw = expect_integer(field, value)?;
match u8::try_from(raw) {
Ok(percent) if percent <= 100 => Ok(percent),
_ => Err(ModuleBoundaryConfigError::PercentOutOfRange {
field: Box::from(field),
value: raw,
}),
}
}
impl<'de> Deserialize<'de> for ModuleBoundaryConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let raw = ModuleBoundaryTable::deserialize(deserializer)?;
Self::from_raw(raw).map_err(D::Error::custom)
}
}