use std::{collections::BTreeMap, marker::PhantomData, time::Duration};
use base64::Engine;
use http::Uri;
const CONNECT_TIMEOUT: Duration = Duration::from_secs(10);
pub struct IppClientBuilder<T> {
uri: Uri,
ignore_tls_errors: bool,
request_timeout: Option<Duration>,
headers: BTreeMap<String, String>,
_phantom_data: PhantomData<T>,
}
impl<T> IppClientBuilder<T> {
fn new(uri: Uri) -> Self {
IppClientBuilder {
uri,
ignore_tls_errors: false,
request_timeout: None,
headers: BTreeMap::new(),
_phantom_data: PhantomData::default(),
}
}
pub fn ignore_tls_errors(mut self, flag: bool) -> Self {
self.ignore_tls_errors = flag;
self
}
pub fn request_timeout(mut self, duration: Duration) -> Self {
self.request_timeout = Some(duration);
self
}
pub fn http_header<K, V>(mut self, key: K, value: V) -> Self
where
K: AsRef<str>,
V: AsRef<str>,
{
self.headers.insert(key.as_ref().to_owned(), value.as_ref().to_owned());
self
}
pub fn basic_auth<U, P>(mut self, username: U, password: P) -> Self
where
U: AsRef<str>,
P: AsRef<str>,
{
let authz =
base64::engine::general_purpose::STANDARD.encode(format!("{}:{}", username.as_ref(), password.as_ref()));
self.headers
.insert("authorization".to_owned(), format!("Basic {}", authz));
self
}
}
#[cfg(feature = "async-client")]
impl IppClientBuilder<non_blocking::AsyncIppClient> {
pub fn build(self) -> non_blocking::AsyncIppClient {
non_blocking::AsyncIppClient(self)
}
}
#[cfg(feature = "client")]
impl IppClientBuilder<blocking::IppClient> {
pub fn build(self) -> blocking::IppClient {
blocking::IppClient(self)
}
}
#[cfg(feature = "async-client")]
pub mod non_blocking {
use std::io;
use futures_util::{io::BufReader, stream::TryStreamExt};
use http::Uri;
use reqwest::{Body, ClientBuilder};
use tokio_util::compat::FuturesAsyncReadCompatExt;
use crate::{error::IppError, parser::AsyncIppParser, request::IppRequestResponse};
use super::{IppClientBuilder, CONNECT_TIMEOUT};
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"), ";reqwest");
pub struct AsyncIppClient(pub(super) IppClientBuilder<Self>);
impl AsyncIppClient {
pub fn new(uri: Uri) -> Self {
AsyncIppClient(AsyncIppClient::builder(uri))
}
pub fn builder(uri: Uri) -> IppClientBuilder<Self> {
IppClientBuilder::new(uri)
}
pub fn uri(&self) -> &Uri {
&self.0.uri
}
pub async fn send<R>(&self, request: R) -> Result<IppRequestResponse, IppError>
where
R: Into<IppRequestResponse>,
{
let mut builder = ClientBuilder::new().connect_timeout(CONNECT_TIMEOUT);
if let Some(timeout) = self.0.request_timeout {
builder = builder.timeout(timeout);
}
#[cfg(feature = "async-client-tls")]
if self.0.ignore_tls_errors {
builder = builder
.danger_accept_invalid_hostnames(true)
.danger_accept_invalid_certs(true);
}
let mut req_builder = builder.user_agent(USER_AGENT).build()?.post(self.0.uri.to_string());
for (k, v) in &self.0.headers {
req_builder = req_builder.header(k, v);
}
let response = req_builder
.header("content-type", "application/ipp")
.body(Body::wrap_stream(tokio_util::io::ReaderStream::new(
request.into().into_async_read().compat(),
)))
.send()
.await?;
if response.status().is_success() {
let parser = AsyncIppParser::new(BufReader::new(
response
.bytes_stream()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
.into_async_read(),
));
parser.parse().await.map_err(IppError::from)
} else {
Err(IppError::RequestError(response.status().as_u16()))
}
}
}
}
#[cfg(feature = "client")]
pub mod blocking {
use http::Uri;
use ureq::AgentBuilder;
use crate::{error::IppError, parser::IppParser, reader::IppReader, request::IppRequestResponse};
use super::{IppClientBuilder, CONNECT_TIMEOUT};
const USER_AGENT: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"), ";ureq");
pub struct IppClient(pub(super) IppClientBuilder<Self>);
impl IppClient {
pub fn new(uri: Uri) -> Self {
IppClient(IppClient::builder(uri))
}
pub fn builder(uri: Uri) -> IppClientBuilder<Self> {
IppClientBuilder::new(uri)
}
pub fn uri(&self) -> &Uri {
&self.0.uri
}
pub fn send<R>(&self, request: R) -> Result<IppRequestResponse, IppError>
where
R: Into<IppRequestResponse>,
{
let mut builder = AgentBuilder::new().timeout_connect(CONNECT_TIMEOUT);
if let Some(timeout) = self.0.request_timeout {
builder = builder.timeout(timeout);
}
#[cfg(feature = "client-tls")]
{
let tls_connector = native_tls::TlsConnector::builder()
.danger_accept_invalid_hostnames(self.0.ignore_tls_errors)
.danger_accept_invalid_certs(self.0.ignore_tls_errors)
.build()?;
builder = builder.tls_connector(std::sync::Arc::new(tls_connector));
}
let agent = builder.user_agent(USER_AGENT).build();
let mut req = agent
.post(&self.0.uri.to_string())
.set("content-type", "application/ipp");
for (k, v) in &self.0.headers {
req = req.set(k, v);
}
let response = req.send(request.into().into_read())?;
let reader = response.into_reader();
let parser = IppParser::new(IppReader::new(reader));
parser.parse().map_err(IppError::from)
}
}
}