1use chrono::prelude::*;
2use reqwest::{RequestBuilder, StatusCode};
3use serde::Deserialize;
4
5use thiserror::Error;
6
7const BASE_API_URL: &str = "https://api.github.com/repos/nixos/nixpkgs";
8
9fn build_request(client: impl AsRef<reqwest::Client>, url: &str, token: Option<&str>) -> RequestBuilder {
10 let mut request = client
11 .as_ref()
12 .get(url)
13 .header("User-Agent", "nixpkgs-track");
14
15 if let Some(token) = token {
16 request = request.bearer_auth(token);
17 }
18
19 request
20}
21
22pub async fn fetch_nixpkgs_pull_request(client: impl AsRef<reqwest::Client>, pull_request: u64, token: Option<&str>) -> Result<PullRequest, NixpkgsTrackError> {
23 let url = format!("{BASE_API_URL}/pulls/{pull_request}");
24 let response = build_request(client, &url, token)
25 .send()
26 .await;
27
28 log::debug!("fetch_nixpkgs_pull_request: {:?}", response);
29
30 match response {
31 Ok(response) => match response.status() {
32 StatusCode::OK => response
33 .json::<PullRequest>()
34 .await
35 .map_err(NixpkgsTrackError::RequestFailed),
36 StatusCode::NOT_FOUND => Err(NixpkgsTrackError::PullRequestNotFound(pull_request)),
37 StatusCode::FORBIDDEN => Err(NixpkgsTrackError::RateLimitExceeded),
38 _ => Err(NixpkgsTrackError::RequestFailed(response.error_for_status().unwrap_err())),
39 },
40 Err(err) => Err(NixpkgsTrackError::RequestFailed(err)),
41 }
42}
43
44pub async fn branch_contains_commit(client: impl AsRef<reqwest::Client>, branch: &str, commit: &str, token: Option<&str>) -> Result<bool, NixpkgsTrackError> {
45 let url = format!("{BASE_API_URL}/compare/{branch}...{commit}");
46 let response = build_request(client, &url, token)
47 .send()
48 .await;
49
50 log::debug!("branch_contains_commit: {:?}", response);
51
52 match response {
53 Ok(response) => match response.status() {
54 StatusCode::OK => match response.json::<Comparison>().await {
55 Ok(json) => Ok(json.status == "identical" || json.status == "behind"),
56 Err(err) => Err(NixpkgsTrackError::RequestFailed(err)),
57 },
58 StatusCode::NOT_FOUND => Ok(false),
59 StatusCode::FORBIDDEN => Err(NixpkgsTrackError::RateLimitExceeded),
60 _ => Err(NixpkgsTrackError::RequestFailed(response.error_for_status().unwrap_err())),
61 },
62 Err(err) => Err(NixpkgsTrackError::RequestFailed(err)),
63 }
64}
65
66#[derive(Clone, Debug, Deserialize)]
67pub struct User {
68 pub login: String,
69 pub url: String,
70}
71
72#[derive(Clone, Debug, Deserialize)]
73pub struct PullRequest {
74 pub html_url: String,
75 pub number: u64,
76 pub title: String,
77 pub user: User,
78 pub created_at: DateTime<Utc>,
79 pub merged_at: Option<DateTime<Utc>>,
80 pub merged: bool,
81 pub merge_commit_sha: Option<String>,
82}
83
84#[derive(Clone, Debug, Deserialize)]
85pub struct Comparison {
86 pub status: String,
87}
88
89#[derive(Clone, Debug, Deserialize)]
90pub struct GitHubError {
91 pub message: String,
92 pub documentation_url: String,
93}
94
95#[derive(Error, Debug)]
96#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
97pub enum NixpkgsTrackError {
98 #[error("Pull request not found")]
99 PullRequestNotFound(u64),
100 #[error("Rate limit exceeded")]
101 RateLimitExceeded,
102 #[error(transparent)]
103 RequestFailed(#[from] reqwest::Error),
104}