#![warn(missing_docs, clippy::all)]
#![allow(clippy::derive_partial_eq_without_eq)]
use serde::de::{Deserializer, Error as _, Unexpected};
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt;
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
#[cfg(test)]
mod test;
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct ServicesConfig {
services: HashMap<String, ServiceConfig>,
security: Option<SecurityConfig>,
proxy: Option<ProxyConfig>,
#[serde(deserialize_with = "de_opt_duration")]
connect_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
read_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
write_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
backoff_slot_size: Option<Duration>,
}
impl ServicesConfig {
pub fn builder() -> ServicesConfigBuilder {
ServicesConfigBuilder::default()
}
pub fn merged_service(&self, name: &str) -> Option<ServiceConfig> {
let mut service = self.services.get(name).cloned()?;
if service.security.is_none() {
service.security = self.security.clone();
}
if service.proxy.is_none() {
service.proxy = self.proxy.clone();
}
if service.connect_timeout.is_none() {
service.connect_timeout = self.connect_timeout;
}
if service.read_timeout.is_none() {
service.read_timeout = self.read_timeout;
}
if service.write_timeout.is_none() {
service.write_timeout = self.write_timeout;
}
if service.backoff_slot_size.is_none() {
service.backoff_slot_size = self.backoff_slot_size;
}
Some(service)
}
pub fn security(&self) -> Option<&SecurityConfig> {
self.security.as_ref()
}
pub fn proxy(&self) -> Option<&ProxyConfig> {
self.proxy.as_ref()
}
pub fn connect_timeout(&self) -> Option<Duration> {
self.connect_timeout
}
pub fn read_timeout(&self) -> Option<Duration> {
self.read_timeout
}
pub fn write_timeout(&self) -> Option<Duration> {
self.write_timeout
}
pub fn backoff_slot_size(&self) -> Option<Duration> {
self.backoff_slot_size
}
}
#[derive(Default)]
pub struct ServicesConfigBuilder(ServicesConfig);
impl From<ServicesConfig> for ServicesConfigBuilder {
fn from(config: ServicesConfig) -> ServicesConfigBuilder {
ServicesConfigBuilder(config)
}
}
impl ServicesConfigBuilder {
pub fn service(&mut self, name: &str, config: ServiceConfig) -> &mut Self {
self.0.services.insert(name.to_string(), config);
self
}
pub fn security(&mut self, security: SecurityConfig) -> &mut Self {
self.0.security = Some(security);
self
}
pub fn proxy(&mut self, proxy: ProxyConfig) -> &mut Self {
self.0.proxy = Some(proxy);
self
}
pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Self {
self.0.connect_timeout = Some(connect_timeout);
self
}
pub fn read_timeout(&mut self, read_timeout: Duration) -> &mut Self {
self.0.read_timeout = Some(read_timeout);
self
}
pub fn write_timeout(&mut self, write_timeout: Duration) -> &mut Self {
self.0.write_timeout = Some(write_timeout);
self
}
pub fn backoff_slot_size(&mut self, backoff_slot_size: Duration) -> &mut Self {
self.0.backoff_slot_size = Some(backoff_slot_size);
self
}
pub fn build(&self) -> ServicesConfig {
self.0.clone()
}
}
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", default)]
pub struct ServiceConfig {
uris: Vec<Url>,
security: Option<SecurityConfig>,
proxy: Option<ProxyConfig>,
#[serde(deserialize_with = "de_opt_duration")]
connect_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
read_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
write_timeout: Option<Duration>,
#[serde(deserialize_with = "de_opt_duration")]
backoff_slot_size: Option<Duration>,
max_num_retries: Option<u32>,
}
impl ServiceConfig {
pub fn builder() -> ServiceConfigBuilder {
ServiceConfigBuilder::default()
}
pub fn uris(&self) -> &[Url] {
&self.uris
}
pub fn security(&self) -> Option<&SecurityConfig> {
self.security.as_ref()
}
pub fn proxy(&self) -> Option<&ProxyConfig> {
self.proxy.as_ref()
}
pub fn connect_timeout(&self) -> Option<Duration> {
self.connect_timeout
}
pub fn read_timeout(&self) -> Option<Duration> {
self.read_timeout
}
pub fn write_timeout(&self) -> Option<Duration> {
self.write_timeout
}
pub fn backoff_slot_size(&self) -> Option<Duration> {
self.backoff_slot_size
}
pub fn max_num_retries(&self) -> Option<u32> {
self.max_num_retries
}
}
#[derive(Default)]
pub struct ServiceConfigBuilder(ServiceConfig);
impl From<ServiceConfig> for ServiceConfigBuilder {
fn from(config: ServiceConfig) -> ServiceConfigBuilder {
ServiceConfigBuilder(config)
}
}
impl ServiceConfigBuilder {
pub fn uri(&mut self, uri: Url) -> &mut Self {
self.0.uris.push(uri);
self
}
pub fn uris(&mut self, uris: Vec<Url>) -> &mut Self {
self.0.uris = uris;
self
}
pub fn security(&mut self, security: SecurityConfig) -> &mut Self {
self.0.security = Some(security);
self
}
pub fn proxy(&mut self, proxy: ProxyConfig) -> &mut Self {
self.0.proxy = Some(proxy);
self
}
pub fn connect_timeout(&mut self, connect_timeout: Duration) -> &mut Self {
self.0.connect_timeout = Some(connect_timeout);
self
}
pub fn read_timeout(&mut self, read_timeout: Duration) -> &mut Self {
self.0.read_timeout = Some(read_timeout);
self
}
pub fn write_timeout(&mut self, write_timeout: Duration) -> &mut Self {
self.0.write_timeout = Some(write_timeout);
self
}
pub fn backoff_slot_size(&mut self, backoff_slot_size: Duration) -> &mut Self {
self.0.backoff_slot_size = Some(backoff_slot_size);
self
}
pub fn max_num_retries(&mut self, max_num_retries: u32) -> &mut Self {
self.0.max_num_retries = Some(max_num_retries);
self
}
pub fn build(&self) -> ServiceConfig {
self.0.clone()
}
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct SecurityConfig {
ca_file: Option<PathBuf>,
key_file: Option<PathBuf>,
cert_file: Option<PathBuf>,
}
impl SecurityConfig {
pub fn builder() -> SecurityConfigBuilder {
SecurityConfigBuilder::default()
}
pub fn ca_file(&self) -> Option<&Path> {
self.ca_file.as_deref()
}
pub fn key_file(&self) -> Option<&Path> {
self.key_file.as_deref()
}
pub fn cert_file(&self) -> Option<&Path> {
self.cert_file.as_deref()
}
}
#[derive(Default)]
pub struct SecurityConfigBuilder(SecurityConfig);
impl From<SecurityConfig> for SecurityConfigBuilder {
fn from(config: SecurityConfig) -> SecurityConfigBuilder {
SecurityConfigBuilder(config)
}
}
impl SecurityConfigBuilder {
pub fn ca_file(&mut self, ca_file: Option<PathBuf>) -> &mut Self {
self.0.ca_file = ca_file;
self
}
pub fn key_file(&mut self, key_file: Option<PathBuf>) -> &mut Self {
self.0.key_file = key_file;
self
}
pub fn cert_file(&mut self, cert_file: Option<PathBuf>) -> &mut Self {
self.0.cert_file = cert_file;
self
}
pub fn build(&self) -> SecurityConfig {
self.0.clone()
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case", tag = "type")]
#[non_exhaustive]
pub enum ProxyConfig {
Direct,
Http(HttpProxyConfig),
}
#[allow(clippy::derivable_impls)]
impl Default for ProxyConfig {
fn default() -> ProxyConfig {
ProxyConfig::Direct
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct HttpProxyConfig {
host_and_port: HostAndPort,
credentials: Option<BasicCredentials>,
}
impl HttpProxyConfig {
pub fn builder() -> HttpProxyConfigBuilder {
HttpProxyConfigBuilder::default()
}
pub fn host_and_port(&self) -> &HostAndPort {
&self.host_and_port
}
pub fn credentials(&self) -> Option<&BasicCredentials> {
self.credentials.as_ref()
}
}
#[derive(Default)]
pub struct HttpProxyConfigBuilder {
host_and_port: Option<HostAndPort>,
credentials: Option<BasicCredentials>,
}
impl From<HttpProxyConfig> for HttpProxyConfigBuilder {
fn from(config: HttpProxyConfig) -> HttpProxyConfigBuilder {
HttpProxyConfigBuilder {
host_and_port: Some(config.host_and_port),
credentials: config.credentials,
}
}
}
impl HttpProxyConfigBuilder {
pub fn host_and_port(&mut self, host_and_port: HostAndPort) -> &mut Self {
self.host_and_port = Some(host_and_port);
self
}
pub fn credentials(&mut self, credentials: Option<BasicCredentials>) -> &mut Self {
self.credentials = credentials;
self
}
pub fn build(&self) -> HttpProxyConfig {
HttpProxyConfig {
host_and_port: self.host_and_port.clone().expect("host_and_port not set"),
credentials: self.credentials.clone(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct HostAndPort {
host: String,
port: u16,
}
impl fmt::Display for HostAndPort {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(fmt, "{}:{}", self.host, self.port)
}
}
impl<'de> Deserialize<'de> for HostAndPort {
fn deserialize<D>(deserializer: D) -> Result<HostAndPort, D::Error>
where
D: Deserializer<'de>,
{
let expected = "a host:port identifier";
let mut s = String::deserialize(deserializer)?;
match s.find(':') {
Some(idx) => {
let port = s[idx + 1..]
.parse()
.map_err(|_| D::Error::invalid_value(Unexpected::Str(&s), &expected))?;
s.truncate(idx);
Ok(HostAndPort { host: s, port })
}
None => Err(D::Error::invalid_value(Unexpected::Str(&s), &expected)),
}
}
}
impl HostAndPort {
pub fn new(host: &str, port: u16) -> HostAndPort {
HostAndPort {
host: host.to_string(),
port,
}
}
pub fn host(&self) -> &str {
&self.host
}
pub fn port(&self) -> u16 {
self.port
}
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct BasicCredentials {
username: String,
password: String,
}
impl BasicCredentials {
pub fn new(username: &str, password: &str) -> BasicCredentials {
BasicCredentials {
username: username.to_string(),
password: password.to_string(),
}
}
pub fn username(&self) -> &str {
&self.username
}
pub fn password(&self) -> &str {
&self.password
}
}
fn de_opt_duration<'de, D>(d: D) -> Result<Option<Duration>, D::Error>
where
D: Deserializer<'de>,
{
humantime_serde::Serde::deserialize(d).map(humantime_serde::Serde::into_inner)
}