use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Allow,
Warn,
Deny,
}
impl fmt::Display for Severity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Severity::Allow => "allow",
Severity::Warn => "warn",
Severity::Deny => "deny",
})
}
}
pub type RuleOptions = BTreeMap<String, toml::Value>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum RuleConfig {
Bare(Severity),
WithOptions(Severity, RuleOptions),
}
impl RuleConfig {
pub fn severity(&self) -> Severity {
match self {
RuleConfig::Bare(s) | RuleConfig::WithOptions(s, _) => *s,
}
}
pub fn options(&self) -> Option<&RuleOptions> {
match self {
RuleConfig::Bare(_) => None,
RuleConfig::WithOptions(_, opts) => Some(opts),
}
}
}
impl From<Severity> for RuleConfig {
fn from(s: Severity) -> Self {
RuleConfig::Bare(s)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Deserialize, Serialize)]
struct Wrap {
rule: RuleConfig,
}
fn parse(toml: &str) -> RuleConfig {
toml::from_str::<Wrap>(toml).expect("parse").rule
}
#[test]
fn bare_string_warn() {
let r = parse(r#"rule = "warn""#);
assert_eq!(r.severity(), Severity::Warn);
assert!(r.options().is_none());
}
#[test]
fn bare_string_allow() {
let r = parse(r#"rule = "allow""#);
assert_eq!(r.severity(), Severity::Allow);
assert!(r.options().is_none());
}
#[test]
fn bare_string_deny() {
let r = parse(r#"rule = "deny""#);
assert_eq!(r.severity(), Severity::Deny);
assert!(r.options().is_none());
}
#[test]
fn array_form_with_options() {
let r = parse(r#"rule = ["warn", { max = 100 }]"#);
assert_eq!(r.severity(), Severity::Warn);
let opts = r.options().expect("options present");
assert_eq!(opts.get("max"), Some(&toml::Value::Integer(100)));
}
#[test]
fn array_form_multiple_options() {
let r = parse(r#"rule = ["deny", { max = 80, indent = "tabs" }]"#);
assert_eq!(r.severity(), Severity::Deny);
let opts = r.options().unwrap();
assert_eq!(opts.get("max"), Some(&toml::Value::Integer(80)));
assert_eq!(
opts.get("indent"),
Some(&toml::Value::String("tabs".into()))
);
}
#[test]
fn array_form_empty_options() {
let r = parse(r#"rule = ["warn", {}]"#);
assert_eq!(r.severity(), Severity::Warn);
assert!(r.options().unwrap().is_empty());
}
#[test]
fn rejects_invalid_severity_string() {
assert!(toml::from_str::<Wrap>(r#"rule = "error""#).is_err());
}
#[test]
fn rejects_invalid_array_severity() {
assert!(toml::from_str::<Wrap>(r#"rule = ["error", {}]"#).is_err());
}
#[test]
fn round_trip_bare() {
let r = parse(r#"rule = "warn""#);
let s = toml::to_string(&Wrap { rule: r.clone() }).unwrap();
let back = toml::from_str::<Wrap>(&s).unwrap().rule;
assert_eq!(back, r);
}
#[test]
fn round_trip_with_options() {
let r = parse(r#"rule = ["warn", { max = 100, indent = "tabs" }]"#);
let s = toml::to_string(&Wrap { rule: r.clone() }).unwrap();
let back = toml::from_str::<Wrap>(&s).unwrap().rule;
assert_eq!(back, r);
assert_eq!(back.severity(), Severity::Warn);
let opts = back.options().expect("options preserved");
assert_eq!(opts.get("max"), Some(&toml::Value::Integer(100)));
assert_eq!(
opts.get("indent"),
Some(&toml::Value::String("tabs".into()))
);
}
#[test]
fn severity_display() {
assert_eq!(Severity::Allow.to_string(), "allow");
assert_eq!(Severity::Warn.to_string(), "warn");
assert_eq!(Severity::Deny.to_string(), "deny");
}
#[test]
fn severity_into_rule_config() {
let r: RuleConfig = Severity::Warn.into();
assert_eq!(r.severity(), Severity::Warn);
assert!(r.options().is_none());
}
}