gh_stack/api/
search.rs

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    // The `unwrap`s are required here because both `reqwest::send` and `reqwest::json` return a `Result` which has
106    // to be unwrapped after the future has been `await`ed on.
107    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}