coil-wasm 0.1.1

WASM extension runtime and host APIs for the Coil framework.
Documentation
use std::collections::BTreeSet;

use crate::error::WasmModelError;
use crate::validation::validate_token;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CacheVisibility {
    Public,
    Private,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypedCacheHint {
    pub visibility: CacheVisibility,
    pub max_age_seconds: u64,
    pub stale_while_revalidate_seconds: Option<u64>,
    pub vary_by_locale: bool,
    pub vary_by_user: bool,
    pub vary_by_session: bool,
    pub tags: BTreeSet<String>,
}

impl TypedCacheHint {
    pub fn new(
        visibility: CacheVisibility,
        max_age_seconds: u64,
        stale_while_revalidate_seconds: Option<u64>,
        vary_by_locale: bool,
        vary_by_user: bool,
        vary_by_session: bool,
        tags: impl IntoIterator<Item = impl Into<String>>,
    ) -> Result<Self, WasmModelError> {
        let tags = tags
            .into_iter()
            .map(|tag| validate_token("cache_tag", tag.into()))
            .collect::<Result<BTreeSet<_>, _>>()?;
        let hint = Self {
            visibility,
            max_age_seconds,
            stale_while_revalidate_seconds,
            vary_by_locale,
            vary_by_user,
            vary_by_session,
            tags,
        };
        hint.validate()?;
        Ok(hint)
    }

    pub(crate) fn validate(&self) -> Result<(), WasmModelError> {
        if self.visibility == CacheVisibility::Public && (self.vary_by_user || self.vary_by_session)
        {
            return Err(WasmModelError::InvalidTypedReturn {
                reason: "public cache hints cannot vary by user or session".to_string(),
            });
        }
        if self
            .stale_while_revalidate_seconds
            .is_some_and(|value| value == 0)
        {
            return Err(WasmModelError::InvalidTypedReturn {
                reason: "stale-while-revalidate must be greater than zero".to_string(),
            });
        }
        for tag in &self.tags {
            let _ = validate_token("cache_tag", tag.clone())?;
        }
        Ok(())
    }

    pub fn merge_from(&mut self, other: &Self) {
        self.visibility = match (self.visibility, other.visibility) {
            (CacheVisibility::Private, _) | (_, CacheVisibility::Private) => {
                CacheVisibility::Private
            }
            _ => CacheVisibility::Public,
        };
        self.max_age_seconds = self.max_age_seconds.min(other.max_age_seconds);
        self.stale_while_revalidate_seconds = match (
            self.stale_while_revalidate_seconds,
            other.stale_while_revalidate_seconds,
        ) {
            (Some(left), Some(right)) => Some(left.min(right)),
            _ => None,
        };
        self.vary_by_locale |= other.vary_by_locale;
        self.vary_by_user |= other.vary_by_user;
        self.vary_by_session |= other.vary_by_session;
        self.tags.extend(other.tags.iter().cloned());
    }
}