1use serde::{Deserialize, Serialize};
2
3use crate::common::DimensionCategory;
4use crate::version::SchemaVersion;
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct GatesModel {
15 pub schema_version: SchemaVersion,
16 #[serde(default)]
17 pub gates: Vec<Gate>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct Gate {
23 pub id: String,
25 pub label: String,
27 pub command: String,
29 #[serde(default)]
31 pub category: DimensionCategory,
32 #[serde(default)]
34 pub timeout_secs: Option<u64>,
35 #[serde(default)]
37 pub allow_skip: bool,
38}
39
40impl GatesModel {
41 pub fn default_model() -> Self {
43 Self {
44 schema_version: SchemaVersion::CURRENT,
45 gates: vec![
46 Gate {
47 id: "fmt".to_string(),
48 label: "Format Check".to_string(),
49 command: "cargo fmt -- --check".to_string(),
50 category: DimensionCategory::Required,
51 timeout_secs: Some(60),
52 allow_skip: false,
53 },
54 Gate {
55 id: "clippy".to_string(),
56 label: "Clippy Lints".to_string(),
57 command: "cargo clippy -- -D warnings".to_string(),
58 category: DimensionCategory::Required,
59 timeout_secs: Some(120),
60 allow_skip: false,
61 },
62 Gate {
63 id: "unit-tests".to_string(),
64 label: "Unit Tests".to_string(),
65 command: "cargo test".to_string(),
66 category: DimensionCategory::Required,
67 timeout_secs: Some(300),
68 allow_skip: false,
69 },
70 Gate {
71 id: "doc-tests".to_string(),
72 label: "Documentation Tests".to_string(),
73 command: "cargo test --doc".to_string(),
74 category: DimensionCategory::Scored,
75 timeout_secs: Some(120),
76 allow_skip: true,
77 },
78 ],
79 }
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn test_gates_model_toml_roundtrip() {
89 let model = GatesModel::default_model();
90 let toml_str = toml::to_string_pretty(&model).unwrap();
91 let parsed: GatesModel = toml::from_str(&toml_str).unwrap();
92 assert_eq!(parsed.gates.len(), 4);
93 assert_eq!(parsed.gates[0].id, "fmt");
94 assert!(!parsed.gates[0].allow_skip);
95 }
96
97 #[test]
98 fn test_required_gates_cannot_skip() {
99 let model = GatesModel::default_model();
100 for gate in &model.gates {
101 if gate.category == DimensionCategory::Required {
102 assert!(!gate.allow_skip, "Required gate {} must not allow skip", gate.id);
103 }
104 }
105 }
106}