lcpfs 2026.1.102

LCP File System - A ZFS-inspired copy-on-write filesystem for Rust
// Copyright 2025 LunaOS Contributors
// SPDX-License-Identifier: Apache-2.0

//! Integration of WASM plugins with the LCPFS I/O pipeline.
//!
//! This module provides hooks into the storage pipeline for plugin execution.

use alloc::string::String;
use alloc::vec::Vec;

use super::error::PluginError;
use super::manager::PluginManager;
use super::types::{HookType, PluginContext};

// ═══════════════════════════════════════════════════════════════════════════════
// PIPELINE HOOKS
// ═══════════════════════════════════════════════════════════════════════════════

/// Pipeline integration for WASM plugins.
///
/// These functions should be called from the appropriate points in the
/// LCPFS I/O pipeline to allow plugins to process data.
pub struct PipelineHooks;

impl PipelineHooks {
    /// Execute pre-write hooks.
    ///
    /// Called before data is written to storage. Plugins can:
    /// - Validate data
    /// - Transform data (compression, encryption, etc.)
    /// - Log/audit writes
    ///
    /// Returns the (possibly modified) data to write.
    pub fn pre_write(dataset: &str, path: &str, data: &[u8]) -> Result<Vec<u8>, PluginError> {
        // Skip if no hooks are registered
        if !PluginManager::has_hooks(dataset, HookType::PreWrite) {
            return Ok(data.to_vec());
        }

        let ctx = PluginContext::new(path, dataset, "write").with_size(data.len() as u64);

        PluginManager::execute_hook(dataset, HookType::PreWrite, &ctx, data)
    }

    /// Execute post-write hooks.
    ///
    /// Called after data has been written. Plugins can:
    /// - Update indices
    /// - Send notifications
    /// - Log completed writes
    pub fn post_write(dataset: &str, path: &str, size: u64) -> Result<(), PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::PostWrite) {
            return Ok(());
        }

        let ctx = PluginContext::new(path, dataset, "write").with_size(size);

        PluginManager::execute_notify_hook(dataset, HookType::PostWrite, &ctx)
    }

    /// Execute pre-read hooks.
    ///
    /// Called before data is read. Plugins can:
    /// - Check access permissions
    /// - Log read attempts
    ///
    /// Returns Ok(()) to allow read, Err to deny.
    pub fn pre_read(dataset: &str, path: &str) -> Result<(), PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::PreRead) {
            return Ok(());
        }

        let ctx = PluginContext::new(path, dataset, "read");

        PluginManager::execute_notify_hook(dataset, HookType::PreRead, &ctx)
    }

    /// Execute post-read hooks.
    ///
    /// Called after data has been read. Plugins can:
    /// - Transform data (decompression, decryption, etc.)
    /// - Validate checksums
    ///
    /// Returns the (possibly modified) data.
    pub fn post_read(dataset: &str, path: &str, data: &[u8]) -> Result<Vec<u8>, PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::PostRead) {
            return Ok(data.to_vec());
        }

        let ctx = PluginContext::new(path, dataset, "read").with_size(data.len() as u64);

        PluginManager::execute_hook(dataset, HookType::PostRead, &ctx, data)
    }

    /// Execute on-create hooks.
    ///
    /// Called when a new file is created.
    pub fn on_create(dataset: &str, path: &str) -> Result<(), PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::OnCreate) {
            return Ok(());
        }

        let ctx = PluginContext::new(path, dataset, "create");

        PluginManager::execute_notify_hook(dataset, HookType::OnCreate, &ctx)
    }

    /// Execute on-delete hooks.
    ///
    /// Called when a file is deleted.
    pub fn on_delete(dataset: &str, path: &str) -> Result<(), PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::OnDelete) {
            return Ok(());
        }

        let ctx = PluginContext::new(path, dataset, "delete");

        PluginManager::execute_notify_hook(dataset, HookType::OnDelete, &ctx)
    }

    /// Execute compress hook.
    ///
    /// Used when a plugin provides custom compression.
    pub fn compress(dataset: &str, data: &[u8]) -> Result<Option<Vec<u8>>, PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::Compress) {
            return Ok(None);
        }

        let ctx = PluginContext::new("", dataset, "compress").with_size(data.len() as u64);

        let compressed = PluginManager::execute_hook(dataset, HookType::Compress, &ctx, data)?;
        Ok(Some(compressed))
    }

    /// Execute decompress hook.
    ///
    /// Used when a plugin provides custom decompression.
    pub fn decompress(dataset: &str, data: &[u8]) -> Result<Option<Vec<u8>>, PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::Decompress) {
            return Ok(None);
        }

        let ctx = PluginContext::new("", dataset, "decompress").with_size(data.len() as u64);

        let decompressed = PluginManager::execute_hook(dataset, HookType::Decompress, &ctx, data)?;
        Ok(Some(decompressed))
    }

    /// Execute validate hook.
    ///
    /// Returns Ok(()) if valid, Err if validation fails.
    pub fn validate(dataset: &str, path: &str, data: &[u8]) -> Result<(), PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::Validate) {
            return Ok(());
        }

        let ctx = PluginContext::new(path, dataset, "validate").with_size(data.len() as u64);

        let _ = PluginManager::execute_hook(dataset, HookType::Validate, &ctx, data)?;
        Ok(())
    }

    /// Execute transform hook.
    ///
    /// General-purpose data transformation.
    pub fn transform(dataset: &str, path: &str, data: &[u8]) -> Result<Vec<u8>, PluginError> {
        if !PluginManager::has_hooks(dataset, HookType::Transform) {
            return Ok(data.to_vec());
        }

        let ctx = PluginContext::new(path, dataset, "transform").with_size(data.len() as u64);

        PluginManager::execute_hook(dataset, HookType::Transform, &ctx, data)
    }
}

// ═══════════════════════════════════════════════════════════════════════════════
// CONVENIENCE WRAPPER
// ═══════════════════════════════════════════════════════════════════════════════

/// Write data with plugin hooks.
///
/// This is a convenience function that wraps the full write lifecycle:
/// 1. Validate data
/// 2. Transform data
/// 3. Pre-write hooks
/// 4. (caller performs actual write)
/// 5. Post-write hooks
pub fn prepare_write(dataset: &str, path: &str, data: &[u8]) -> Result<Vec<u8>, PluginError> {
    // Validate
    PipelineHooks::validate(dataset, path, data)?;

    // Transform
    let data = PipelineHooks::transform(dataset, path, data)?;

    // Pre-write
    PipelineHooks::pre_write(dataset, path, &data)
}

/// Read data with plugin hooks.
///
/// Convenience function for read lifecycle:
/// 1. Pre-read hooks
/// 2. (caller performs actual read)
/// 3. Post-read hooks
/// 4. Transform (reverse)
pub fn process_read(dataset: &str, path: &str, data: &[u8]) -> Result<Vec<u8>, PluginError> {
    // Post-read
    PipelineHooks::post_read(dataset, path, data)
}

// ═══════════════════════════════════════════════════════════════════════════════
// TESTS
// ═══════════════════════════════════════════════════════════════════════════════

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_pre_write_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::pre_write("test_dataset", "/path", data);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), data);
    }

    #[test]
    fn test_post_write_no_hooks() {
        let result = PipelineHooks::post_write("test_dataset", "/path", 1024);
        assert!(result.is_ok());
    }

    #[test]
    fn test_pre_read_no_hooks() {
        let result = PipelineHooks::pre_read("test_dataset", "/path");
        assert!(result.is_ok());
    }

    #[test]
    fn test_post_read_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::post_read("test_dataset", "/path", data);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), data);
    }

    #[test]
    fn test_on_create_no_hooks() {
        let result = PipelineHooks::on_create("test_dataset", "/path");
        assert!(result.is_ok());
    }

    #[test]
    fn test_on_delete_no_hooks() {
        let result = PipelineHooks::on_delete("test_dataset", "/path");
        assert!(result.is_ok());
    }

    #[test]
    fn test_compress_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::compress("test_dataset", data);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none());
    }

    #[test]
    fn test_decompress_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::decompress("test_dataset", data);
        assert!(result.is_ok());
        assert!(result.unwrap().is_none());
    }

    #[test]
    fn test_validate_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::validate("test_dataset", "/path", data);
        assert!(result.is_ok());
    }

    #[test]
    fn test_transform_no_hooks() {
        let data = b"test data";
        let result = PipelineHooks::transform("test_dataset", "/path", data);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), data);
    }

    #[test]
    fn test_prepare_write_no_hooks() {
        let data = b"test data";
        let result = prepare_write("test_dataset", "/path", data);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), data);
    }

    #[test]
    fn test_process_read_no_hooks() {
        let data = b"test data";
        let result = process_read("test_dataset", "/path", data);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), data);
    }
}