use std::collections::BTreeMap;
use serde::Deserialize;
use serde::de::IgnoredAny;
use thiserror::Error;
use super::module_boundary::{
ModuleBoundaryConfig, ModuleBoundaryConfigError, ModuleBoundaryTable,
};
#[derive(Debug)]
pub enum GateRuleOverride {
Disabled,
Severity(crate::gate::GateSeverity),
}
pub(super) enum RuleSetting<'a> {
Toggle(bool),
Named(&'a str),
}
#[derive(Debug, Error)]
#[error("invalid gate severity '{0}': expected \"deny\", \"warn\", or \"info\"")]
pub(super) struct InvalidGateSeverity(Box<str>);
pub(super) fn rule_override(
setting: RuleSetting<'_>,
) -> Result<Option<GateRuleOverride>, InvalidGateSeverity> {
match setting {
RuleSetting::Toggle(false) => Ok(Some(GateRuleOverride::Disabled)),
RuleSetting::Toggle(true) => Ok(None),
RuleSetting::Named(name) => parse_gate_severity(name)
.map(|severity| Some(GateRuleOverride::Severity(severity)))
.ok_or_else(|| InvalidGateSeverity(Box::from(name))),
}
}
#[derive(Debug)]
pub struct GateConfig {
pub enabled: bool,
pub overrides: BTreeMap<Box<str>, GateRuleOverride>,
pub module_boundary: ModuleBoundaryConfig,
}
impl Default for GateConfig {
fn default() -> Self {
Self {
enabled: true,
overrides: BTreeMap::new(),
module_boundary: ModuleBoundaryConfig::default(),
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum GateTomlValue {
Bool(bool),
String(String),
ModuleBoundary(ModuleBoundaryTable),
Other(IgnoredAny),
}
#[derive(Debug, Error)]
enum GateConfigError {
#[error("unknown gate rule '{0}'")]
UnknownRule(Box<str>),
#[error("'enabled' must be a boolean")]
EnabledNotBoolean,
#[error("'module-boundary' must be a table")]
ModuleBoundaryNotTable,
#[error("gate rule '{0}' must be a boolean or a severity string")]
RuleNotToggleOrSeverity(Box<str>),
#[error(transparent)]
ModuleBoundary(#[from] ModuleBoundaryConfigError),
#[error(transparent)]
Severity(#[from] InvalidGateSeverity),
}
impl<'de> Deserialize<'de> for GateConfig {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let raw: BTreeMap<Box<str>, GateTomlValue> = BTreeMap::deserialize(deserializer)?;
let mut config = GateConfig::default();
for (key, value) in raw {
apply_entry(&mut config, key, value).map_err(D::Error::custom)?;
}
Ok(config)
}
}
fn apply_entry(
config: &mut GateConfig,
key: Box<str>,
value: GateTomlValue,
) -> Result<(), GateConfigError> {
match (&*key, value) {
("module-boundary", GateTomlValue::ModuleBoundary(table)) => {
config.module_boundary = ModuleBoundaryConfig::from_raw(table)?;
}
("module-boundary", _) => return Err(GateConfigError::ModuleBoundaryNotTable),
("enabled", GateTomlValue::Bool(toggle)) => config.enabled = toggle,
("enabled", _) => return Err(GateConfigError::EnabledNotBoolean),
(name, _) if !crate::gate::is_gate_rule(name) => {
return Err(GateConfigError::UnknownRule(key));
}
(_, GateTomlValue::Bool(toggle)) => {
insert_override(config, key, RuleSetting::Toggle(toggle))?;
}
(_, GateTomlValue::String(severity)) => {
insert_override(config, key, RuleSetting::Named(&severity))?;
}
(_, _) => {
return Err(GateConfigError::RuleNotToggleOrSeverity(key));
}
}
Ok(())
}
fn insert_override(
config: &mut GateConfig,
rule: Box<str>,
setting: RuleSetting<'_>,
) -> Result<(), InvalidGateSeverity> {
if let Some(resolved) = rule_override(setting)? {
config.overrides.insert(rule, resolved);
}
Ok(())
}
fn parse_gate_severity(s: &str) -> Option<crate::gate::GateSeverity> {
use crate::gate::GateSeverity;
match s {
"deny" => Some(GateSeverity::Deny),
"warn" => Some(GateSeverity::Warn),
"info" => Some(GateSeverity::Info),
_ => None,
}
}