alma 0.1.1

A Bevy-native modal text editor with Vim-style navigation.
Documentation
//! Component path proofs for static plugin config.

use crate::plugin::policy::error::PluginComponentPathError;
use std::{
    fmt::{Debug, Formatter},
    path::{Path, PathBuf},
};

/// Validated local Wasm component path accepted by the runtime boundary.
#[derive(Clone, Eq, PartialEq)]
pub struct PluginComponentPath(PathBuf);

impl PluginComponentPath {
    /// Validates a configured component path.
    ///
    /// # Errors
    ///
    /// Returns [`PluginComponentPathError`] when `path` is empty, absolute, or non-normalized.
    pub fn try_new(path: impl Into<PathBuf>) -> Result<Self, PluginComponentPathError> {
        let path = path.into();
        validate_component_path(&path)?;
        Ok(Self(path))
    }

    /// Returns the validated component path.
    #[must_use]
    pub fn as_path(&self) -> &Path {
        &self.0
    }

    /// Returns the validated component path as an owned path.
    #[must_use]
    pub fn into_path_buf(self) -> PathBuf {
        self.0
    }
}

impl Debug for PluginComponentPath {
    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
        formatter
            .debug_struct("PluginComponentPath")
            .field("path_byte_len", &self.0.to_str().map_or(0, str::len))
            .finish()
    }
}

/// Validates a local component path before runtime loading.
fn validate_component_path(path: &Path) -> Result<(), PluginComponentPathError> {
    let Some(path) = path.to_str() else {
        return Err(PluginComponentPathError::PlatformSeparator);
    };

    if path.is_empty() {
        return Err(PluginComponentPathError::Empty);
    }
    if path.starts_with('/') {
        return Err(PluginComponentPathError::Absolute);
    }
    if path.as_bytes().get(1) == Some(&b':') {
        return Err(PluginComponentPathError::WindowsDrivePrefix);
    }
    if path.contains('\\') {
        return Err(PluginComponentPathError::PlatformSeparator);
    }

    for component in path.split('/') {
        if component.is_empty() {
            return Err(PluginComponentPathError::EmptyComponent);
        }
        if matches!(component, "." | "..") {
            return Err(PluginComponentPathError::DotComponent);
        }
    }

    Ok(())
}