1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
5pub enum Verdict {
6 #[serde(rename = "merge")]
7 Merge,
8 #[serde(rename = "notMerge")]
9 NotMerge,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct EmbeddedReview {
16 pub reviewer: String,
17 pub difficulty_ranking: Vec<String>,
18 pub novelty_ranking: Vec<String>,
19 pub design_quality_ranking: Vec<String>,
20 pub verdict: Verdict,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25#[serde(rename_all = "camelCase")]
26pub struct MetaReview {
27 pub meta_reviewer: String,
28 pub target_reviewer: String,
29 pub approve: bool,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(rename_all = "camelCase")]
35pub struct SignedCommit {
36 pub id: String,
37 pub pr_id: u64,
38 pub author: String,
39 pub merge_epoch: u64,
40 #[serde(skip_serializing_if = "Option::is_none")]
41 pub pr_created_at: Option<u64>,
42 pub comparison_targets: Vec<String>,
43 pub reviews: Vec<EmbeddedReview>,
44 pub meta_reviews: Vec<MetaReview>,
45 pub founder_override: bool,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct CommitScore {
52 pub difficulty: u64,
53 pub novelty: u64,
54 pub design_quality: u64,
55}
56
57#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct CommitIndex {
61 pub commit_hash: String,
62 pub epoch: u64,
63 pub score: CommitScore,
64 pub contributor: String,
65 pub weight_delta: u64,
66 pub reviewers: Vec<String>,
67 pub meta_reviews: Vec<MetaReview>,
68 pub merge_votes: Vec<String>,
69 pub reject_votes: Vec<String>,
70 pub founder_override: bool,
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub warnings: Option<Vec<String>>,
73}
74
75#[derive(Debug, Clone, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct MergeReadiness {
79 pub ready: bool,
80 pub merge_weight: u64,
81 pub reject_weight: u64,
82 pub total_weight: u64,
83}
84
85#[derive(Debug, Clone, Deserialize)]
87pub struct SelectTargetsOutput {
88 pub targets: Vec<String>,
89}
90
91#[derive(Debug, Clone, Deserialize)]
93pub struct RankingOutput {
94 pub ranking: Vec<String>,
95}
96
97#[derive(Debug, Clone, Deserialize)]
99pub struct ValidateOutput {
100 pub valid: bool,
101 pub errors: Vec<String>,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(rename_all = "camelCase")]
107pub struct CollectedReviews {
108 pub reviews: Vec<EmbeddedReview>,
109 pub meta_reviews: Vec<MetaReview>,
110 pub warnings: Vec<String>,
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_verdict_serde_roundtrip() {
119 let merge = serde_json::to_string(&Verdict::Merge).unwrap();
120 assert_eq!(merge, "\"merge\"");
121 let not_merge = serde_json::to_string(&Verdict::NotMerge).unwrap();
122 assert_eq!(not_merge, "\"notMerge\"");
123
124 let parsed: Verdict = serde_json::from_str("\"merge\"").unwrap();
125 assert_eq!(parsed, Verdict::Merge);
126 let parsed: Verdict = serde_json::from_str("\"notMerge\"").unwrap();
127 assert_eq!(parsed, Verdict::NotMerge);
128 }
129
130 #[test]
131 fn test_commit_index_camel_case() {
132 let json = r#"{
133 "commitHash": "abc123",
134 "epoch": 1000,
135 "score": {"difficulty": 85, "novelty": 100, "designQuality": 85},
136 "contributor": "alice",
137 "weightDelta": 88,
138 "reviewers": ["bob"],
139 "metaReviews": [],
140 "mergeVotes": ["bob"],
141 "rejectVotes": [],
142 "founderOverride": false
143 }"#;
144 let idx: CommitIndex = serde_json::from_str(json).unwrap();
145 assert_eq!(idx.commit_hash, "abc123");
146 assert_eq!(idx.score.design_quality, 85);
147 assert_eq!(idx.weight_delta, 88);
148 assert!(!idx.founder_override);
149 assert!(idx.warnings.is_none());
150 }
151
152 #[test]
153 fn test_commit_index_with_warnings() {
154 let json = r#"{
155 "commitHash": "abc",
156 "epoch": 1000,
157 "score": {"difficulty": 0, "novelty": 0, "designQuality": 0},
158 "contributor": "alice",
159 "weightDelta": 0,
160 "reviewers": [],
161 "metaReviews": [],
162 "mergeVotes": [],
163 "rejectVotes": [],
164 "founderOverride": false,
165 "warnings": ["some warning"]
166 }"#;
167 let idx: CommitIndex = serde_json::from_str(json).unwrap();
168 assert_eq!(idx.warnings.as_ref().unwrap().len(), 1);
169 }
170
171 #[test]
172 fn test_commit_index_without_warnings_backward_compat() {
173 let json = r#"{
175 "commitHash": "abc",
176 "epoch": 1000,
177 "score": {"difficulty": 0, "novelty": 0, "designQuality": 0},
178 "contributor": "alice",
179 "weightDelta": 0,
180 "reviewers": [],
181 "metaReviews": [],
182 "mergeVotes": [],
183 "rejectVotes": [],
184 "founderOverride": false
185 }"#;
186 let idx: CommitIndex = serde_json::from_str(json).unwrap();
187 assert!(idx.warnings.is_none());
188 }
189
190 #[test]
191 fn test_embedded_review_serde() {
192 let review = EmbeddedReview {
193 reviewer: "alice".to_string(),
194 difficulty_ranking: vec!["abc".to_string(), "def".to_string()],
195 novelty_ranking: vec!["def".to_string(), "abc".to_string()],
196 design_quality_ranking: vec!["abc".to_string(), "def".to_string()],
197 verdict: Verdict::Merge,
198 };
199 let json = serde_json::to_string(&review).unwrap();
200 assert!(json.contains("difficultyRanking"));
201 assert!(json.contains("designQualityRanking"));
202 let parsed: EmbeddedReview = serde_json::from_str(&json).unwrap();
203 assert_eq!(parsed.reviewer, "alice");
204 }
205
206 #[test]
207 fn test_signed_commit_serde() {
208 let commit = SignedCommit {
209 id: "abc123".to_string(),
210 pr_id: 42,
211 author: "alice".to_string(),
212 merge_epoch: 1000,
213 pr_created_at: Some(900),
214 comparison_targets: vec!["target1".to_string()],
215 reviews: vec![],
216 meta_reviews: vec![],
217 founder_override: false,
218 };
219 let json = serde_json::to_string(&commit).unwrap();
220 assert!(json.contains("prId"));
221 assert!(json.contains("mergeEpoch"));
222 assert!(json.contains("prCreatedAt"));
223 assert!(json.contains("founderOverride"));
224 }
225
226 #[test]
227 fn test_signed_commit_without_pr_created_at() {
228 let json = r#"{
230 "id": "abc",
231 "prId": 1,
232 "author": "alice",
233 "mergeEpoch": 1000,
234 "comparisonTargets": [],
235 "reviews": [],
236 "metaReviews": [],
237 "founderOverride": false
238 }"#;
239 let commit: SignedCommit = serde_json::from_str(json).unwrap();
240 assert!(commit.pr_created_at.is_none());
241 }
242
243 #[test]
244 fn test_merge_readiness_serde() {
245 let json =
246 r#"{"ready": true, "mergeWeight": 1000, "rejectWeight": 0, "totalWeight": 1000}"#;
247 let readiness: MergeReadiness = serde_json::from_str(json).unwrap();
248 assert!(readiness.ready);
249 assert_eq!(readiness.merge_weight, 1000);
250 }
251}