use super::{Backend, MAX_BACKEND_NAME_LEN};
use crate::abi::fastly_http_req::register_dynamic_backend;
use fastly_shared::{FastlyStatus, SslVersion};
use fastly_sys::{BackendConfigOptions, DynamicBackendConfig};
use std::time::Duration;
use thiserror::Error;
pub struct BackendBuilder {
name: String,
target: String,
host_override: Option<String>,
connect_timeout: Option<Duration>,
first_byte_timeout: Option<Duration>,
between_bytes_timeout: Option<Duration>,
use_ssl: bool,
min_tls_version: Option<SslVersion>,
max_tls_version: Option<SslVersion>,
cert_hostname: Option<String>,
ca_cert: Option<String>,
ciphers: Option<String>,
sni_hostname: Option<String>,
}
#[derive(Clone, Debug, Error, PartialEq)]
pub enum BackendCreationError {
#[error("Connect timeout too long; must be < 2^32 milliseconds")]
ConnectTimeoutTooLarge(Duration),
#[error("First byte timeout too long; must be < 2^32 milliseconds")]
FirstByteTimeoutTooLarge(Duration),
#[error("Between-byte timeout too long; must be < 2^32 milliseconds")]
BetweenBytesTimeoutTooLarge(Duration),
#[error("Dynamic backends not supported for this service")]
Disallowed,
#[error("Host failed with status {0:?}")]
HostError(FastlyStatus),
#[error(transparent)]
EncodingError(#[from] std::string::FromUtf8Error),
#[error("Provided backend name too long: {0}")]
NameTooLong(String),
#[error("The provided backend name is already in use")]
NameInUse,
}
impl From<FastlyStatus> for BackendCreationError {
fn from(x: FastlyStatus) -> Self {
match x {
FastlyStatus::UNSUPPORTED => BackendCreationError::Disallowed,
FastlyStatus::ERROR => BackendCreationError::NameInUse,
_ => BackendCreationError::HostError(x),
}
}
}
impl BackendBuilder {
#[doc = include_str!("../../docs/snippets/dynamic-backend-builder.md")]
pub fn new(name: String, target: String) -> Self {
BackendBuilder {
name,
target,
host_override: None,
connect_timeout: None,
first_byte_timeout: None,
between_bytes_timeout: None,
use_ssl: false,
min_tls_version: None,
max_tls_version: None,
cert_hostname: None,
ca_cert: None,
ciphers: None,
sni_hostname: None,
}
}
pub fn override_host(mut self, name: &str) -> Self {
self.host_override = Some(name.to_owned());
self
}
pub fn connect_timeout(mut self, timeout: Duration) -> Self {
self.connect_timeout = Some(timeout);
self
}
pub fn first_byte_timeout(mut self, timeout: Duration) -> Self {
self.first_byte_timeout = Some(timeout);
self
}
pub fn between_bytes_timeout(mut self, timeout: Duration) -> Self {
self.between_bytes_timeout = Some(timeout);
self
}
pub fn enable_ssl(mut self) -> Self {
self.use_ssl = true;
self
}
pub fn disable_ssl(mut self) -> Self {
self.use_ssl = false;
self
}
pub fn set_min_tls_version(mut self, minimum: SslVersion) -> Self {
self.use_ssl = true;
self.min_tls_version = Some(minimum);
self
}
pub fn set_max_tls_version(mut self, maximum: SslVersion) -> Self {
self.use_ssl = true;
self.max_tls_version = Some(maximum);
self
}
pub fn check_certificate(mut self, hostname: impl ToString) -> Self {
self.use_ssl = true;
self.cert_hostname = Some(hostname.to_string());
self
}
pub fn ca_certificate(mut self, value: impl ToString) -> Self {
self.use_ssl = true;
self.ca_cert = Some(value.to_string());
self
}
pub fn tls_ciphers(mut self, value: impl ToString) -> Self {
self.use_ssl = true;
self.ciphers = Some(value.to_string());
self
}
pub fn sni_hostname(mut self, value: impl ToString) -> Self {
self.use_ssl = true;
self.sni_hostname = Some(value.to_string());
self
}
pub fn finish(self) -> Result<Backend, BackendCreationError> {
let name = self.name.as_ptr();
let name_len = self.name.len();
if name_len > MAX_BACKEND_NAME_LEN {
return Err(BackendCreationError::NameTooLong(self.name));
}
let target = self.target.as_ptr();
let target_len = self.target.len();
let mut config_options = BackendConfigOptions::empty();
let mut config = DynamicBackendConfig::default();
if let Some(host_override) = self.host_override.as_deref() {
config.host_override = host_override.as_ptr();
config.host_override_len = host_override.bytes().count() as u32;
config_options.insert(BackendConfigOptions::HOST_OVERRIDE);
}
if let Some(connect_timeout) = self.connect_timeout {
config.connect_timeout_ms = connect_timeout
.as_millis()
.try_into()
.map_err(|_| BackendCreationError::ConnectTimeoutTooLarge(connect_timeout))?;
config_options.insert(BackendConfigOptions::CONNECT_TIMEOUT);
}
if let Some(first_byte_timeout) = self.first_byte_timeout {
config.first_byte_timeout_ms = first_byte_timeout
.as_millis()
.try_into()
.map_err(|_| BackendCreationError::FirstByteTimeoutTooLarge(first_byte_timeout))?;
config_options.insert(BackendConfigOptions::FIRST_BYTE_TIMEOUT);
}
if let Some(between_bytes_timeout) = self.between_bytes_timeout {
config.between_bytes_timeout_ms =
between_bytes_timeout.as_millis().try_into().map_err(|_| {
BackendCreationError::BetweenBytesTimeoutTooLarge(between_bytes_timeout)
})?;
config_options.insert(BackendConfigOptions::BETWEEN_BYTES_TIMEOUT);
}
if self.use_ssl {
config_options.insert(BackendConfigOptions::USE_SSL);
}
if let Some(min_tls_version) = self.min_tls_version {
config.ssl_min_version = min_tls_version as u32;
config_options.insert(BackendConfigOptions::SSL_MIN_VERSION);
}
if let Some(max_tls_version) = self.max_tls_version {
config.ssl_max_version = max_tls_version as u32;
config_options.insert(BackendConfigOptions::SSL_MAX_VERSION);
}
if let Some(hostname) = self.cert_hostname.as_deref() {
config.cert_hostname = hostname.as_ptr();
config.cert_hostname_len = hostname.bytes().count() as u32;
config_options.insert(BackendConfigOptions::CERT_HOSTNAME);
}
if let Some(string) = self.ca_cert.as_deref() {
config.ca_cert = string.as_ptr();
config.ca_cert_len = string.bytes().count() as u32;
config_options.insert(BackendConfigOptions::CA_CERT);
}
if let Some(string) = self.ciphers.as_deref() {
config.ciphers = string.as_ptr();
config.ciphers_len = string.bytes().count() as u32;
config_options.insert(BackendConfigOptions::CIPHERS);
}
if let Some(string) = self.sni_hostname.as_deref() {
config.sni_hostname = string.as_ptr();
config.sni_hostname_len = string.bytes().count() as u32;
config_options.insert(BackendConfigOptions::SNI_HOSTNAME);
}
let basic_result = unsafe {
register_dynamic_backend(name, name_len, target, target_len, config_options, &config)
};
match basic_result {
FastlyStatus::OK => Ok(Backend { name: self.name }),
_ => Err(BackendCreationError::from(basic_result)),
}
}
}