use std::collections::HashMap;
use anyhow::{anyhow, Context, Result};
use reqwest::{header, Url};
use serde::Deserialize;
use simplelog::debug;
use crate::utils;
#[derive(Clone, Default, Debug)]
pub struct HttpClient {
base_url: String,
headers: header::HeaderMap,
plex_token: String,
client: reqwest::Client,
}
type Params = Option<HashMap<String, String>>;
impl HttpClient {
pub fn new(base_url: &str, plex_token: &str) -> Result<Self> {
debug!("Creating HTTP client...");
let mut headers = header::HeaderMap::new();
headers.append(
header::ACCEPT,
header::HeaderValue::from_static("application/json"),
);
let client = reqwest::Client::builder().build()?;
Ok(Self {
base_url: base_url.to_owned(),
plex_token: plex_token.to_owned(),
headers,
client,
})
}
pub async fn get<T>(&self, path: &str, params: Params, max_results: Option<i32>) -> Result<T>
where
T: for<'de> Deserialize<'de> + Default,
{
let url = self.build_final_url(path, params)?;
let req = self.client.get(url).headers(self.headers.clone());
let req = if let Some(max_results) = max_results {
req.header("X-Plex-Container-Size", max_results.to_string())
.header("X-Plex-Container-Start", "0")
} else {
req
};
match req.send().await {
Ok(resp) => {
let url = resp.url().to_owned();
let contents = resp.text().await?;
if contents.is_empty() {
return Ok(T::default());
}
serde_json::from_str(&contents).with_context(|| {
format!(
"Unable to deserialize GET response [{url}].\nBody was:\n \"{}\"",
utils::truncate_string(&contents, 5000)
)
})
}
Err(err) => Err(anyhow!("An error occurred while attempting to GET: {err}")),
}
}
pub async fn delete(&self, path: &str, params: Params) -> Result<()> {
let url = self.build_final_url(path, params)?;
self.client.delete(url).send().await?;
Ok(())
}
pub async fn post<T>(&self, path: &str, params: Params) -> Result<T>
where
T: for<'de> Deserialize<'de> + Default,
{
let url = self.build_final_url(path, params)?;
match self
.client
.post(url)
.headers(self.headers.clone())
.send()
.await
{
Ok(resp) => {
let contents = resp.text().await?;
if contents.is_empty() {
return Ok(T::default());
}
serde_json::from_str(&contents).with_context(|| {
format!("Unable to deserialise response. Body was: \"{}\"", contents)
})
}
Err(err) => Err(anyhow!("An error occurred while attempting to POST: {err}")),
}
}
pub async fn put<T>(&self, path: &str, params: Params) -> Result<T>
where
T: for<'de> Deserialize<'de> + Default,
{
let url = self.build_final_url(path, params)?;
match self
.client
.put(url)
.headers(self.headers.clone())
.send()
.await
{
Ok(resp) => {
let contents = resp.text().await?;
if contents.is_empty() {
return Ok(T::default());
}
serde_json::from_str(&contents).with_context(|| {
format!("Unable to deserialise response. Body was: \"{}\"", contents)
})
}
Err(err) => Err(anyhow!("An error occurred while attempting to PUT: {err}")),
}
}
fn build_final_url(&self, path: &str, params: Params) -> Result<Url> {
let mut url = Url::parse(&self.base_url)?.join(path)?;
url.query_pairs_mut()
.append_pair("X-Plex-Token", &self.plex_token);
if let Some(params) = params {
for (k, v) in params {
url.query_pairs_mut().append_pair(&k, &v);
}
}
Ok(url)
}
}