1use serde::Deserialize;
4
5use crate::model::PrState;
6
7#[derive(Debug, Clone, Default, Deserialize)]
9pub struct Author {
10 #[serde(default)]
12 pub login: String,
13}
14
15#[derive(Debug, Clone, Deserialize)]
17pub struct PrSummary {
18 pub number: u64,
20 pub title: String,
22 #[serde(default)]
24 pub author: Author,
25 pub state: String,
27 #[serde(rename = "isDraft", default)]
29 pub is_draft: bool,
30 #[serde(rename = "headRefName", default)]
32 pub head_ref_name: String,
33 #[serde(rename = "createdAt", default)]
35 pub created_at: String,
36}
37
38impl PrSummary {
39 pub fn pr_state(&self) -> PrState {
41 pr_state(&self.state, self.is_draft)
42 }
43}
44
45#[derive(Debug, Clone, Deserialize)]
47pub struct PrView {
48 pub number: u64,
50 pub title: String,
52 pub state: String,
54 #[serde(rename = "isDraft", default)]
56 pub is_draft: bool,
57 #[serde(rename = "headRefName")]
59 pub head_ref_name: String,
60 #[serde(rename = "baseRefName")]
62 pub base_ref_name: String,
63 #[serde(default)]
65 pub url: String,
66}
67
68impl PrView {
69 pub fn pr_state(&self) -> PrState {
71 pr_state(&self.state, self.is_draft)
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
81pub struct OpenPr {
82 pub number: u64,
84 #[serde(default)]
86 pub url: String,
87 pub state: String,
89 #[serde(rename = "isDraft", default)]
91 pub is_draft: bool,
92}
93
94pub(crate) fn parse_default_branch(json: &str) -> Option<String> {
98 #[derive(Deserialize)]
99 struct Ref {
100 name: String,
101 }
102 #[derive(Deserialize)]
103 struct View {
104 #[serde(rename = "defaultBranchRef")]
105 default_branch_ref: Option<Ref>,
106 }
107 let view: View = serde_json::from_str(json).ok()?;
108 view.default_branch_ref.map(|r| r.name)
109}
110
111pub fn pr_state(state: &str, is_draft: bool) -> PrState {
113 if is_draft && state.eq_ignore_ascii_case("open") {
114 return PrState::Draft;
115 }
116 match state.to_ascii_lowercase().as_str() {
117 "closed" => PrState::Closed,
118 "merged" => PrState::Merged,
119 _ => PrState::Open,
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn parses_pr_list_json() {
129 let json = r#"[
130 {"number": 42, "title": "Add login", "author": {"login": "alice"},
131 "state": "OPEN", "isDraft": false, "headRefName": "feature/login",
132 "createdAt": "2024-01-15T10:30:00Z"},
133 {"number": 7, "title": "WIP", "author": {"login": "bob"},
134 "state": "OPEN", "isDraft": true, "headRefName": "wip"}
135 ]"#;
136 let prs: Vec<PrSummary> = serde_json::from_str(json).unwrap();
137 assert_eq!(prs.len(), 2);
138 assert_eq!(prs[0].number, 42);
139 assert_eq!(prs[0].author.login, "alice");
140 assert_eq!(prs[0].pr_state(), PrState::Open);
141 assert_eq!(prs[1].pr_state(), PrState::Draft); }
143
144 #[test]
145 fn parses_pr_view_json() {
146 let json = r#"{"number": 5, "title": "Fix", "state": "MERGED", "isDraft": false,
147 "headRefName": "fork-branch", "baseRefName": "main"}"#;
148 let view: PrView = serde_json::from_str(json).unwrap();
149 assert_eq!(view.number, 5);
150 assert_eq!(view.head_ref_name, "fork-branch");
151 assert_eq!(view.base_ref_name, "main");
152 assert_eq!(view.pr_state(), PrState::Merged);
153 }
154
155 #[test]
156 fn state_mapping() {
157 assert_eq!(pr_state("OPEN", false), PrState::Open);
158 assert_eq!(pr_state("OPEN", true), PrState::Draft);
159 assert_eq!(pr_state("CLOSED", false), PrState::Closed);
160 assert_eq!(pr_state("MERGED", false), PrState::Merged);
161 assert_eq!(pr_state("CLOSED", true), PrState::Closed); }
163
164 #[test]
165 fn parses_open_pr_list() {
166 let json = r#"[{"number": 77, "url": "https://github.com/o/r/pull/77",
167 "state": "OPEN", "isDraft": true}]"#;
168 let prs: Vec<OpenPr> = serde_json::from_str(json).unwrap();
169 assert_eq!(prs.len(), 1);
170 assert_eq!(prs[0].number, 77);
171 assert_eq!(prs[0].url, "https://github.com/o/r/pull/77");
172 assert!(prs[0].is_draft);
173 }
174
175 #[test]
176 fn parses_default_branch() {
177 assert_eq!(
178 parse_default_branch(r#"{"defaultBranchRef": {"name": "main"}}"#),
179 Some("main".to_string())
180 );
181 assert_eq!(parse_default_branch(r#"{"defaultBranchRef": null}"#), None);
183 assert_eq!(parse_default_branch("not json"), None);
184 }
185}