1use serde::{Deserialize, Deserializer};
4use std::fmt;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
8pub enum ReviewStatus {
9 #[default]
11 Pending,
12 InReview,
14 Reviewed,
16 Failed,
18 Cancelled,
20 Skipped,
22 Unknown,
24}
25
26impl fmt::Display for ReviewStatus {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28 match self {
29 Self::Pending => write!(f, "Pending"),
30 Self::InReview => write!(f, "In Review"),
31 Self::Reviewed => write!(f, "Reviewed"),
32 Self::Failed => write!(f, "Failed"),
33 Self::Cancelled => write!(f, "Cancelled"),
34 Self::Skipped => write!(f, "Skipped"),
35 Self::Unknown => write!(f, "Unknown"),
36 }
37 }
38}
39
40impl<'de> Deserialize<'de> for ReviewStatus {
41 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
42 where
43 D: Deserializer<'de>,
44 {
45 let s = Option::<String>::deserialize(deserializer)?;
46 Ok(match s.as_deref().map(str::to_lowercase).as_deref() {
47 Some("in review") => Self::InReview,
48 Some("reviewed") => Self::Reviewed,
49 Some("failed") => Self::Failed,
50 Some("cancelled") => Self::Cancelled,
51 Some("skipped") => Self::Skipped,
52 Some("pending") | None => Self::Pending,
53 Some(other) => {
54 tracing::warn!(status = other, "unknown review status, using Unknown");
55 Self::Unknown
56 }
57 })
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Deserialize)]
63pub struct Review {
64 #[serde(default)]
66 pub id: i64,
67 #[serde(default)]
69 pub patch_id: i64,
70 #[serde(default)]
72 pub status: ReviewStatus,
73 #[serde(default)]
75 pub model: Option<String>,
76 #[serde(default)]
78 pub provider: Option<String>,
79 #[serde(default)]
81 pub inline_review: Option<String>,
82 #[serde(default)]
84 pub summary: Option<String>,
85 #[serde(default)]
87 pub created_at: Option<i64>,
88}
89
90#[cfg(test)]
91#[allow(clippy::expect_used)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn review_status_default() {
97 assert_eq!(ReviewStatus::default(), ReviewStatus::Pending);
98 }
99
100 #[test]
101 fn review_status_display() {
102 assert_eq!(ReviewStatus::InReview.to_string(), "In Review");
103 assert_eq!(ReviewStatus::Cancelled.to_string(), "Cancelled");
104 }
105
106 #[test]
107 fn deserialize_review() {
108 let json = r#"{
109 "id": 1,
110 "patch_id": 42,
111 "status": "Reviewed",
112 "model": "gemini-3.1-pro-preview",
113 "provider": "gemini",
114 "summary": "Looks good"
115 }"#;
116 let review: Review = serde_json::from_str(json).expect("deserialize review");
117 assert_eq!(review.status, ReviewStatus::Reviewed);
118 assert_eq!(review.summary.as_deref(), Some("Looks good"));
119 }
120
121 #[test]
122 fn deserialize_review_unknown_status() {
123 let json = r#"{"id": 1, "patch_id": 1, "status": "FutureStatus"}"#;
124 let review: Review = serde_json::from_str(json).expect("deserialize unknown review");
125 assert_eq!(review.status, ReviewStatus::Unknown);
126 }
127}