fastly-shared 0.2.0-alpha4

Shared definitions for Fastly Compute@Edge
Documentation
use std::convert::TryFrom;
use std::fmt;

/// The maximum number of pending requests that can be passed to `select`.
///
/// In practice, a program will be limited first by the number of requests it can create.
pub const MAX_PENDING_REQS: u32 = 16 * 1024;

// These should always be a very high number that is not `MAX`, to avoid clashing with both
// legitimate handles, as well as other sentinel values defined by cranelift_entity.
pub const INVALID_REQUEST_HANDLE: u32 = std::u32::MAX - 1;
pub const INVALID_PENDING_REQUEST_HANDLE: u32 = std::u32::MAX - 1;
pub const INVALID_RESPONSE_HANDLE: u32 = std::u32::MAX - 1;
pub const INVALID_BODY_HANDLE: u32 = std::u32::MAX - 1;

#[derive(Clone, Copy, Eq, PartialEq)]
#[repr(transparent)]
pub struct XqdStatus {
    pub code: i32,
}

impl XqdStatus {
    /// Success value.
    ///
    /// This indicates that a hostcall finished successfully.
    pub const OK: Self = Self { code: 0 };
    /// Generic error value.
    ///
    /// This means that some unexpected error occured during a hostcall.
    pub const ERROR: Self = Self { code: 1 };
    /// Invalid argument.
    pub const INVAL: Self = Self { code: 2 };
    /// Invalid handle.
    ///
    /// Thrown when a request, response, or body handle is not valid.
    pub const BADF: Self = Self { code: 3 };
    /// Buffer length error.
    ///
    /// Thrown when a buffer is too long.
    pub const BUFLEN: Self = Self { code: 4 };
    /// Unsupported operation error.
    ///
    /// This error is thrown when some operation cannot be performed, because it is not supported.
    pub const UNSUPPORTED: Self = Self { code: 5 };
    /// Alignment error.
    ///
    /// This is thrown when a pointer does not point to a properly aligned slice of memory.
    pub const BADALIGN: Self = Self { code: 6 };
    /// HTTP parse error.
    ///
    /// This can be thrown when a method, URI, header, or status is not valid. This can also
    /// be thrown if a message head is too large.
    pub const HTTPPARSE: Self = Self { code: 7 };
    /// HTTP user error.
    ///
    /// This is thrown in cases where user code caused an HTTP error. For example, attempt to send
    /// a 1xx response code, or a request with a non-absolute URI. This can also be caused by
    /// an unexpected header: both `content-length` and `transfer-encoding`, for example.
    pub const HTTPUSER: Self = Self { code: 8 };
    /// HTTP incomplete message error.
    ///
    /// This can be thrown when a stream ended unexpectedly.
    pub const HTTPINCOMPLETE: Self = Self { code: 9 };

    pub fn is_ok(&self) -> bool {
        self == &Self::OK
    }

    pub fn is_err(&self) -> bool {
        !self.is_ok()
    }
}

impl fmt::Debug for XqdStatus {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(match *self {
            XqdStatus::OK => "OK",
            XqdStatus::ERROR => "ERROR",
            XqdStatus::INVAL => "INVAL",
            XqdStatus::BADF => "BADF",
            XqdStatus::BUFLEN => "BUFLEN",
            XqdStatus::BADALIGN => "BADALIGN",
            XqdStatus::HTTPPARSE => "HTTP_PARSE_ERROR",
            XqdStatus::HTTPUSER => "HTTP_USER_ERROR",
            XqdStatus::HTTPINCOMPLETE => "HTTP_INCOMPLETE_MESSAGE",
            _ => panic!("unexpected XqdStatus"),
        })
    }
}

pub const XQD_ABI_VERSION: u64 = 1;

// define our own enum rather than using `http`'s, so that we can easily convert it to a scalar
#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(u32)]
pub enum HttpVersion {
    Http09 = 0,
    Http10 = 1,
    Http11 = 2,
    H2 = 3,
    H3 = 4,
}

// TODO ACF 2019-12-04: could use num-derive for this, but I don't think it's worth pulling in a
// whole new set of dependencies when this will likely be encoded by witx shortly
impl TryFrom<u32> for HttpVersion {
    type Error = String;

    fn try_from(x: u32) -> Result<Self, Self::Error> {
        if x == Self::Http09 as u32 {
            Ok(Self::Http09)
        } else if x == Self::Http10 as u32 {
            Ok(Self::Http10)
        } else if x == Self::Http11 as u32 {
            Ok(Self::Http11)
        } else if x == Self::H2 as u32 {
            Ok(Self::H2)
        } else if x == Self::H3 as u32 {
            Ok(Self::H3)
        } else {
            Err(format!("unknown http version enum value: {}", x))
        }
    }
}

impl From<http::Version> for HttpVersion {
    fn from(v: http::Version) -> Self {
        match v {
            http::Version::HTTP_09 => Self::Http09,
            http::Version::HTTP_10 => Self::Http10,
            http::Version::HTTP_11 => Self::Http11,
            http::Version::HTTP_2 => Self::H2,
            http::Version::HTTP_3 => Self::H3,
            _ => unreachable!(),
        }
    }
}

impl From<HttpVersion> for http::Version {
    fn from(v: HttpVersion) -> Self {
        match v {
            HttpVersion::Http09 => Self::HTTP_09,
            HttpVersion::Http10 => Self::HTTP_10,
            HttpVersion::Http11 => Self::HTTP_11,
            HttpVersion::H2 => Self::HTTP_2,
            HttpVersion::H3 => Self::HTTP_3,
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[repr(u32)]
pub enum BodyWriteEnd {
    Back = 0,
    Front = 1,
}

/// Additional Fastly-specific metadata for requests.
#[derive(Debug)]
pub struct FastlyRequestMetadata {
    pub cache_override: CacheOverride,
}

impl FastlyRequestMetadata {
    pub const fn default() -> Self {
        Self {
            cache_override: CacheOverride::default(),
        }
    }
}

/// Optional override for response caching behavior.
#[derive(Clone, Copy, Debug)]
pub enum CacheOverride {
    /// Do not override the behavior specified in the origin response's cache control headers.
    None,
    /// Do not cache the response to this request, regardless of the origin response's headers.
    Pass,
    /// Override particular cache control settings.
    ///
    /// The origin response's cache control headers will be used for any fields that are `None`.
    Override {
        ttl: Option<u32>,
        stale_while_revalidate: Option<u32>,
    },
}

impl CacheOverride {
    pub const fn none() -> Self {
        Self::None
    }

    pub const fn pass() -> Self {
        Self::Pass
    }

    pub const fn ttl(ttl: u32) -> Self {
        Self::Override {
            ttl: Some(ttl),
            stale_while_revalidate: None,
        }
    }

    pub const fn stale_while_revalidate(swr: u32) -> Self {
        Self::Override {
            ttl: None,
            stale_while_revalidate: Some(swr),
        }
    }

    pub fn set_none(&mut self) {
        *self = Self::None;
    }

    pub fn set_pass(&mut self) {
        *self = Self::Pass;
    }

    pub fn set_ttl(&mut self, new_ttl: u32) {
        match self {
            Self::Override { ttl, .. } => *ttl = Some(new_ttl),
            _ => {
                *self = Self::Override {
                    ttl: Some(new_ttl),
                    stale_while_revalidate: None,
                }
            }
        }
    }

    pub fn set_stale_while_revalidate(&mut self, new_swr: u32) {
        match self {
            Self::Override {
                stale_while_revalidate,
                ..
            } => *stale_while_revalidate = Some(new_swr),
            _ => {
                *self = Self::Override {
                    ttl: None,
                    stale_while_revalidate: Some(new_swr),
                }
            }
        }
    }

    pub const fn default() -> Self {
        Self::None
    }

    /// Convert to a representation suitable for passing across the ABI boundary.
    ///
    /// The representation contains the `CacheOverrideTag` along with all of the possible fields:
    /// `(tag, ttl, swr)`.
    #[doc(hidden)]
    pub fn to_abi(self) -> (u32, u32, u32) {
        match self {
            Self::None => (CacheOverrideTag::empty().bits(), 0, 0),
            Self::Pass => (CacheOverrideTag::PASS.bits(), 0, 0),
            Self::Override {
                ttl,
                stale_while_revalidate,
            } => {
                let mut tag = CacheOverrideTag::empty();
                let ttl = if let Some(ttl) = ttl {
                    tag |= CacheOverrideTag::TTL;
                    ttl
                } else {
                    0
                };
                let swr = if let Some(swr) = stale_while_revalidate {
                    tag |= CacheOverrideTag::STALE_WHILE_REVALIDATE;
                    swr
                } else {
                    0
                };
                (tag.bits(), ttl, swr)
            }
        }
    }

    /// Convert from the representation suitable for passing across the ABI boundary.
    ///
    /// Returns `None` if the tag is not recognized. Depending on the tag, some of the values may be
    /// ignored.
    #[doc(hidden)]
    pub fn from_abi(tag: u32, ttl: u32, swr: u32) -> Option<Self> {
        if let Some(tag) = CacheOverrideTag::from_bits(tag) {
            if tag.is_empty() {
                Some(CacheOverride::None)
            } else if tag.contains(CacheOverrideTag::PASS) {
                Some(CacheOverride::Pass)
            } else {
                let ttl = if tag.contains(CacheOverrideTag::TTL) {
                    Some(ttl)
                } else {
                    None
                };
                let stale_while_revalidate =
                    if tag.contains(CacheOverrideTag::STALE_WHILE_REVALIDATE) {
                        Some(swr)
                    } else {
                        None
                    };
                Some(CacheOverride::Override {
                    ttl,
                    stale_while_revalidate,
                })
            }
        } else {
            None
        }
    }
}

bitflags::bitflags! {
    /// A bit field used to tell the host which fields are used when setting the cache override.
    ///
    /// If the `PASS` bit is set, all other bits are ignored.
    struct CacheOverrideTag: u32 {
        const PASS = 0b0001;
        const TTL = 0b0010;
        const STALE_WHILE_REVALIDATE = 0b0100;
    }
}