use std::{fs, io}; use log::error; use serde::{Deserialize, Serialize}; use tauri::path::BaseDirectory; use tauri::{AppHandle, Manager}; use thiserror::Error; use crate::deno::{get_plugin_capabilities_block, run_plugin_block}; use crate::models::{HttpRequest, WorkspaceExportResources}; #[derive(Error, Debug)] pub enum PluginError { #[error("directory not found")] DirectoryNotFound(#[from] io::Error), #[error("anyhow error")] V8(#[from] anyhow::Error), // #[error("unknown data store error")] // Unknown, } #[derive(Default, Debug, Deserialize, Serialize)] pub struct FilterResult { pub filtered: String, } #[derive(Default, Debug, Deserialize, Serialize)] pub struct ImportResult { pub resources: WorkspaceExportResources, } #[derive(Eq, PartialEq, Hash, Clone)] pub enum PluginCapability { Export, Import, Filter, } pub struct PluginDef { pub name: String, pub path: String, pub capabilities: Vec, } pub fn scan_plugins(app_handle: &AppHandle) -> Result, PluginError> { let plugins_dir = app_handle .path() .resolve("plugins", BaseDirectory::Resource) .expect("failed to resolve plugin directory resource"); let plugin_entries = fs::read_dir(plugins_dir)?; let mut plugins = Vec::new(); for entry in plugin_entries { let plugin_dir_entry = match entry { Err(_) => continue, Ok(entry) => entry, }; let plugin_index_file = plugin_dir_entry.path().join("index.mjs"); let capabilities = get_plugin_capabilities_block(&plugin_index_file.to_str().unwrap())?; plugins.push(PluginDef { name: plugin_dir_entry.file_name().to_string_lossy().to_string(), path: plugin_index_file.to_string_lossy().to_string(), capabilities, }); } Ok(plugins) } pub async fn find_plugins( app_handle: &AppHandle, capability: &PluginCapability, ) -> Result, PluginError> { let plugins = scan_plugins(app_handle)? .into_iter() .filter(|p| p.capabilities.contains(capability)) .collect(); Ok(plugins) } pub fn get_plugin(app_handle: &AppHandle, name: &str) -> Result, PluginError> { Ok(scan_plugins(app_handle)? .into_iter() .find(|p| p.name == name)) } pub async fn run_plugin_filter( plugin: &PluginDef, response_body: &str, filter: &str, ) -> Option { let result = run_plugin_block( &plugin.path, "pluginHookResponseFilter", vec![ serde_json::to_value(response_body).unwrap(), serde_json::to_value(filter).unwrap(), ], ) .map_err(|e| e.to_string()) .expect("Failed to run plugin"); if result.is_null() { error!("Plugin {} failed to run", plugin.name); return None; } let resources: FilterResult = serde_json::from_value(result).expect("failed to parse filter plugin result json"); Some(resources) } pub fn run_plugin_export_curl( app_handle: &AppHandle, request: &HttpRequest, ) -> Result { let plugin = match get_plugin(app_handle, "exporter-curl").map_err(|e| e.to_string())? { None => return Err("Failed to get plugin".into()), Some(p) => p, }; let request_json = serde_json::to_value(request).map_err(|e| e.to_string())?; let result = run_plugin_block(&plugin.path, "pluginHookExport", vec![request_json]) .map_err(|e| e.to_string())?; let export_str: String = serde_json::from_value(result).map_err(|e| e.to_string())?; Ok(export_str) } pub async fn run_plugin_import( plugin: &PluginDef, file_contents: &str, ) -> Result, String> { let result = run_plugin_block( &plugin.path, "pluginHookImport", vec![serde_json::to_value(file_contents).map_err(|e| e.to_string())?], ) .map_err(|e| e.to_string())?; if result.is_null() { return Ok(None); } let resources: ImportResult = serde_json::from_value(result).map_err(|e| e.to_string())?; Ok(Some(resources)) }