ghub/v3/
pull_request.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use reqwest::Client as HttpClient;
5use serde::Deserialize;
6use serde::Serialize;
7use serde_json::Value;
8
9use crate::types::ResultDynError;
10use crate::v3::util as client_util;
11
12pub struct GithubPullRequestClient {
13  pub(crate) http_client: Arc<HttpClient>,
14}
15
16impl GithubPullRequestClient {
17  pub fn new(http_client: Arc<HttpClient>) -> ResultDynError<GithubPullRequestClient> {
18    let client = GithubPullRequestClient { http_client };
19
20    return Ok(client);
21  }
22}
23
24#[derive(Debug, Deserialize, Serialize)]
25pub enum GithubMergeMethod {
26  #[serde(rename = "merge")]
27  Merge,
28
29  #[serde(rename = "rebase")]
30  Rebase,
31
32  #[serde(rename = "squash")]
33  Squash,
34}
35
36#[derive(Debug)]
37pub struct CreatePullRequestInput<'a> {
38  pub title: &'a str,
39  pub repo_path: &'a str,
40  pub branch_name: &'a str,
41  pub into_branch: &'a str,
42}
43
44#[derive(Debug)]
45pub struct MergePullRequestInput<'a> {
46  pub repo_path: &'a str,
47  pub pull_number: &'a str,
48  pub merge_method: GithubMergeMethod,
49}
50
51#[derive(Debug)]
52pub struct GetPullRequestByHeadInput<'a> {
53  pub repo_path: &'a str,
54  pub branch_owner: &'a str,
55  pub branch_name: &'a str,
56}
57
58impl GithubPullRequestClient {
59  pub async fn create<'a>(&self, input: CreatePullRequestInput<'a>) -> ResultDynError<Value> {
60    let CreatePullRequestInput {
61      title,
62      repo_path,
63      branch_name,
64      into_branch,
65    } = input;
66    let mut req_body = HashMap::new();
67    req_body.insert("title", title);
68    req_body.insert("head", branch_name);
69    req_body.insert("base", into_branch);
70
71    log::debug!(
72      "Creating pull request {}, req_body: {:?}",
73      repo_path,
74      req_body
75    );
76
77    let req = self
78      .http_client
79      .post(&client_util::api_path(&format!(
80        "/repos/{}/pulls",
81        repo_path
82      )))
83      .body(serde_json::to_string(&req_body)?);
84
85    log::debug!("Initiating request instance {:?} {:?}", req, req_body);
86
87    let res = req.send().await;
88
89    log::debug!("Done creating pull request, response {:?}", res);
90
91    return client_util::result_from_server_response(res?).await;
92  }
93
94  pub async fn merge<'a>(&self, input: MergePullRequestInput<'a>) -> ResultDynError<Value> {
95    let MergePullRequestInput {
96      repo_path,
97      pull_number,
98      merge_method,
99    } = input;
100    let mut req_body = HashMap::new();
101    req_body.insert("merge_method", merge_method);
102
103    log::debug!(
104      "Merging pull request {} pull number {}, req_body: {:?}",
105      repo_path,
106      pull_number,
107      req_body
108    );
109
110    let req = self
111      .http_client
112      .put(&client_util::api_path(&format!(
113        "/repos/{}/pulls/{}/merge",
114        repo_path, pull_number
115      )))
116      .body(serde_json::to_string(&req_body)?);
117
118    log::debug!("Initiating request instance {:?} {:?}", req, req_body);
119
120    let res = req.send().await;
121
122    log::debug!("Done merging pull request, response {:?}", res);
123
124    return client_util::result_from_server_response(res?).await;
125  }
126
127  /// Get pull request by head in format `{branch_owner}:{branch_name}'`
128  ///
129  /// So for example if my user is sendyhalim and I'm creating a PR
130  /// with branch name `x` then head will be `sendyhalim:x`.
131  pub async fn get_by_head<'a>(
132    &self,
133    input: GetPullRequestByHeadInput<'a>,
134  ) -> ResultDynError<Option<Value>> {
135    log::debug!("Getting pull request by head {:?}", input);
136
137    let GetPullRequestByHeadInput {
138      branch_owner,
139      repo_path,
140      branch_name,
141    } = input;
142
143    let req = self.http_client.get(&client_util::api_path(&format!(
144      "/repos/{}/pulls?head={}:{}",
145      repo_path, branch_owner, branch_name
146    )));
147
148    log::debug!(
149      "Initiating request instance to list pull requests {:?}",
150      req
151    );
152
153    let res = req.send().await;
154
155    log::debug!("Done listing pull request, response {:?}", res);
156
157    let body: Value = client_util::result_from_server_response(res?).await?;
158
159    log::debug!("Response body from listing pull request {:?}", body);
160
161    // Try to get the first index,
162    // if it's not an object then we assume it's the PR does not exist
163    let pull_request: Option<Value> = match &body[0] {
164      Value::Object(obj) => Some(Value::Object(obj.to_owned())),
165      _ => None,
166    };
167
168    // We will try to get the PR via single record GET API req because there are
169    // some fields that only available on single record API.
170    // For example, the Mergeable field is compute-intensive, so it's only exposed on Get.
171    // Ref: https://github.com/octokit/octokit.net/issues/1710#issuecomment-342331188
172
173    if pull_request.is_none() {
174      return Ok(pull_request);
175    }
176
177    let pull_request = pull_request.unwrap();
178
179    let req = self.http_client.get(&client_util::api_path(&format!(
180      "/repos/{}/pulls/{}",
181      repo_path, pull_request["number"]
182    )));
183
184    log::debug!(
185      "Initiating request instance to get pull request detail {:?}",
186      req
187    );
188
189    let res = req.send().await;
190
191    log::debug!("Done getting pull request detail, response {:?}", res);
192
193    return client_util::result_from_server_response(res?)
194      .await
195      .map(Some);
196  }
197}