alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
//! Registry validation and enabled-entry load proofs.

use super::{
    PluginComponentPath, PluginConfig, PluginIdentity, PluginProposalPolicy, PluginWitWorld,
};
use crate::plugin::{PluginCapabilities, PluginRegistryError, ValidatedPluginRuntimeLimits};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

/// Root registry for statically configured Wasm plugins.
#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(default, deny_unknown_fields)]
pub struct PluginRegistryConfig {
    /// Configured plugin entries.
    pub plugins: Vec<PluginConfig>,
}

impl PluginRegistryConfig {
    /// Returns enabled plugin entries in declared order.
    pub fn enabled_plugins(&self) -> impl Iterator<Item = &PluginConfig> {
        self.plugins.iter().filter(|plugin| plugin.enabled)
    }

    /// Returns the configured entry for `identity`.
    #[must_use]
    pub fn plugin(&self, identity: &str) -> Option<&PluginConfig> {
        self.plugins
            .iter()
            .find(|plugin| plugin.identity == identity)
    }

    /// Validates registry-level invariants and returns a loadable registry view.
    ///
    /// # Errors
    ///
    /// Returns [`PluginRegistryError`] when plugin identities are empty or duplicated.
    pub fn validate(&self) -> Result<ValidatedPluginRegistry, PluginRegistryError> {
        ValidatedPluginRegistry::try_from(self.clone())
    }
}

/// Plugin registry after identity invariants have been checked.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ValidatedPluginRegistry {
    /// Configured plugin entries with unique validated identities.
    plugins: Vec<ValidatedPluginConfig>,
}

impl ValidatedPluginRegistry {
    /// Returns enabled plugin entries in declared order.
    pub fn enabled_plugins(&self) -> impl Iterator<Item = EnabledPluginConfig<'_>> {
        self.plugins
            .iter()
            .filter_map(ValidatedPluginConfig::enabled)
    }

    /// Returns the enabled plugin entry for `identity`.
    #[must_use]
    pub fn enabled_plugin(&self, identity: &PluginIdentity) -> Option<EnabledPluginConfig<'_>> {
        self.plugins
            .iter()
            .find(|plugin| plugin.identity() == identity)
            .and_then(ValidatedPluginConfig::enabled)
    }

    /// Returns the unique configured entry for `identity`.
    #[must_use]
    pub fn plugin(&self, identity: &PluginIdentity) -> Option<&PluginConfig> {
        self.plugins
            .iter()
            .find(|plugin| plugin.identity() == identity)
            .map(ValidatedPluginConfig::config)
    }

    /// Returns every validated plugin entry in declared order.
    #[must_use]
    pub fn plugins(&self) -> impl ExactSizeIterator<Item = &ValidatedPluginConfig> {
        self.plugins.iter()
    }
}

impl TryFrom<PluginRegistryConfig> for ValidatedPluginRegistry {
    type Error = PluginRegistryError;

    fn try_from(config: PluginRegistryConfig) -> Result<Self, Self::Error> {
        let mut identities = HashSet::with_capacity(config.plugins.len());
        let mut identity_proofs = Vec::with_capacity(config.plugins.len());
        for plugin in &config.plugins {
            let identity = PluginIdentity::try_new(plugin.identity.clone()).map_err(|source| {
                PluginRegistryError::InvalidIdentity {
                    identity: plugin.identity.clone(),
                    source,
                }
            })?;
            if !identities.insert(identity.clone()) {
                return Err(PluginRegistryError::DuplicateIdentity { identity });
            }
            identity_proofs.push(identity);
        }

        let mut plugins = Vec::with_capacity(config.plugins.len());
        for (plugin, identity) in config.plugins.into_iter().zip(identity_proofs) {
            let validated = ValidatedPluginConfig::from_validated_identity(identity, plugin)?;
            plugins.push(validated);
        }

        Ok(Self { plugins })
    }
}

/// Plugin config after registry-level identity validation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ValidatedPluginConfig {
    /// Stable validated plugin identity.
    identity: PluginIdentity,
    /// Original static plugin config.
    config: PluginConfig,
    /// Additional proofs present only for loadable enabled entries.
    enabled: Option<EnabledPluginConfigProof>,
}

impl ValidatedPluginConfig {
    /// Returns the stable plugin identity.
    #[must_use]
    pub const fn identity(&self) -> &PluginIdentity {
        &self.identity
    }

    /// Returns the original static config.
    #[must_use]
    pub const fn config(&self) -> &PluginConfig {
        &self.config
    }

    /// Returns an enabled plugin view when this entry is loadable.
    #[must_use]
    pub fn enabled(&self) -> Option<EnabledPluginConfig<'_>> {
        self.enabled
            .as_ref()
            .map(|proof| EnabledPluginConfig { entry: self, proof })
    }
}

impl TryFrom<PluginConfig> for ValidatedPluginConfig {
    type Error = PluginRegistryError;

    fn try_from(config: PluginConfig) -> Result<Self, Self::Error> {
        let identity = PluginIdentity::try_new(config.identity.clone()).map_err(|source| {
            PluginRegistryError::InvalidIdentity {
                identity: config.identity.clone(),
                source,
            }
        })?;
        Self::from_validated_identity(identity, config)
    }
}

impl ValidatedPluginConfig {
    /// Reuses the identity proof while deriving enabled-entry proofs.
    fn from_validated_identity(
        identity: PluginIdentity,
        config: PluginConfig,
    ) -> Result<Self, PluginRegistryError> {
        let enabled = if config.enabled {
            Some(EnabledPluginConfigProof::try_from_config(
                &identity, &config,
            )?)
        } else {
            None
        };

        Ok(Self {
            identity,
            config,
            enabled,
        })
    }
}

/// Load-time proofs for an enabled plugin config.
#[derive(Clone, Debug, Eq, PartialEq)]
struct EnabledPluginConfigProof {
    /// Proven current WIT world.
    wit_world: PluginWitWorld,
    /// Validated component path shape.
    component_path: PluginComponentPath,
    /// Validated runtime limits.
    runtime_limits: ValidatedPluginRuntimeLimits,
}

impl EnabledPluginConfigProof {
    /// Builds enabled-entry proofs from a static config.
    fn try_from_config(
        identity: &PluginIdentity,
        config: &PluginConfig,
    ) -> Result<Self, PluginRegistryError> {
        if !config.supports_current_world() {
            return Err(PluginRegistryError::UnsupportedEnabledWitWorld {
                identity: identity.clone(),
                wit_world: config.wit_world.clone(),
            });
        }
        let wit_world = PluginWitWorld::current();

        let component_path =
            PluginComponentPath::try_new(config.component_path.clone()).map_err(|source| {
                PluginRegistryError::InvalidEnabledComponentPath {
                    identity: identity.clone(),
                    source,
                }
            })?;
        let runtime_limits = config.runtime_limits.validate().map_err(|source| {
            PluginRegistryError::InvalidEnabledRuntimeLimits {
                identity: identity.clone(),
                source,
            }
        })?;

        Ok(Self {
            wit_world,
            component_path,
            runtime_limits,
        })
    }
}

/// Borrowed validated view of a loadable enabled plugin config.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct EnabledPluginConfig<'registry> {
    /// Validated registry entry.
    entry: &'registry ValidatedPluginConfig,
    /// Enabled-entry proofs.
    proof: &'registry EnabledPluginConfigProof,
}

impl EnabledPluginConfig<'_> {
    /// Returns the stable plugin identity.
    #[must_use]
    pub const fn identity(&self) -> &PluginIdentity {
        &self.entry.identity
    }

    /// Returns the supported WIT world.
    #[must_use]
    pub const fn wit_world(&self) -> &'static str {
        self.proof.wit_world.as_str()
    }

    /// Returns the supported WIT world proof.
    #[must_use]
    pub(in crate::plugin) const fn wit_world_proof(&self) -> PluginWitWorld {
        self.proof.wit_world
    }

    /// Returns the validated component path shape.
    #[must_use]
    pub const fn component_path(&self) -> &PluginComponentPath {
        &self.proof.component_path
    }

    /// Returns maximum static capability grants from config.
    #[must_use]
    pub const fn capabilities(&self) -> &PluginCapabilities {
        &self.entry.config.capabilities
    }

    /// Returns validated runtime limits.
    #[must_use]
    pub const fn runtime_limits(&self) -> ValidatedPluginRuntimeLimits {
        self.proof.runtime_limits
    }

    /// Returns proposal handling policy for effects produced by this plugin.
    #[must_use]
    pub const fn proposal_policy(&self) -> PluginProposalPolicy {
        self.entry.config.proposal_policy
    }

    /// Returns the original config entry.
    #[must_use]
    pub const fn config(&self) -> &PluginConfig {
        &self.entry.config
    }
}