coil-wasm 0.1.1

WASM extension runtime and host APIs for the Coil framework.
Documentation
use super::*;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledHandler {
    pub handler_id: HandlerId,
    pub granted_capabilities: HostGrantSet,
    pub effective_limits: ResourceLimits,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HandlerInstallation {
    pub handler_id: HandlerId,
    pub granted_capabilities: HostGrantSet,
    pub limit_override: Option<ResourceLimits>,
}

impl HandlerInstallation {
    pub fn new(handler_id: HandlerId, granted_capabilities: HostGrantSet) -> Self {
        Self {
            handler_id,
            granted_capabilities,
            limit_override: None,
        }
    }

    pub fn with_limit_override(mut self, limits: ResourceLimits) -> Self {
        self.limit_override = Some(limits);
        self
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionInstallation {
    pub customer_app_id: String,
    pub handlers: Vec<HandlerInstallation>,
}

impl ExtensionInstallation {
    pub fn new(
        customer_app_id: impl Into<String>,
        handlers: Vec<HandlerInstallation>,
    ) -> Result<Self, WasmModelError> {
        Ok(Self {
            customer_app_id: validate_token("customer_app_id", customer_app_id.into())?,
            handlers,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InstalledExtension {
    pub(crate) manifest: ExtensionManifest,
    pub(crate) customer_app_id: String,
    pub(crate) config: std::collections::BTreeMap<String, ExtensionConfigValue>,
    pub(crate) handlers: std::collections::BTreeMap<HandlerId, InstalledHandler>,
    pub(crate) artifact: Option<InstalledArtifact>,
}

impl InstalledExtension {
    pub fn install(
        manifest: ExtensionManifest,
        installation: ExtensionInstallation,
    ) -> Result<Self, WasmModelError> {
        manifest.validate()?;

        let mut handlers = std::collections::BTreeMap::new();
        for configured_handler in installation.handlers {
            let manifest_handler = manifest
                .handler(&configured_handler.handler_id)
                .ok_or_else(|| WasmModelError::HandlerNotFound {
                    handler_id: configured_handler.handler_id.to_string(),
                })?;

            if handlers.contains_key(&configured_handler.handler_id) {
                return Err(WasmModelError::DuplicateInstalledHandler {
                    handler_id: configured_handler.handler_id.to_string(),
                });
            }

            if !configured_handler
                .granted_capabilities
                .is_subset_of(&manifest_handler.requested_grants)
            {
                let offending = configured_handler
                    .granted_capabilities
                    .iter()
                    .find(|grant| !manifest_handler.requested_grants.contains(grant))
                    .expect("subset failure has an offending grant")
                    .clone();

                return Err(WasmModelError::GrantNotDeclared {
                    handler_id: configured_handler.handler_id.to_string(),
                    grant: offending,
                });
            }

            let declared_limits = manifest_handler.effective_limits(manifest.default_limits);
            let effective_limits = configured_handler.limit_override.unwrap_or(declared_limits);
            effective_limits.validate()?;
            effective_limits
                .ensure_no_looser_than(&declared_limits, &configured_handler.handler_id)?;

            handlers.insert(
                configured_handler.handler_id.clone(),
                InstalledHandler {
                    handler_id: configured_handler.handler_id,
                    granted_capabilities: configured_handler.granted_capabilities,
                    effective_limits,
                },
            );
        }

        Ok(Self {
            manifest,
            customer_app_id: installation.customer_app_id,
            config: std::collections::BTreeMap::new(),
            handlers,
            artifact: None,
        })
    }

    pub fn manifest(&self) -> &ExtensionManifest {
        &self.manifest
    }

    pub fn customer_app_id(&self) -> &str {
        &self.customer_app_id
    }

    pub fn installed_handler_count(&self) -> usize {
        self.handlers.len()
    }

    pub fn config(&self) -> &std::collections::BTreeMap<String, ExtensionConfigValue> {
        &self.config
    }

    pub fn artifact(&self) -> Option<&InstalledArtifact> {
        self.artifact.as_ref()
    }

    pub fn prepare_invocation(
        &self,
        handler_id: &HandlerId,
        mut context: InvocationContext,
    ) -> Result<InvocationPlan, WasmModelError> {
        context.extension_config = self.config.clone();
        context.validate()?;

        let manifest_handler =
            self.manifest
                .handler(handler_id)
                .ok_or_else(|| WasmModelError::HandlerNotFound {
                    handler_id: handler_id.to_string(),
                })?;
        let installed_handler =
            self.handlers
                .get(handler_id)
                .ok_or_else(|| WasmModelError::HandlerNotFound {
                    handler_id: handler_id.to_string(),
                })?;

        let actual_point = context.input.kind();
        let expected_point = manifest_handler.point.kind();
        if actual_point != expected_point {
            return Err(WasmModelError::InvocationPointMismatch {
                handler_id: handler_id.to_string(),
                expected: expected_point,
                actual: actual_point,
            });
        }

        crate::validation::validate_invocation_target(
            handler_id,
            &manifest_handler.point,
            &context.input,
        )?;

        Ok(InvocationPlan {
            extension_id: self.manifest.id.clone(),
            handler_id: handler_id.clone(),
            point: manifest_handler.point.kind(),
            customer_app_id: self.customer_app_id.clone(),
            granted_capabilities: installed_handler.granted_capabilities.clone(),
            limits: installed_handler.effective_limits,
            context,
        })
    }
}