1use anyhow::Result;
2use log::{debug, error};
3use reqwest::header;
4use serde::Serialize;
5use url::{Position, Url};
6
7pub struct Client {
8 pub api_server: Url,
9 pub client: reqwest::blocking::Client,
10}
11
12#[derive(Clone, Copy)]
13enum Verb {
14 Get,
15 Put,
16 Post,
17}
18
19pub struct Paginated<'t, T> {
20 client: &'t Client,
21 api_path: &'t str,
22 verb: Verb,
23 message: Option<&'t T>,
24 next_page: NextPage,
25}
26
27#[derive(Debug)]
28enum NextPage {
29 Start,
30 Next(isize),
31 End,
32}
33
34impl<'t, T> Paginated<'t, T>
35where
36 T: Serialize + Sized,
37{
38 fn new(client: &'t Client, api_path: &'t str, verb: Verb, message: Option<&'t T>) -> Self {
39 Self {
40 client,
41 api_path,
42 verb,
43 message,
44 next_page: NextPage::Start,
45 }
46 }
47 fn get_next_page(&mut self) -> Result<Option<reqwest::blocking::Response>> {
48 let update_next_page = |resp: &reqwest::blocking::Response| -> Result<NextPage> {
49 let next_page = resp
50 .headers()
51 .get("x-next-page")
52 .map(|p| p.to_str().unwrap_or(""));
53 let next_page = match next_page {
54 None | Some("") => NextPage::End,
55 Some(num) => NextPage::Next(num.parse::<isize>()?),
56 };
57 Ok(next_page)
58 };
59 match self.next_page {
60 NextPage::Start => {
61 let resp = self
62 .client
63 .send(self.api_path, self.verb, self.message, None)?;
64 self.next_page = update_next_page(&resp)?;
65 Ok(Some(resp))
66 }
67 NextPage::Next(page) => {
68 let resp = self
69 .client
70 .send(self.api_path, self.verb, self.message, Some(page))?;
71 self.next_page = update_next_page(&resp)?;
72 Ok(Some(resp))
73 }
74 NextPage::End => Ok(None),
75 }
76 }
77}
78
79impl<'t, T> Iterator for Paginated<'t, T>
80where
81 T: Serialize + Sized,
82{
83 type Item = Result<reqwest::blocking::Response>;
84 fn next(&mut self) -> Option<Self::Item> {
85 match self.get_next_page() {
86 Ok(Some(res)) => Some(Ok(res)),
87 Ok(None) => None,
88 Err(err) => Some(Err(err)),
89 }
90 }
91}
92
93impl Client {
94 pub fn new(api_server: Url, private_token: &[u8]) -> Result<Client> {
95 let mut headers = header::HeaderMap::new();
96 headers.insert(
97 "PRIVATE-TOKEN",
98 header::HeaderValue::from_bytes(private_token)?,
99 );
100 let client = reqwest::blocking::Client::builder()
101 .default_headers(headers)
102 .build()?;
103 let api_server = Url::parse(&api_server[..Position::BeforePath])?;
104 Ok(Client { api_server, client })
105 }
106 fn send<T>(
107 &self,
108 api_path: &str,
109 verb: Verb,
110 message: Option<&T>,
111 page: Option<isize>,
112 ) -> Result<reqwest::blocking::Response>
113 where
114 T: Serialize + ?Sized,
115 {
116 let mut full_url = self.api_server.join(api_path)?;
117 if let Some(num) = page {
118 full_url
119 .query_pairs_mut()
120 .append_pair("page", &format!("{}", num));
121 }
122 let mut builder = match verb {
123 Verb::Get => self.client.get(full_url.as_str()),
124 Verb::Put => self.client.put(full_url.as_str()),
125 Verb::Post => self.client.post(full_url.as_str()),
126 };
127 if let Some(message) = message {
128 builder = builder.json(message);
129 }
130 debug!("{:?}", builder);
131 Ok(builder
132 .send()
133 .map_err(|err| {
134 error!("{}", err);
135 err
136 })?
137 .error_for_status()?)
138 }
139 pub fn put_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
140 where
141 T: Serialize + ?Sized,
142 {
143 self.send(api_path, Verb::Put, Some(message), None)
144 }
145 pub fn post_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
146 where
147 T: Serialize + ?Sized,
148 {
149 self.send(api_path, Verb::Post, Some(message), None)
150 }
151 pub fn get_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
152 where
153 T: Serialize + ?Sized,
154 {
155 self.send(api_path, Verb::Get, Some(message), None)
156 }
157 pub fn put(&self, api_path: &str) -> Result<reqwest::blocking::Response> {
158 self.send::<&str>(api_path, Verb::Put, None, None)
161 }
162 pub fn get(&self, api_path: &str) -> Result<reqwest::blocking::Response> {
163 self.send::<&str>(api_path, Verb::Get, None, None)
164 }
165 pub fn get_paginated<'c>(&'c self, api_path: &'c str) -> Paginated<()> {
166 Paginated::new(&self, api_path, Verb::Get, None)
167 }
168 pub fn get_json_paginated<'c, T>(&'c self, api_path: &'c str, message: &'c T) -> Paginated<T>
169 where
170 T: Serialize + Sized,
171 {
172 Paginated::new(&self, api_path, Verb::Get, Some(message))
173 }
174}