1mod api_token;
2mod insights;
3mod pipeline;
4mod project;
5
6use reqwest::{
7 header::{HeaderMap, HeaderValue, InvalidHeaderValue},
8 Response,
9};
10use std::{
11 env::{self, VarError},
12 fmt::Debug,
13};
14use thiserror::Error;
15
16pub use api_token::ApiToken;
17pub use insights::*;
18pub use pipeline::*;
19pub use project::*;
20
21const API_URL: &str = "https://circleci.com/api/v2";
22
23pub struct Client {
24 url: String,
25 client: reqwest::Client,
26}
27
28impl Debug for Client {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("Client")
31 .field("url", &self.url)
32 .field("api_token", &"<redacted>")
33 .finish()
34 }
35}
36
37#[derive(Debug, Error)]
38pub enum Error {
39 #[error("invalid header")]
40 InvalidHeader(#[from] InvalidHeaderValue),
41
42 #[error("reqwest error")]
43 Reqwest(#[from] reqwest::Error),
44
45 #[error("something wrong with the env var")]
46 EnvVar(#[from] VarError),
47
48 #[error("received an error code from an HTTP request")]
49 Http(String),
50}
51
52type Result<T> = std::result::Result<T, Error>;
53
54impl Client {
55 pub fn new(api_token: &ApiToken) -> Result<Self> {
60 let mut headers = HeaderMap::new();
61 let mut auth_value = HeaderValue::from_str(api_token)?;
62 auth_value.set_sensitive(true);
63 headers.insert("Circle-Token", auth_value);
64
65 let client = reqwest::Client::builder()
66 .default_headers(headers)
67 .build()?;
68
69 Ok(Self {
70 url: env::var("CIRCLECI_API_URL")
71 .unwrap_or_else(|_| API_URL.to_string()),
72 client,
73 })
74 }
75
76 pub async fn get<T: Requestable>(&self, params: &T) -> Result<Response> {
80 let req = self
81 .client
82 .get(params.path(&self.url))
83 .query(¶ms.query_params());
84
85 let result = req.send().await?;
86 if !result.status().is_success() {
87 return Err(Error::Http(result.text().await?));
88 }
89
90 Ok(result)
91 }
92
93 pub async fn delete<T: Requestable>(&self, params: &T) -> Result<Response> {
99 let req = self
100 .client
101 .delete(params.path(&self.url))
102 .query(¶ms.query_params());
103
104 let result = req.send().await?;
105 if !result.status().is_success() {
106 return Err(Error::Http(result.text().await?));
107 }
108
109 Ok(result)
110 }
111}
112
113pub trait Requestable {
114 fn path(&self, base: &str) -> String;
115
116 fn query_params(&self) -> Vec<(&str, &str)> {
117 vec![]
118 }
119}