use serde::{Deserialize, Serialize};
use tatara_lisp::DeriveTataraDomain;
#[derive(DeriveTataraDomain, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
#[tatara(keyword = "defgate")]
pub struct GateSpec {
pub name: String,
pub on_event: String,
#[serde(default)]
pub filetype: String,
#[serde(default)]
pub command: String,
#[serde(default)]
pub source: String,
#[serde(default)]
pub severity: String,
pub action: String,
#[serde(default)]
pub auto_fix: String,
#[serde(default)]
pub message: String,
#[serde(default)]
pub timeout_ms: u64,
}
pub const KNOWN_ACTIONS: &[&str] = &["reject", "warn", "auto-fix"];
pub const KNOWN_SEVERITIES: &[&str] = &["hint", "info", "warn", "error"];
pub const KNOWN_SOURCES: &[&str] = &[
"lsp.diagnostics",
"formatter.drift",
"ts.query",
"git.status",
"secrets.scan",
"type.check",
];
#[must_use]
pub fn is_known_action(name: &str) -> bool {
KNOWN_ACTIONS.iter().any(|a| *a == name)
}
#[must_use]
pub fn is_known_severity(name: &str) -> bool {
KNOWN_SEVERITIES.iter().any(|s| *s == name)
}
#[must_use]
pub fn is_known_source(name: &str) -> bool {
KNOWN_SOURCES.iter().any(|s| *s == name)
}
impl GateSpec {
#[must_use]
pub fn mode(&self) -> GateMode {
match (self.command.is_empty(), self.source.is_empty()) {
(false, true) => GateMode::Command,
(true, false) => GateMode::Source,
(true, true) => GateMode::Invalid,
(false, false) => GateMode::Invalid,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GateMode {
Command,
Source,
Invalid,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mode_classifies_spec_shape() {
let cmd = GateSpec {
name: "x".into(),
on_event: "BufWritePre".into(),
command: "rustfmt --check $FILE".into(),
action: "reject".into(),
..Default::default()
};
assert_eq!(cmd.mode(), GateMode::Command);
let src = GateSpec {
name: "y".into(),
on_event: "BufWritePost".into(),
source: "lsp.diagnostics".into(),
action: "warn".into(),
..Default::default()
};
assert_eq!(src.mode(), GateMode::Source);
let bad = GateSpec {
name: "z".into(),
on_event: "BufWritePre".into(),
action: "reject".into(),
..Default::default()
};
assert_eq!(bad.mode(), GateMode::Invalid);
let both = GateSpec {
name: "w".into(),
on_event: "BufWritePre".into(),
command: "x".into(),
source: "y".into(),
action: "reject".into(),
..Default::default()
};
assert_eq!(both.mode(), GateMode::Invalid);
}
#[test]
fn known_action_classifier_is_strict() {
assert!(is_known_action("reject"));
assert!(is_known_action("warn"));
assert!(is_known_action("auto-fix"));
assert!(!is_known_action("AutoFix"));
assert!(!is_known_action("log"));
}
}
impl Default for GateSpec {
fn default() -> Self {
Self {
name: String::new(),
on_event: String::new(),
filetype: String::new(),
command: String::new(),
source: String::new(),
severity: String::new(),
action: String::new(),
auto_fix: String::new(),
message: String::new(),
timeout_ms: 0,
}
}
}