use std::sync::Arc;
use http::header::USER_AGENT;
use http::header::{HeaderMap, HeaderValue};
use reqwest::{Client, ClientBuilder, Proxy};
use crate::auth::Credentials;
use crate::error::{self, Error, Result};
use crate::{TargetPlatform, TargetPortal};
use super::{ClientRef, Modio};
use super::{DEFAULT_AGENT, DEFAULT_HOST, TEST_HOST};
#[must_use]
pub struct Builder {
config: Config,
}
struct Config {
host: Option<String>,
credentials: Credentials,
builder: Option<ClientBuilder>,
headers: HeaderMap,
proxies: Vec<Proxy>,
#[cfg(feature = "__tls")]
tls: TlsBackend,
error: Option<Error>,
}
#[cfg(feature = "__tls")]
enum TlsBackend {
#[cfg(feature = "default-tls")]
Default,
#[cfg(feature = "rustls-tls")]
Rustls,
}
#[cfg(feature = "__tls")]
#[allow(clippy::derivable_impls)]
impl Default for TlsBackend {
fn default() -> TlsBackend {
#[cfg(feature = "default-tls")]
{
TlsBackend::Default
}
#[cfg(all(feature = "rustls-tls", not(feature = "default-tls")))]
{
TlsBackend::Rustls
}
}
}
impl Builder {
pub fn new<C: Into<Credentials>>(credentials: C) -> Builder {
Builder {
config: Config {
host: None,
credentials: credentials.into(),
builder: None,
headers: HeaderMap::new(),
proxies: Vec::new(),
#[cfg(feature = "__tls")]
tls: TlsBackend::default(),
error: None,
},
}
}
pub fn build(self) -> Result<Modio> {
let config = self.config;
if let Some(e) = config.error {
return Err(e);
}
let host = config.host.unwrap_or_else(|| DEFAULT_HOST.to_string());
let credentials = config.credentials;
let client = {
let mut builder = {
let builder = config.builder.unwrap_or_else(Client::builder);
#[cfg(feature = "__tls")]
match config.tls {
#[cfg(feature = "default-tls")]
TlsBackend::Default => builder.use_native_tls(),
#[cfg(feature = "rustls-tls")]
TlsBackend::Rustls => builder.use_rustls_tls(),
}
#[cfg(not(feature = "__tls"))]
builder
};
let mut headers = config.headers;
if !headers.contains_key(USER_AGENT) {
headers.insert(USER_AGENT, HeaderValue::from_static(DEFAULT_AGENT));
}
for proxy in config.proxies {
builder = builder.proxy(proxy);
}
builder
.default_headers(headers)
.build()
.map_err(error::builder)?
};
Ok(Modio {
inner: Arc::new(ClientRef {
host,
client,
credentials,
}),
})
}
pub fn client<F>(mut self, f: F) -> Builder
where
F: FnOnce(ClientBuilder) -> ClientBuilder,
{
self.config.builder = Some(f(Client::builder()));
self
}
pub fn host<S: Into<String>>(mut self, host: S) -> Builder {
self.config.host = Some(host.into());
self
}
pub fn use_test(mut self) -> Builder {
self.config.host = Some(TEST_HOST.into());
self
}
pub fn user_agent<V>(mut self, value: V) -> Builder
where
V: TryInto<HeaderValue>,
V::Error: Into<http::Error>,
{
match value.try_into() {
Ok(value) => {
self.config.headers.insert(USER_AGENT, value);
}
Err(e) => {
self.config.error = Some(error::builder(e.into()));
}
}
self
}
pub fn proxy(mut self, proxy: Proxy) -> Builder {
self.config.proxies.push(proxy);
self
}
pub fn target_platform(mut self, platform: TargetPlatform) -> Builder {
match HeaderValue::from_str(platform.as_str()) {
Ok(value) => {
self.config.headers.insert("X-Modio-Platform", value);
}
Err(e) => {
self.config.error = Some(error::builder(e));
}
}
self
}
pub fn target_portal(mut self, portal: TargetPortal) -> Builder {
match HeaderValue::from_str(portal.as_str()) {
Ok(value) => {
self.config.headers.insert("X-Modio-Portal", value);
}
Err(e) => {
self.config.error = Some(error::builder(e));
}
}
self
}
#[cfg(feature = "default-tls")]
pub fn use_default_tls(mut self) -> Builder {
self.config.tls = TlsBackend::Default;
self
}
#[cfg(feature = "rustls-tls")]
pub fn use_rustls_tls(mut self) -> Builder {
self.config.tls = TlsBackend::Rustls;
self
}
}