use std::fmt;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::options::{Format, RenderOptions};
use crate::result::RenderResult;
use crate::shared::{self, BuilderState, Config, Payload, Prepared};
#[derive(Clone)]
pub struct Client {
http: reqwest::blocking::Client,
config: Arc<Config>,
}
impl fmt::Debug for Client {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.config.fmt(f)
}
}
impl Client {
pub fn new() -> Result<Self> {
Self::builder().build()
}
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
pub fn render(&self, zpl: impl Into<Vec<u8>>, options: RenderOptions) -> Result<RenderResult> {
let prepared = shared::prepare(&self.config, Payload::Label(zpl.into()), &options)?;
self.execute(prepared)
}
pub fn render_files<I, N, D>(&self, files: I, options: RenderOptions) -> Result<RenderResult>
where
I: IntoIterator<Item = (N, D)>,
N: Into<String>,
D: Into<Vec<u8>>,
{
let prepared = shared::prepare(&self.config, Payload::files(files)?, &options)?;
self.execute(prepared)
}
pub fn render_pdf(&self, zpl: impl Into<Vec<u8>>) -> Result<RenderResult> {
self.render(zpl, RenderOptions::new().format(Format::Pdf))
}
pub fn render_png(&self, zpl: impl Into<Vec<u8>>, page: u32) -> Result<RenderResult> {
self.render(zpl, RenderOptions::new().format(Format::Png).page(page))
}
pub fn health(&self) -> Result<bool> {
let url = format!("{}/health", self.config.base_url);
let response = self.http.get(url).send().map_err(Error::from_transport)?;
let ok = response.status().as_u16() == 200;
let body = response.text().map_err(Error::from_transport)?;
Ok(ok && body.trim() == "ok")
}
fn execute(&self, prepared: Prepared) -> Result<RenderResult> {
let mut attempt = 0;
loop {
let error = match self.send(&prepared) {
Ok(result) => return Ok(result),
Err(error) => error,
};
match shared::retry_delay(&error, attempt, self.config.max_retries) {
Some(delay) => std::thread::sleep(delay),
None => return Err(error),
}
attempt += 1;
}
}
fn send(&self, prepared: &Prepared) -> Result<RenderResult> {
let response = self
.http
.post(&prepared.url)
.headers(prepared.headers.clone())
.body(prepared.body.clone())
.send()
.map_err(Error::from_transport)?;
let status = response.status().as_u16();
let headers = response.headers().clone();
let body = response.bytes().map_err(Error::from_transport)?;
if status == 200 {
Ok(RenderResult::from_response(&headers, body.into()))
} else {
Err(shared::error_response(status, &headers, &body))
}
}
}
#[derive(Debug, Default, Clone)]
#[must_use]
pub struct ClientBuilder {
state: BuilderState,
}
impl ClientBuilder {
shared::builder_setters!();
pub fn build(self) -> Result<Client> {
let config = self.state.into_config()?;
let http = reqwest::blocking::Client::builder()
.user_agent(shared::USER_AGENT)
.default_headers(config.default_headers.clone())
.timeout(config.timeout)
.build()
.map_err(|e| Error::InvalidArgument(format!("could not build the HTTP client: {e}")))?;
Ok(Client {
http,
config: Arc::new(config),
})
}
}