1use super::{Backend, MAX_BACKEND_NAME_LEN};
2use crate::abi::fastly_http_req::register_dynamic_backend;
3use crate::experimental::GrpcBackend;
4use crate::secret_store::Secret;
5use fastly_shared::{FastlyStatus, SslVersion};
6use fastly_sys::{BackendConfigOptions, DynamicBackendConfig};
7use std::num::NonZeroU32;
8use std::time::Duration;
9use thiserror::Error;
10
11pub struct BackendBuilder {
17 name: String,
18 target: String,
19 host_override: Option<String>,
20 connect_timeout: Option<Duration>,
21 first_byte_timeout: Option<Duration>,
22 between_bytes_timeout: Option<Duration>,
23 use_ssl: bool,
24 min_tls_version: Option<SslVersion>,
25 max_tls_version: Option<SslVersion>,
26 cert_hostname: Option<String>,
27 ca_cert: Option<String>,
28 ciphers: Option<String>,
29 sni_hostname: Option<String>,
30 pool_connections: bool,
31 client_certificate_info: Option<(String, Secret)>,
32 grpc: bool,
33 http_keepalive_time_ms: Option<u32>,
34 tcp_keepalive_enable: Option<bool>,
35 tcp_keepalive_interval_secs: Option<NonZeroU32>,
36 tcp_keepalive_probes: Option<NonZeroU32>,
37 tcp_keepalive_time_secs: Option<NonZeroU32>,
38}
39
40#[derive(Clone, Debug, Error, PartialEq)]
45pub enum BackendCreationError {
46 #[error("Connect timeout too long; must be < 2^32 milliseconds")]
49 ConnectTimeoutTooLarge(Duration),
50 #[error("First byte timeout too long; must be < 2^32 milliseconds")]
53 FirstByteTimeoutTooLarge(Duration),
54 #[error("Between-byte timeout too long; must be < 2^32 milliseconds")]
57 BetweenBytesTimeoutTooLarge(Duration),
58 #[error("Dynamic backends not supported for this service")]
62 Disallowed,
63 #[error("Host failed with status {0:?}")]
68 HostError(FastlyStatus),
69 #[error(transparent)]
74 EncodingError(#[from] std::string::FromUtf8Error),
75 #[error("Provided backend name too long: {0}")]
78 NameTooLong(String),
79 #[error("The provided backend name is already in use")]
81 NameInUse,
82}
83
84impl From<FastlyStatus> for BackendCreationError {
85 fn from(x: FastlyStatus) -> Self {
86 match x {
87 FastlyStatus::UNSUPPORTED => BackendCreationError::Disallowed,
88 FastlyStatus::ERROR => BackendCreationError::NameInUse,
89 _ => BackendCreationError::HostError(x),
90 }
91 }
92}
93
94impl BackendBuilder {
95 #[doc = include_str!("../../docs/snippets/dynamic-backend-builder.md")]
96 pub fn new(name: impl ToString, target: impl ToString) -> Self {
97 BackendBuilder {
98 name: name.to_string(),
99 target: target.to_string(),
100 host_override: None,
101 connect_timeout: None,
102 first_byte_timeout: None,
103 between_bytes_timeout: None,
104 use_ssl: false,
106 min_tls_version: None,
107 max_tls_version: None,
108 cert_hostname: None,
109 ca_cert: None,
110 ciphers: None,
111 sni_hostname: None,
112 pool_connections: true,
113 client_certificate_info: None,
114 grpc: false,
115 http_keepalive_time_ms: None,
116 tcp_keepalive_enable: None,
117 tcp_keepalive_interval_secs: None,
118 tcp_keepalive_probes: None,
119 tcp_keepalive_time_secs: None,
120 }
121 }
122
123 pub fn override_host(mut self, name: impl ToString) -> Self {
132 self.host_override = Some(name.to_string());
133 self
134 }
135
136 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
138 self.connect_timeout = Some(timeout);
139 self
140 }
141
142 pub fn first_byte_timeout(mut self, timeout: Duration) -> Self {
145 self.first_byte_timeout = Some(timeout);
146 self
147 }
148
149 pub fn between_bytes_timeout(mut self, timeout: Duration) -> Self {
152 self.between_bytes_timeout = Some(timeout);
153 self
154 }
155
156 pub fn enable_ssl(mut self) -> Self {
172 self.use_ssl = true;
173 self
174 }
175
176 pub fn disable_ssl(mut self) -> Self {
178 self.use_ssl = false;
179 self
180 }
181
182 pub fn set_min_tls_version(mut self, minimum: SslVersion) -> Self {
185 self.use_ssl = true;
186 self.min_tls_version = Some(minimum);
187 self
188 }
189
190 pub fn set_max_tls_version(mut self, maximum: SslVersion) -> Self {
193 self.use_ssl = true;
194 self.max_tls_version = Some(maximum);
195 self
196 }
197
198 pub fn check_certificate(mut self, hostname: impl ToString) -> Self {
206 self.use_ssl = true;
207 self.cert_hostname = Some(hostname.to_string());
208 self
209 }
210
211 pub fn ca_certificate(mut self, value: impl ToString) -> Self {
218 self.use_ssl = true;
219 self.ca_cert = Some(value.to_string());
220 self
221 }
222
223 pub fn tls_ciphers(mut self, value: impl ToString) -> Self {
226 self.use_ssl = true;
227 self.ciphers = Some(value.to_string());
228 self
229 }
230
231 pub fn sni_hostname(mut self, value: impl ToString) -> Self {
234 self.use_ssl = true;
235 self.sni_hostname = Some(value.to_string());
236 self
237 }
238
239 pub fn provide_client_certificate(
252 mut self,
253 pem_certificate: impl ToString,
254 pem_key: Secret,
255 ) -> Self {
256 self.use_ssl = true;
257 self.client_certificate_info = Some((pem_certificate.to_string(), pem_key));
258 self
259 }
260
261 pub fn enable_pooling(mut self, value: bool) -> Self {
272 self.pool_connections = value;
273 self
274 }
275
276 pub fn http_keepalive_time(mut self, value: Duration) -> Self {
279 self.http_keepalive_time_ms = value.as_millis().try_into().ok();
280 self
281 }
282
283 pub fn tcp_keepalive_enable(mut self, value: bool) -> Self {
285 self.tcp_keepalive_enable = Some(value);
286 self
287 }
288
289 pub fn tcp_keepalive_interval_secs(mut self, value: NonZeroU32) -> Self {
291 self.tcp_keepalive_interval_secs = Some(value);
292 self
293 }
294
295 pub fn tcp_keepalive_probes(mut self, value: NonZeroU32) -> Self {
298 self.tcp_keepalive_probes = Some(value);
299 self
300 }
301
302 pub fn tcp_keepalive_time_secs(mut self, value: NonZeroU32) -> Self {
305 self.tcp_keepalive_time_secs = Some(value);
306 self
307 }
308
309 pub fn finish(self) -> Result<Backend, BackendCreationError> {
318 let name = self.name.as_ptr();
319 let name_len = self.name.len();
320
321 if name_len > MAX_BACKEND_NAME_LEN {
322 return Err(BackendCreationError::NameTooLong(self.name));
323 }
324
325 let target = self.target.as_ptr();
326 let target_len = self.target.len();
327
328 let mut config_options = BackendConfigOptions::empty();
331 let mut config = DynamicBackendConfig::default();
332
333 if let Some(host_override) = self.host_override.as_deref() {
334 config.host_override = host_override.as_ptr();
335 config.host_override_len = host_override.bytes().count() as u32;
336 config_options.insert(BackendConfigOptions::HOST_OVERRIDE);
337 }
338
339 if let Some(connect_timeout) = self.connect_timeout {
340 config.connect_timeout_ms = connect_timeout
341 .as_millis()
342 .try_into()
343 .map_err(|_| BackendCreationError::ConnectTimeoutTooLarge(connect_timeout))?;
344 config_options.insert(BackendConfigOptions::CONNECT_TIMEOUT);
345 }
346
347 if let Some(first_byte_timeout) = self.first_byte_timeout {
348 config.first_byte_timeout_ms = first_byte_timeout
349 .as_millis()
350 .try_into()
351 .map_err(|_| BackendCreationError::FirstByteTimeoutTooLarge(first_byte_timeout))?;
352 config_options.insert(BackendConfigOptions::FIRST_BYTE_TIMEOUT);
353 }
354
355 if let Some(between_bytes_timeout) = self.between_bytes_timeout {
356 config.between_bytes_timeout_ms =
357 between_bytes_timeout.as_millis().try_into().map_err(|_| {
358 BackendCreationError::BetweenBytesTimeoutTooLarge(between_bytes_timeout)
359 })?;
360 config_options.insert(BackendConfigOptions::BETWEEN_BYTES_TIMEOUT);
361 }
362
363 if self.use_ssl {
364 config_options.insert(BackendConfigOptions::USE_SSL);
365 }
366
367 if let Some(min_tls_version) = self.min_tls_version {
368 config.ssl_min_version = min_tls_version as u32;
369 config_options.insert(BackendConfigOptions::SSL_MIN_VERSION);
370 }
371
372 if let Some(max_tls_version) = self.max_tls_version {
373 config.ssl_max_version = max_tls_version as u32;
374 config_options.insert(BackendConfigOptions::SSL_MAX_VERSION);
375 }
376
377 if let Some(hostname) = self.cert_hostname.as_deref() {
378 config.cert_hostname = hostname.as_ptr();
379 config.cert_hostname_len = hostname.bytes().count() as u32;
380 config_options.insert(BackendConfigOptions::CERT_HOSTNAME);
381 }
382
383 if let Some(string) = self.ca_cert.as_deref() {
384 config.ca_cert = string.as_ptr();
385 config.ca_cert_len = string.bytes().count() as u32;
386 config_options.insert(BackendConfigOptions::CA_CERT);
387 }
388
389 if let Some(string) = self.ciphers.as_deref() {
390 config.ciphers = string.as_ptr();
391 config.ciphers_len = string.bytes().count() as u32;
392 config_options.insert(BackendConfigOptions::CIPHERS);
393 }
394
395 if let Some(string) = self.sni_hostname.as_deref() {
396 config.sni_hostname = string.as_ptr();
397 config.sni_hostname_len = string.bytes().count() as u32;
398 config_options.insert(BackendConfigOptions::SNI_HOSTNAME);
399 }
400
401 if let Some((pem_cert, pem_key)) = self.client_certificate_info.as_ref() {
402 config.client_certificate = pem_cert.as_ptr();
403 config.client_certificate_len = pem_cert.bytes().count() as u32;
404 config.client_key = pem_key.underlying_handle();
405 config_options.insert(BackendConfigOptions::CLIENT_CERT);
406 }
407
408 if let Some(time) = self.http_keepalive_time_ms {
409 config.http_keepalive_time_ms = time;
410 config_options.insert(BackendConfigOptions::KEEPALIVE);
411 }
412
413 if let Some(intvl) = self.tcp_keepalive_interval_secs {
414 config.tcp_keepalive_interval_secs = intvl.get();
415 config_options.insert(BackendConfigOptions::KEEPALIVE);
416 }
417
418 if let Some(probes) = self.tcp_keepalive_probes {
419 config.tcp_keepalive_probes = probes.get();
420 config_options.insert(BackendConfigOptions::KEEPALIVE);
421 }
422
423 if let Some(time) = self.tcp_keepalive_time_secs {
424 config.tcp_keepalive_time_secs = time.get();
425 config_options.insert(BackendConfigOptions::KEEPALIVE);
426 }
427
428 if let Some(enable) = self.tcp_keepalive_enable {
429 config.tcp_keepalive_enable = u32::from(enable);
430 config_options.insert(BackendConfigOptions::KEEPALIVE);
431 } else if config_options.contains(BackendConfigOptions::KEEPALIVE) {
432 config.tcp_keepalive_enable = u32::from(true);
435 }
436
437 if !self.pool_connections {
438 config_options.insert(BackendConfigOptions::DONT_POOL);
439 }
440
441 if self.grpc {
442 config_options.insert(BackendConfigOptions::GRPC);
443 }
444
445 let basic_result = unsafe {
446 register_dynamic_backend(name, name_len, target, target_len, config_options, &config)
447 };
448
449 match basic_result {
450 FastlyStatus::OK => Ok(Backend { name: self.name }),
451 _ => {
452 drop(self);
458 Err(BackendCreationError::from(basic_result))
459 }
460 }
461 }
462}
463
464impl GrpcBackend for BackendBuilder {
465 fn for_grpc(mut self, value: bool) -> Self {
466 self.grpc = value;
467 self
468 }
469}