use std::time::Duration;
use reqwest::Certificate;
use crate::error::{Result, TreetopError};
use crate::token::UploadToken;
use crate::types::RequestLimits;
use super::capability::{CanUpload, ReadOnly};
use super::inner::{
BaseUrl, Client, ClientState, CorrelationId, RequestSizeLimit, ResponseSizeLimit,
};
const DEFAULT_MAX_REQUEST_BYTES: usize = 16 * 1024 * 1024;
const DEFAULT_MAX_RESPONSE_BYTES: usize = 16 * 1024 * 1024;
pub struct ClientBuilder<Capability = ReadOnly> {
base_url: String,
connect_timeout: Duration,
request_timeout: Duration,
pool_idle_timeout: Option<Duration>,
pool_max_idle_per_host: Option<usize>,
capability: Capability,
correlation_id: Option<String>,
danger_accept_invalid_certs: bool,
root_certificates: Vec<Certificate>,
custom_client: Option<reqwest::Client>,
max_request_bytes: usize,
max_response_bytes: usize,
request_limits: RequestLimits,
danger_allow_insecure_uploads: bool,
}
impl ClientBuilder<ReadOnly> {
pub fn new(base_url: impl Into<String>) -> Self {
Self {
base_url: base_url.into(),
connect_timeout: Duration::from_secs(5),
request_timeout: Duration::from_secs(30),
pool_idle_timeout: Some(Duration::from_secs(90)),
pool_max_idle_per_host: None,
capability: ReadOnly,
correlation_id: None,
danger_accept_invalid_certs: false,
root_certificates: Vec::new(),
custom_client: None,
max_request_bytes: DEFAULT_MAX_REQUEST_BYTES,
max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES,
request_limits: RequestLimits::default(),
danger_allow_insecure_uploads: false,
}
}
pub fn upload_token(self, token: UploadToken) -> ClientBuilder<CanUpload> {
let Self {
base_url,
connect_timeout,
request_timeout,
pool_idle_timeout,
pool_max_idle_per_host,
capability: _,
correlation_id,
danger_accept_invalid_certs,
root_certificates,
custom_client,
max_request_bytes,
max_response_bytes,
request_limits,
danger_allow_insecure_uploads,
} = self;
ClientBuilder {
base_url,
connect_timeout,
request_timeout,
pool_idle_timeout,
pool_max_idle_per_host,
capability: CanUpload::new(token),
correlation_id,
danger_accept_invalid_certs,
root_certificates,
custom_client,
max_request_bytes,
max_response_bytes,
request_limits,
danger_allow_insecure_uploads,
}
}
}
impl<Capability> ClientBuilder<Capability> {
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = timeout;
self
}
pub fn request_timeout(mut self, timeout: Duration) -> Self {
self.request_timeout = timeout;
self
}
pub fn pool_idle_timeout(mut self, timeout: Duration) -> Self {
self.pool_idle_timeout = Some(timeout);
self
}
pub fn pool_max_idle_per_host(mut self, n: usize) -> Self {
self.pool_max_idle_per_host = Some(n);
self
}
pub fn correlation_id(mut self, id: impl Into<String>) -> Self {
self.correlation_id = Some(id.into());
self
}
pub fn danger_accept_invalid_certs(mut self, accept: bool) -> Self {
self.danger_accept_invalid_certs = accept;
self
}
pub fn add_root_certificate(mut self, cert: Certificate) -> Self {
self.root_certificates.push(cert);
self
}
pub fn with_reqwest_client(mut self, client: reqwest::Client) -> Self {
self.custom_client = Some(client);
self
}
pub fn max_response_bytes(mut self, max_response_bytes: usize) -> Self {
self.max_response_bytes = max_response_bytes;
self
}
pub fn max_request_bytes(mut self, max_request_bytes: usize) -> Self {
self.max_request_bytes = max_request_bytes;
self
}
pub fn request_limits(mut self, request_limits: RequestLimits) -> Self {
self.request_limits = request_limits;
self
}
fn build_state(
self,
validate_upload_transport: bool,
upload_redactor: Option<CanUpload>,
) -> Result<(ClientState, Option<CorrelationId>, Capability)> {
let base_url = BaseUrl::parse(&self.base_url)?;
if validate_upload_transport {
base_url.validate_upload_transport(self.danger_allow_insecure_uploads)?;
}
let correlation_id = self.correlation_id.map(CorrelationId::parse).transpose()?;
let max_request_bytes = RequestSizeLimit::new(self.max_request_bytes)?;
let max_response_bytes = ResponseSizeLimit::new(self.max_response_bytes)?;
let http = if let Some(client) = self.custom_client {
client
} else {
let mut builder = reqwest::Client::builder()
.connect_timeout(self.connect_timeout)
.timeout(self.request_timeout)
.danger_accept_invalid_certs(self.danger_accept_invalid_certs)
.redirect(reqwest::redirect::Policy::none());
if let Some(idle_timeout) = self.pool_idle_timeout {
builder = builder.pool_idle_timeout(idle_timeout);
}
if let Some(max_idle) = self.pool_max_idle_per_host {
builder = builder.pool_max_idle_per_host(max_idle);
}
for cert in self.root_certificates {
builder = builder.add_root_certificate(cert);
}
builder.build().map_err(TreetopError::Transport)?
};
Ok((
ClientState::new(
http,
base_url,
max_request_bytes,
max_response_bytes,
self.request_limits,
upload_redactor,
),
correlation_id,
self.capability,
))
}
}
impl ClientBuilder<ReadOnly> {
pub fn build(self) -> Result<Client<ReadOnly>> {
let (state, correlation_id, capability) = self.build_state(false, None)?;
Ok(Client::new(state, correlation_id, capability))
}
}
impl ClientBuilder<CanUpload> {
pub fn danger_allow_insecure_uploads(mut self, allow: bool) -> Self {
self.danger_allow_insecure_uploads = allow;
self
}
pub fn build(self) -> Result<Client<CanUpload>> {
let upload_redactor = self.capability.clone();
let (state, correlation_id, capability) = self.build_state(true, Some(upload_redactor))?;
Ok(Client::new(state, correlation_id, capability))
}
}