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}?per_page=1000000&page=100");
49 let response = build_request(client, &url, token)
50 .send()
51 .await;
52
53 log::debug!("branch_contains_commit: {:?}", response);
54
55 match response {
56 Ok(response) => match response.status() {
57 StatusCode::OK => match response.json::<Comparison>().await {
58 Ok(json) => Ok(json.status == "identical" || json.status == "behind"),
59 Err(err) => Err(NixpkgsTrackError::RequestFailed(err)),
60 },
61 StatusCode::NOT_FOUND => Ok(false),
62 StatusCode::FORBIDDEN => Err(NixpkgsTrackError::RateLimitExceeded),
63 _ => Err(NixpkgsTrackError::RequestFailed(response.error_for_status().unwrap_err())),
64 },
65 Err(err) => Err(NixpkgsTrackError::RequestFailed(err)),
66 }
67}
68
69#[derive(Clone, Debug, Deserialize)]
70pub struct User {
71 pub login: String,
72 pub url: String,
73}
74
75#[non_exhaustive]
76#[derive(Clone, Debug, Deserialize)]
77pub struct PullRequest {
78 pub html_url: String,
79 pub number: u64,
80 pub title: String,
81 pub user: User,
82 pub created_at: DateTime<Utc>,
83 pub merged_at: Option<DateTime<Utc>>,
84 pub merged: bool,
85 pub merge_commit_sha: Option<String>,
86 pub base: ForkBranch,
88 pub head: ForkBranch,
90}
91
92#[non_exhaustive]
93#[derive(Clone, Debug, Deserialize)]
94pub struct ForkBranch {
95 pub label: String,
97 pub r#ref: String,
99 pub sha: String,
100}
101
102#[derive(Clone, Debug, Deserialize)]
103pub struct Comparison {
104 pub status: String,
105}
106
107#[derive(Clone, Debug, Deserialize)]
108pub struct GitHubError {
109 pub message: String,
110 pub documentation_url: String,
111}
112
113#[derive(Error, Debug)]
114#[cfg_attr(feature = "miette", derive(miette::Diagnostic))]
115pub enum NixpkgsTrackError {
116 #[error("Pull request not found")]
117 PullRequestNotFound(u64),
118 #[error("Rate limit exceeded")]
119 RateLimitExceeded,
120 #[error(transparent)]
121 RequestFailed(#[from] reqwest::Error),
122}