pub use crate::endpoints::*; use crate::{ClientBuilder, Error, Result}; use flate2::write::ZlibDecoder; use reqwest::Response; use serde::Serialize; use std::fmt::Debug; use std::io::Write; pub struct Client { pub(crate) client: reqwest::Client, pub(crate) base_url: String, pub(crate) access_key: String, } impl Client { /// Creates a new client builder pub fn builder() -> ClientBuilder { ClientBuilder::default() } /// Creates a new PTR Client pub fn new(endpoint: S1, access_key: S2) -> Self { Self { base_url: endpoint.to_string(), client: reqwest::Client::new(), access_key: access_key.to_string(), } } /// Returns the options of the PTR #[tracing::instrument(skip(self), level = "debug")] pub async fn get_options(&self) -> Result { self.get::(&()).await } /// Returns information about all available updates since the given ID /// and when the next check for updates should be made #[tracing::instrument(skip(self), level = "debug")] pub async fn get_metadata(&self, since: u64) -> Result { self.get::(&[("since", since)]).await } /// Returns the parsed update file identified by the given hash. /// The hash can be retrieved by fetching the metadata with [Client::metadata] #[tracing::instrument(skip(self), level = "debug")] pub async fn get_update + Debug>( &self, update_hash: S, ) -> Result { self.get::(&[("update_hash", update_hash.as_ref())]) .await } /// Performs a get request to the given Get Endpoint #[tracing::instrument(skip(self), level = "trace")] async fn get(&self, query: &Q) -> Result { tracing::trace!("GET request to {}", E::path()); let response = self .client .get(format!("{}/{}", self.base_url, E::path())) .query(query) .header("Hydrus-Key", self.access_key.to_string()) .send() .await?; let body = Self::get_body(response).await?; let bytes = Self::decompress_body(body)?; let response_type = Self::deserialize_body(bytes)?; tracing::trace!("response is: {:?}", response_type); Ok(response_type) } /// Returns the body from the response #[tracing::instrument(level = "trace")] async fn get_body(response: Response) -> Result> { if response.status().is_success() { Ok(response.bytes().await?.to_vec()) } else { let message = response.text().await?; Err(Error::Response(message)) } } /// Uses zlib to decompress the body #[tracing::instrument(skip(bytes), level = "trace")] fn decompress_body(mut bytes: Vec) -> Result> { tracing::trace!("body length {}", bytes.len()); let mut buf = Vec::new(); let mut decoder = ZlibDecoder::new(buf); decoder.write_all(&mut bytes)?; buf = decoder.finish()?; tracing::trace!("result length {}", buf.len()); Ok(buf) } /// Deserializes the body to the given type #[tracing::instrument(skip(bytes), level = "trace")] fn deserialize_body(bytes: Vec) -> Result { let json_value: serde_json::Value = serde_json::from_reader(&bytes[..])?; tracing::trace!("json value = {}", json_value.to_string()); T::from_json(json_value) } }