spectrust_fastly_worker 0.1.0

SpecTrust library to integrate Spec Proxy with Fastly Compute@Edge
Documentation
//! This module defines the Configuration objects that control how Spec Proxy
//! behaves. This allows altering things like the Spec Proxy operating mode,
//! the amount of traffic routed to Spec Proxy, and to which customer servers
//! Spec Proxy can make requests.

use std::collections::HashMap;

use crate::Error;

/// Define the operating mode of Spec Proxy.
#[derive(Debug, Copy, Clone)]
pub enum SpecProxyMode {
    /// Inline mode will route all requests _through_ Spec Proxy. This means
    /// that Spec Proxy itself will be responsible for sending incoming traffic to your origin.
    /// This feature allows for the full capabilities of Spec Proxy, which includes content
    /// injection, request or response modification, and other powerful features.
    ///
    /// Talk with your SpecTrust representative about enabling and using this feature.
    Inline,
    /// Listening mode will copy each request and send the copy to Spec Proxy and the original
    /// to your server. In this mode, a request is sent asynchronously in the background to
    /// Spec Proxy, and traffic continues as normal to your origin. This operating mode doesn't
    /// provide Spec Proxy's full suite of features like content injection and request or
    /// response modification, but will provide the most minimal latency possible when using
    /// the Spec Proxy product.
    ///
    /// Use this mode if you haven't configured your Spec Proxy for Inline mode.
    Listening,
}

/// Define how Spec Proxy will behave.
///
/// The `SpecConfiguration` object defines how this library will behave.
/// This includes disabling the features of this library entirely, changing
/// the operating mode of the proxy, and configuring how IPs and traffic is
/// routed.
///
/// Please use [`SpecConfiguration::builder()`] to construct this object.
///
/// ```
/// # use spectrust_fastly_worker::{SpecConfiguration, SpecProxyMode};
/// let config = SpecConfiguration::builder([
///         ("www.example.com", "example_origin"),
///         ("www.example.com.specprotected.com", "example_spec_proxy_origin")
///     ].into())
///     .with_operating_mode(SpecProxyMode::Inline)
///     .with_percentage_of_ips(50)
///     .build();
/// ```
///
///
/// ## Note
///
/// You can still use the [`spec_proxy_process`](crate::spec_proxy_process) when
/// [`SpecConfiguration::disable_spec_proxy()`] is true. It will perform the request
/// without any modification and without contacting Spec Proxy. It will use the Fastly
/// Backend map to look up the appropriate backend for the incoming host, make the request,
/// and then return the response without modifying anything.
#[derive(Debug)]
pub struct SpecConfiguration<'a> {
    /// Disable Spec Proxy entirely when this value is set to `true`
    disable_spec_proxy: bool,
    /// Choose the operating mode of Spec Proxy
    operating_mode: SpecProxyMode,
    /// Select the percentage of IPs that are routed through Spec Proxy
    percentage_of_ips: u8,
    /// Configure the Fastly Backends for each potential host this worker function
    /// might contact
    host_to_fastly_backend: HashMap<&'a str, &'a str>,
}

impl<'a> SpecConfiguration<'a> {
    /// Create a Builder for configuring the Spec Proxy library
    ///
    /// Requires a [`HashMap`] of hostname to
    /// [Fastly Backends](https://developer.fastly.com/reference/api/services/backend/)
    /// which is needed to properly route requests from the Compute@Edge framework.
    pub fn builder(
        host_to_fastly_backend: HashMap<&'a str, &'a str>,
    ) -> SpecConfigurationBuilder<'a> {
        SpecConfigurationBuilder {
            config: SpecConfiguration::new(host_to_fastly_backend),
        }
    }

    /// `true` if this library's functionality is completely disabled.
    ///
    /// This library will perform the request and return the response immediately
    /// without involving Spec Proxy at all or performing any request or response
    /// modification.
    pub fn disable_spec_proxy(&self) -> bool {
        self.disable_spec_proxy
    }

    /// Retrieve the [`SpecProxyMode`] this library operates in
    pub fn operating_mode(&self) -> SpecProxyMode {
        self.operating_mode
    }

    /// Retrieve the Percentage of IP traffic that is routed to Spec Proxy
    pub fn percentage_of_ips(&self) -> u8 {
        self.percentage_of_ips
    }

    /// Acquire the Fastly Backend for a given hostname
    pub fn backend_for_host(&self, host: &'_ str) -> Result<&'a str, Error> {
        self.host_to_fastly_backend
            .get(host)
            // deref an &&str
            .cloned()
            .ok_or_else(|| Error::MissingFastlyBackend(host.to_string()))
    }

    /// The default `SpecConfiguration` which routes 100% of traffic through
    /// Spec Proxy and uses the [`SpecProxyMode::Listening`] operating mode.
    ///
    /// Note: this default doesn't configure any Fastly Backends, which means requests
    /// won't go through when this is used in a Compute@Edge function. You should
    /// use the [`SpecConfiguration::builder`] to properly configure Spec Proxy.
    fn new(host_to_fastly_backend: HashMap<&'a str, &'a str>) -> Self {
        Self {
            disable_spec_proxy: false,
            operating_mode: SpecProxyMode::Listening,
            percentage_of_ips: 100,
            host_to_fastly_backend,
        }
    }
}

/// The builder responsible for constructing a [`SpecConfiguration`] object.
#[derive(Debug)]
pub struct SpecConfigurationBuilder<'a> {
    config: SpecConfiguration<'a>,
}

impl<'a> SpecConfigurationBuilder<'a> {
    /// Configure whether or not this library is disabled.
    ///
    /// This value is allowed to change during runtime.
    pub fn with_disable_spec_proxy(mut self, disable: bool) -> Self {
        self.config.disable_spec_proxy = disable;
        self
    }

    /// Select the operating mode of the library.
    ///
    /// This value is allowed to change during runtime.
    pub fn with_operating_mode(mut self, mode: SpecProxyMode) -> Self {
        self.config.operating_mode = mode;
        self
    }

    /// Configure the amount of IPs that are routed through Spec Proxy.
    ///
    /// This value is allowed to change during runtime.
    pub fn with_percentage_of_ips(mut self, percentage_of_ips: u8) -> Self {
        self.config.percentage_of_ips = percentage_of_ips;
        self
    }

    /// Finalize the builder into a [`SpecConfiguration`] object.
    pub fn build(self) -> SpecConfiguration<'a> {
        self.config
    }
}