subalfred_core/github/
mod.rs

1//! Subalfred core GitHub library.
2
3#[cfg(test)] mod test;
4
5pub mod substrate;
6
7// std
8use std::{env, sync::Arc};
9// crates.io
10use githuber::api::{ApiExt, Method::*};
11use reqwest::{
12	Client,
13	header::{ACCEPT, USER_AGENT},
14};
15use serde::{Deserialize, de::DeserializeOwned};
16// subalfred
17use crate::{http::CLIENT, prelude::*};
18
19/// GitHub REST API client.
20#[derive(Debug, Clone)]
21pub struct ApiClient {
22	inner: Arc<Client>,
23	token: String,
24}
25impl ApiClient {
26	const PER_PAGE: u8 = 100;
27	const USER_AGENT: &'static str = "subalfred-api-client";
28
29	/// Create a new API client.
30	pub fn new() -> Result<Self> {
31		Ok(Self { inner: CLIENT.clone(), token: get_github_token()? })
32	}
33
34	/// Send the given request.
35	pub async fn request<R, D>(&self, request: &R) -> Result<D>
36	where
37		R: ApiExt,
38		D: DeserializeOwned,
39	{
40		let api = request.api();
41		let payload_params = request.payload_params();
42
43		tracing::trace!("Request({api}),Payload({payload_params:?})");
44
45		let response = match R::METHOD {
46			Delete => todo!(),
47			Get => self
48				.inner
49				.get(api)
50				.header(ACCEPT, R::ACCEPT)
51				.header(USER_AGENT, Self::USER_AGENT)
52				.bearer_auth(&self.token)
53				.query(&payload_params),
54			Patch => todo!(),
55			Post => todo!(),
56			Put => todo!(),
57		}
58		.send()
59		.await
60		.map_err(error::Generic::Reqwest)?
61		.json::<D>()
62		.await
63		.map_err(error::Generic::Reqwest)?;
64
65		Ok(response)
66	}
67
68	/// Send the given request.
69	///
70	/// This function is infallible.
71	/// It will retry at most `u16::MAX` times.
72	/// If the target server is down or the deserialization fails current thread will be blocked.
73	pub async fn request_auto_retry<R, D>(&self, request: &R) -> D
74	where
75		R: Clone + ApiExt,
76		D: DeserializeOwned,
77	{
78		for i in 0_u16.. {
79			match self.request::<R, D>(request).await {
80				Ok(r) => return r,
81				Err(e) => {
82					tracing::warn!("request failed dut to: {e:?}, retried {i} times");
83				},
84			}
85		}
86
87		unreachable!(
88			"[core::github] there is an infinity loop before; hence this block can never be reached; qed"
89		)
90	}
91}
92
93#[derive(Debug, Deserialize)]
94struct Commits {
95	commits: Vec<Commit>,
96}
97#[derive(Debug, Deserialize)]
98struct Commit {
99	sha: String,
100}
101
102/// Elementary pull request type.
103#[cfg_attr(test, derive(PartialEq, Eq))]
104#[derive(Clone, Debug, Deserialize)]
105pub struct PullRequest {
106	/// Pull request's title.
107	pub title: String,
108	/// Pull request's uri.
109	pub html_url: String,
110	/// Pull request's labels.
111	pub labels: Vec<Label>,
112}
113/// Elementary label type.
114#[cfg_attr(test, derive(PartialEq, Eq))]
115#[derive(Clone, Debug, Deserialize)]
116pub struct Label {
117	/// Label's name
118	pub name: String,
119}
120
121fn get_github_token() -> Result<String> {
122	Ok(env::var("GITHUB_TOKEN").map_err(error::Github::NoTokenFound)?)
123}