fastly 0.13.1

Fastly Compute API
Documentation
//! Support for dynamic backend healthchecks.
//!
//! Adds support for healthchecking on dynamic backends, much as there is
//! the existing support for healthchecking for static backends. The main difference
//! being that with dynamic backends, there is a rate limit applied to the number
//! of healthchecks per sid you can create to prevent ill-use. A rate-limited
//! healthcheck will return an error on healthcheck() or on dynamic backend creation.

#[allow(unused_imports)]
use crate::experimental::BackendCreationError;
use crate::Backend;
use http::uri::PathAndQuery;
use std::time::Duration;

/// A builder structure for generating healthchecks for a dynamic backend
///
/// This structure can be constructed using either [`Backend::healthcheck_builder()`][Backend::healthcheck_builder()] or its
/// own [`new()`][Self::new()] method, and will generate a new healthcheck for use by the `DynamicBackendBuilder` after
/// consuming the `BackendBuilder` with `finish()`.
#[derive(Clone)]
pub struct HealthcheckBuilder {
    host: String,
    method: String,
    path: PathAndQuery,
    expected_status: u16,
    window: u32,
    threshold: u32,
    initial: u32,
    interval_ms: Duration,
    timeout_ms: Duration,
}

#[cfg(target_env = "p1")]
const URL_SIZE_LIMIT: usize = 8192;
#[cfg(target_env = "p1")]
const METHOD_SIZE_LIMIT: usize = 8192;
#[cfg(target_env = "p1")]
const WINDOW_LIMIT: u32 = 15; // May need to be increased
#[cfg(target_env = "p1")]
const INTERVAL_MAX_MS: u128 = 3600000; // 1 hour
#[cfg(target_env = "p1")]
const INTERVAL_MIN_MS: u128 = 1000; // 1 second
#[cfg(target_env = "p1")]
const TIMEOUT_MAX_MS: u128 = 3600000; // 1 hour
#[cfg(target_env = "p1")]
const TIMEOUT_MIN_MS: u128 = 1000; // 1 second

impl HealthcheckBuilder {
    #[doc = include_str!("../../docs/snippets/healthcheck-builder.md")]
    pub fn new(host: impl ToString) -> Self {
        HealthcheckBuilder {
            host: host.to_string(),
            method: "GET".to_string(),
            path: PathAndQuery::from_static("/"),
            expected_status: 200,
            window: 5,
            threshold: 3,
            initial: 4,
            interval_ms: Duration::from_millis(15000),
            timeout_ms: Duration::from_millis(5000),
        }
    }

    /// Validate that the current health check has acceptable values
    #[cfg(target_env = "p1")]
    pub fn validate(&self) -> Result<(), BackendCreationError> {
        let host_size: usize = self.host.len();
        let path_size: usize = self.path.path().len();
        if self.host.is_empty() {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "host:{} is empty",
                self.host
            )));
        }
        if (host_size + path_size) > URL_SIZE_LIMIT {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "host:{} or path:{} is too large",
                self.host,
                self.path.path()
            )));
        }

        let method_size: usize = self.method.len();
        if method_size > METHOD_SIZE_LIMIT {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "method:{} is too large",
                self.method
            )));
        }

        if self.window > WINDOW_LIMIT {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "window:{} is too large",
                self.window
            )));
        }

        if self.window < self.threshold {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "threshold:{} is greater than window:{}",
                self.threshold, self.window
            )));
        }
        if self.window < self.initial {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "initial:{} is greater than window:{}",
                self.initial, self.window
            )));
        }

        if !(INTERVAL_MIN_MS..INTERVAL_MAX_MS).contains(&self.interval_ms.as_millis()) {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "interval_ms:{} is not within {}, {}",
                self.interval_ms.as_millis(),
                INTERVAL_MIN_MS,
                INTERVAL_MAX_MS
            )));
        }

        if !(TIMEOUT_MIN_MS..TIMEOUT_MAX_MS).contains(&self.timeout_ms.as_millis()) {
            return Err(BackendCreationError::InvalidHealthcheckValue(format!(
                "timeout_ms:{} is not within {}, {}",
                self.timeout_ms.as_millis(),
                TIMEOUT_MIN_MS,
                TIMEOUT_MAX_MS
            )));
        }

        Ok(())
    }

    /// Returns the host from the HealthcheckBuilder
    pub fn get_host(&self) -> &str {
        self.host.as_str()
    }

    /// Returns the method from the HealthcheckBuilder
    pub fn get_method(&self) -> &str {
        self.method.as_str()
    }

    /// Returns the path from the HealthcheckBuilder
    pub fn get_path(&self) -> &PathAndQuery {
        &self.path
    }

    /// Returns the expected_status from the HealthcheckBuilder
    pub fn get_expected_status(&self) -> u16 {
        self.expected_status
    }

    /// Returns the window from the HealthcheckBuilder
    pub fn get_window(&self) -> u32 {
        self.window
    }

    /// Returns the threshold from the HealthcheckBuilder
    pub fn get_threshold(&self) -> u32 {
        self.threshold
    }

    /// Returns the initial from the HealthcheckBuilder
    pub fn get_initial(&self) -> u32 {
        self.initial
    }

    /// Returns the interval_ms from the HealthcheckBuilder
    pub fn get_interval_ms(&self) -> Duration {
        self.interval_ms
    }

    /// Returns the timeout_ms from the HealthcheckBuilder
    pub fn get_timeout_ms(&self) -> Duration {
        self.timeout_ms
    }

    /// Set the Host header to set when making the request (e.g. example.com).
    ///
    /// A common mistake is setting the wrong host header, which causes the health check to fail.
    pub fn host(mut self, name: impl ToString) -> Self {
        self.host = name.to_string();
        self
    }

    /// Set an HTTP verb (i.e., HEAD, GET, or POST) to use when performing the health check.
    ///
    /// Defaults to "GET".
    pub fn method(mut self, value: impl ToString) -> Self {
        self.method = value.to_string();
        self
    }

    /// Set a path to visit on your origins when performing the check. Use a unique path.
    /// For example, use /website-healthcheck.txt, not / or /healthcheck.
    ///
    /// Defaults to "/".
    pub fn path(mut self, value: impl ToString) -> Self {
        self.path = PathAndQuery::from_maybe_shared(value.to_string()).expect("Improper path set");
        self
    }

    /// Sets the HTTP status code that signifies a healthy state.  
    ///
    /// Defaults to 200.
    pub fn expected_status(mut self, value: u16) -> Self {
        self.expected_status = value;
        self
    }

    /// Set the number of most recent health check queries to keep.
    /// Must not be greater than 15.
    ///
    /// Defaults to 5.
    pub fn window(mut self, value: u32) -> Self {
        self.window = value;
        self
    }

    /// Set the number of health checks that must be successful within the window
    /// to be considered healthy. Must not be greater than the current window.
    ///
    /// Defaults to 3.
    pub fn threshold(mut self, value: u32) -> Self {
        self.threshold = value;
        self
    }

    /// Set the number of successes to assume are successful when beginning a health check.
    /// Must not be greater than the current window.
    ///
    /// Defaults to 4.
    pub fn initial(mut self, value: u32) -> Self {
        self.initial = value;
        self
    }

    /// Set the interval in milliseconds where a health check should be performed.
    /// Times must be 1 second to 1 hour inclusive. Must be less
    /// than the current timeout_ms.
    ///
    /// Defaults to 15000ms.
    pub fn interval_ms(mut self, value: Duration) -> Self {
        self.interval_ms = value;
        self
    }

    /// Set the time in milliseconds in which to perform the health check.
    /// Note that querying the health check renews the timer. Must not be less
    /// than the current interval_ms.
    ///
    /// Defaults to 5000ms.
    pub fn timeout_ms(mut self, value: Duration) -> Self {
        self.timeout_ms = value;
        self
    }
}

impl Backend {
    #[doc = include_str!("../../docs/snippets/healthcheck-builder.md")]
    pub fn healthcheck_builder(host: impl ToString) -> HealthcheckBuilder {
        HealthcheckBuilder::new(host.to_string())
    }
}