nixpkgs_track_lib/
lib.rs

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}