1use futures::future::join_all;
2use serde::Deserialize;
3use std::error::Error;
4
5use crate::{api, Credentials};
6
7#[derive(Deserialize, Debug, Clone)]
8pub struct SearchItem {
9 url: String,
10 title: String,
11}
12
13#[derive(Deserialize, Debug, Clone)]
14pub struct PullRequestRef {
15 label: String,
16 #[serde(rename = "ref")]
17 gitref: String,
18 sha: String,
19}
20
21#[derive(Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
22pub enum PullRequestStatus {
23 #[serde(rename = "open")]
24 Open,
25 #[serde(rename = "closed")]
26 Closed,
27}
28
29#[derive(Deserialize, Debug, Clone)]
30pub struct PullRequest {
31 id: usize,
32 number: usize,
33 head: PullRequestRef,
34 base: PullRequestRef,
35 title: String,
36 url: String,
37 body: String,
38 state: PullRequestStatus,
39}
40
41impl PullRequest {
42 pub fn head(&self) -> &str {
43 &self.head.gitref
44 }
45
46 pub fn base(&self) -> &str {
47 &self.base.gitref
48 }
49
50 pub fn url(&self) -> &str {
51 &self.url
52 }
53
54 pub fn number(&self) -> usize {
55 self.number
56 }
57
58 pub fn title(&self) -> String {
59 match &self.state {
60 PullRequestStatus::Open => self.title.to_owned(),
61 PullRequestStatus::Closed => format!("~~{}~~", &self.title.trim()),
62 }
63 }
64
65 pub fn state(&self) -> &PullRequestStatus {
66 &self.state
67 }
68
69 pub fn body(&self) -> &str {
70 &self.body
71 }
72
73 pub fn note(&self) -> &str {
74 match &self.state {
75 PullRequestStatus::Open => "N/A",
76 PullRequestStatus::Closed => "Merged",
77 }
78 }
79}
80
81#[derive(Deserialize, Debug)]
82struct SearchResponse {
83 items: Vec<SearchItem>,
84}
85
86pub async fn fetch_pull_requests_matching(
87 pattern: &str,
88 credentials: &Credentials,
89) -> Result<Vec<PullRequest>, Box<dyn Error>> {
90 let client = reqwest::Client::new();
91
92 let request = api::base_request(
93 &client,
94 &credentials,
95 "https://api.github.com/search/issues",
96 )
97 .query(&[("q", format!("{} in:title", pattern))]);
98
99 let items = request.send().await?.json::<SearchResponse>().await?.items;
100
101 let item_futures = items.into_iter().map(|item| {
102 api::base_request(&client, &credentials, &item.url.replace("issues", "pulls")).send()
103 });
104
105 let items = join_all(item_futures)
108 .await
109 .into_iter()
110 .map(|item| item.unwrap());
111
112 let responses: Vec<_> = join_all(items.map(|item| item.json::<PullRequest>()))
113 .await
114 .into_iter()
115 .map(|item| item.unwrap())
116 .collect();
117
118 Ok(responses)
119}