dkp_core/validate/
gates.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4pub enum Gate {
5 Gate4 = 4,
7 Gate7 = 7,
9 Gate8 = 8,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub enum GateStatus {
15 Pass,
16 Fail,
17 Skipped,
19 NotApplicable,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct GateResult {
25 pub gate: u8,
26 pub status: GateStatus,
27 pub checks: Vec<CheckResult>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub message: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct CheckResult {
34 pub description: String,
35 pub status: GateStatus,
36 #[serde(skip_serializing_if = "Option::is_none")]
37 pub detail: Option<String>,
38}
39
40impl CheckResult {
41 pub fn pass(description: impl Into<String>) -> Self {
42 Self {
43 description: description.into(),
44 status: GateStatus::Pass,
45 detail: None,
46 }
47 }
48
49 pub fn fail(description: impl Into<String>, detail: impl Into<String>) -> Self {
50 Self {
51 description: description.into(),
52 status: GateStatus::Fail,
53 detail: Some(detail.into()),
54 }
55 }
56
57 pub fn skip(description: impl Into<String>) -> Self {
58 Self {
59 description: description.into(),
60 status: GateStatus::Skipped,
61 detail: None,
62 }
63 }
64}
65
66#[derive(Debug, Serialize, Deserialize)]
67pub struct ValidationReport {
68 pub pack_name: String,
69 pub pack_version: String,
70 pub gates: Vec<GateResult>,
71 pub overall: GateStatus,
72 pub conformance: ConformanceLevel,
73 pub reviewed: bool,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77pub enum ConformanceLevel {
78 DkpConformant,
80 DkpReviewed,
82 NonConformant,
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn check_result_pass_sets_pass_status_and_no_detail() {
92 let c = CheckResult::pass("thing checked");
93 assert_eq!(c.status, GateStatus::Pass);
94 assert_eq!(c.description, "thing checked");
95 assert!(c.detail.is_none());
96 }
97
98 #[test]
99 fn check_result_fail_sets_fail_status_and_detail() {
100 let c = CheckResult::fail("thing checked", "why it failed");
101 assert_eq!(c.status, GateStatus::Fail);
102 assert_eq!(c.detail.as_deref(), Some("why it failed"));
103 }
104
105 #[test]
106 fn check_result_skip_sets_skipped_status_and_no_detail() {
107 let c = CheckResult::skip("thing checked");
108 assert_eq!(c.status, GateStatus::Skipped);
109 assert!(c.detail.is_none());
110 }
111
112 #[test]
113 fn gate_status_serde_round_trip() {
114 for status in [
115 GateStatus::Pass,
116 GateStatus::Fail,
117 GateStatus::Skipped,
118 GateStatus::NotApplicable,
119 ] {
120 let json = serde_json::to_string(&status).unwrap();
121 let back: GateStatus = serde_json::from_str(&json).unwrap();
122 assert_eq!(status, back);
123 }
124 }
125
126 #[test]
127 fn conformance_level_serde_round_trip() {
128 for level in [
129 ConformanceLevel::DkpConformant,
130 ConformanceLevel::DkpReviewed,
131 ConformanceLevel::NonConformant,
132 ] {
133 let json = serde_json::to_string(&level).unwrap();
134 let back: ConformanceLevel = serde_json::from_str(&json).unwrap();
135 assert_eq!(level, back);
136 }
137 }
138
139 #[test]
140 fn gate_result_serde_round_trip_omits_none_message() {
141 let result = GateResult {
142 gate: 4,
143 status: GateStatus::Pass,
144 checks: vec![CheckResult::pass("ok")],
145 message: None,
146 };
147 let json = serde_json::to_string(&result).unwrap();
148 assert!(!json.contains("\"message\""));
149 let back: GateResult = serde_json::from_str(&json).unwrap();
150 assert_eq!(back.gate, 4);
151 assert_eq!(back.status, GateStatus::Pass);
152 assert_eq!(back.checks.len(), 1);
153 }
154}