use anyhow::Result;
use log::{debug, error};
use reqwest::header;
use serde::Serialize;
use url::{Position, Url};
pub struct Client {
pub api_server: Url,
pub client: reqwest::blocking::Client,
}
#[derive(Clone, Copy)]
enum Verb {
Get,
Put,
Post,
}
pub struct Paginated<'t, T> {
client: &'t Client,
api_path: &'t str,
verb: Verb,
message: Option<&'t T>,
next_page: NextPage,
}
#[derive(Debug)]
enum NextPage {
Start,
Next(isize),
End,
}
impl<'t, T> Paginated<'t, T>
where
T: Serialize + Sized,
{
fn new(client: &'t Client, api_path: &'t str, verb: Verb, message: Option<&'t T>) -> Self {
Self {
client,
api_path,
verb,
message,
next_page: NextPage::Start,
}
}
fn get_next_page(&mut self) -> Result<Option<reqwest::blocking::Response>> {
let update_next_page = |resp: &reqwest::blocking::Response| -> Result<NextPage> {
let next_page = resp
.headers()
.get("x-next-page")
.map(|p| p.to_str().unwrap_or(""));
let next_page = match next_page {
None | Some("") => NextPage::End,
Some(num) => NextPage::Next(num.parse::<isize>()?),
};
Ok(next_page)
};
match self.next_page {
NextPage::Start => {
let resp = self
.client
.send(self.api_path, self.verb, self.message, None)?;
self.next_page = update_next_page(&resp)?;
Ok(Some(resp))
}
NextPage::Next(page) => {
let resp = self
.client
.send(self.api_path, self.verb, self.message, Some(page))?;
self.next_page = update_next_page(&resp)?;
Ok(Some(resp))
}
NextPage::End => Ok(None),
}
}
}
impl<'t, T> Iterator for Paginated<'t, T>
where
T: Serialize + Sized,
{
type Item = Result<reqwest::blocking::Response>;
fn next(&mut self) -> Option<Self::Item> {
match self.get_next_page() {
Ok(Some(res)) => Some(Ok(res)),
Ok(None) => None,
Err(err) => Some(Err(err)),
}
}
}
impl Client {
pub fn new(api_server: Url, private_token: &[u8]) -> Result<Client> {
let mut headers = header::HeaderMap::new();
headers.insert(
"PRIVATE-TOKEN",
header::HeaderValue::from_bytes(private_token)?,
);
let client = reqwest::blocking::Client::builder()
.default_headers(headers)
.build()?;
let api_server = Url::parse(&api_server[..Position::BeforePath])?;
Ok(Client { api_server, client })
}
fn send<T>(
&self,
api_path: &str,
verb: Verb,
message: Option<&T>,
page: Option<isize>,
) -> Result<reqwest::blocking::Response>
where
T: Serialize + ?Sized,
{
let mut full_url = self.api_server.join(api_path)?;
if let Some(num) = page {
full_url
.query_pairs_mut()
.append_pair("page", &format!("{}", num));
}
let mut builder = match verb {
Verb::Get => self.client.get(full_url.as_str()),
Verb::Put => self.client.put(full_url.as_str()),
Verb::Post => self.client.post(full_url.as_str()),
};
if let Some(message) = message {
builder = builder.json(message);
}
debug!("{:?}", builder);
Ok(builder
.send()
.map_err(|err| {
error!("{}", err);
err
})?
.error_for_status()?)
}
pub fn put_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
where
T: Serialize + ?Sized,
{
self.send(api_path, Verb::Put, Some(message), None)
}
pub fn post_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
where
T: Serialize + ?Sized,
{
self.send(api_path, Verb::Post, Some(message), None)
}
pub fn get_json<T>(&self, api_path: &str, message: &T) -> Result<reqwest::blocking::Response>
where
T: Serialize + ?Sized,
{
self.send(api_path, Verb::Get, Some(message), None)
}
pub fn put(&self, api_path: &str) -> Result<reqwest::blocking::Response> {
self.send::<&str>(api_path, Verb::Put, None, None)
}
pub fn get(&self, api_path: &str) -> Result<reqwest::blocking::Response> {
self.send::<&str>(api_path, Verb::Get, None, None)
}
pub fn get_paginated<'c>(&'c self, api_path: &'c str) -> Paginated<()> {
Paginated::new(&self, api_path, Verb::Get, None)
}
pub fn get_json_paginated<'c, T>(&'c self, api_path: &'c str, message: &'c T) -> Paginated<T>
where
T: Serialize + Sized,
{
Paginated::new(&self, api_path, Verb::Get, Some(message))
}
}