1use std::{
7 collections::HashMap,
8 path::{Path, PathBuf},
9 sync::Arc,
10};
11
12use anyhow::{Context, Result};
13use serde::{Deserialize, Serialize};
14use tokio::sync::RwLock;
15use tracing::{debug, info, instrument, warn};
16
17use crate::{Host::HostConfig, WASM::Runtime::WASMRuntime};
18
19pub struct ExtensionManagerImpl {
21 #[allow(dead_code)]
23 wasm_runtime:Arc<WASMRuntime>,
24 config:HostConfig,
26 extensions:Arc<RwLock<HashMap<String, ExtensionInfo>>>,
28 stats:Arc<RwLock<ExtensionStats>>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ExtensionInfo {
35 pub id:String,
37 pub display_name:String,
39 pub description:String,
41 pub version:String,
43 pub publisher:String,
45 pub path:PathBuf,
47 pub entry_point:PathBuf,
49 pub activation_events:Vec<String>,
51 pub extension_type:ExtensionType,
53 pub state:ExtensionState,
55 pub capabilities:Vec<String>,
57 pub dependencies:Vec<String>,
59 pub manifest:serde_json::Value,
61 pub loaded_at:u64,
63 pub activated_at:Option<u64>,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69pub enum ExtensionType {
70 WASM,
72 Native,
74 JavaScript,
76 Unknown,
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
82pub enum ExtensionState {
83 Loaded,
85 Activated,
87 Deactivated,
89 Error,
91}
92
93#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct ExtensionStats {
96 pub total_loaded:usize,
98 pub total_activated:usize,
100 pub total_deactivated:usize,
102 pub total_activation_time_ms:u64,
104 pub errors:u64,
106}
107
108impl ExtensionManagerImpl {
109 pub fn new(wasm_runtime:Arc<WASMRuntime>, config:HostConfig) -> Self {
111 Self {
112 wasm_runtime,
113 config,
114 extensions:Arc::new(RwLock::new(HashMap::new())),
115 stats:Arc::new(RwLock::new(ExtensionStats::default())),
116 }
117 }
118
119 #[instrument(skip(self, path))]
121 pub async fn load_extension(&self, path:&PathBuf) -> Result<String> {
122 info!("Loading extension from: {:?}", path);
123
124 if !path.exists() {
126 return Err(anyhow::anyhow!("Extension path does not exist: {:?}", path));
127 }
128
129 let manifest = self.parse_manifest(path)?;
131 let extension_id = self.extract_extension_id(&manifest)?;
132
133 let extensions = self.extensions.read().await;
135 if extensions.contains_key(&extension_id) {
136 warn!("Extension already loaded: {}", extension_id);
137 return Ok(extension_id);
138 }
139 drop(extensions);
140
141 let extension_type = self.determine_extension_type(path, &manifest)?;
143
144 let extension_info = ExtensionInfo {
146 id:extension_id.clone(),
147 display_name:manifest.get("displayName").and_then(|v| v.as_str()).unwrap_or("").to_string(),
148 description:manifest.get("description").and_then(|v| v.as_str()).unwrap_or("").to_string(),
149 version:manifest.get("version").and_then(|v| v.as_str()).unwrap_or("0.0.0").to_string(),
150 publisher:manifest.get("publisher").and_then(|v| v.as_str()).unwrap_or("").to_string(),
151 path:path.clone(),
152 entry_point:path.join(manifest.get("main").and_then(|v| v.as_str()).unwrap_or("dist/extension.js")),
153 activation_events:self.extract_activation_events(&manifest),
154 extension_type,
155 state:ExtensionState::Loaded,
156 capabilities:self.extract_capabilities(&manifest),
157 dependencies:self.extract_dependencies(&manifest),
158 manifest,
159 loaded_at:std::time::SystemTime::now()
160 .duration_since(std::time::UNIX_EPOCH)
161 .map(|d| d.as_secs())
162 .unwrap_or(0),
163 activated_at:None,
164 };
165
166 let mut extensions = self.extensions.write().await;
168 extensions.insert(extension_id.clone(), extension_info);
169
170 let mut stats = self.stats.write().await;
172 stats.total_loaded += 1;
173
174 info!("Extension loaded successfully: {}", extension_id);
175
176 Ok(extension_id)
177 }
178
179 #[instrument(skip(self, extension_id))]
181 pub async fn unload_extension(&self, extension_id:&str) -> Result<()> {
182 info!("Unloading extension: {}", extension_id);
183
184 let mut extensions = self.extensions.write().await;
185 extensions.remove(extension_id);
186
187 info!("Extension unloaded: {}", extension_id);
188
189 Ok(())
190 }
191
192 pub async fn get_extension(&self, extension_id:&str) -> Option<ExtensionInfo> {
194 self.extensions.read().await.get(extension_id).cloned()
195 }
196
197 pub async fn list_extensions(&self) -> Vec<String> { self.extensions.read().await.keys().cloned().collect() }
199
200 pub async fn list_extensions_by_state(&self, state:ExtensionState) -> Vec<ExtensionInfo> {
202 self.extensions
203 .read()
204 .await
205 .values()
206 .filter(|ext| ext.state == state)
207 .cloned()
208 .collect()
209 }
210
211 #[instrument(skip(self, extension_id))]
213 pub async fn update_state(&self, extension_id:&str, state:ExtensionState) -> Result<()> {
214 let mut extensions = self.extensions.write().await;
215 if let Some(info) = extensions.get_mut(extension_id) {
216 info.state = state;
217 if state == ExtensionState::Activated {
218 info.activated_at = Some(
219 std::time::SystemTime::now()
220 .duration_since(std::time::UNIX_EPOCH)
221 .map(|d| d.as_secs())
222 .unwrap_or(0),
223 );
224
225 let mut stats = self.stats.write().await;
226 stats.total_activated += 1;
227 } else if state == ExtensionState::Deactivated {
228 let mut stats = self.stats.write().await;
229 stats.total_deactivated += 1;
230 }
231 Ok(())
232 } else {
233 Err(anyhow::anyhow!("Extension not found: {}", extension_id))
234 }
235 }
236
237 pub async fn stats(&self) -> ExtensionStats { self.stats.read().await.clone() }
239
240 #[instrument(skip(self))]
242 pub async fn discover_extensions(&self) -> Result<Vec<PathBuf>> {
243 info!("Discovering extensions in configured paths");
244
245 let mut extensions = Vec::new();
246
247 for discovery_path in &self.config.discovery_paths {
248 match self.discover_in_path(discovery_path).await {
249 Ok(mut found) => extensions.append(&mut found),
250 Err(e) => {
251 warn!("Failed to discover extensions in {}: {}", discovery_path, e);
252 },
253 }
254 }
255
256 info!("Discovered {} extensions", extensions.len());
257
258 Ok(extensions)
259 }
260
261 async fn discover_in_path(&self, path:&str) -> Result<Vec<PathBuf>> {
263 let path = PathBuf::from(shellexpand::tilde(path).as_ref());
264
265 if !path.exists() {
266 return Ok(Vec::new());
267 }
268
269 let mut extensions = Vec::new();
270
271 let mut entries = tokio::fs::read_dir(&path)
273 .await
274 .context(format!("Failed to read directory: {:?}", path))?;
275
276 while let Some(entry) = entries.next_entry().await? {
277 let entry_path = entry.path();
278
279 if !entry_path.is_dir() {
281 continue;
282 }
283
284 let manifest_path = entry_path.join("package.json");
286 let alt_manifest_path = entry_path.join("manifest.json");
287
288 if manifest_path.exists() || alt_manifest_path.exists() {
289 extensions.push(entry_path.clone());
290 debug!("Discovered extension: {:?}", entry_path);
291 }
292 }
293
294 Ok(extensions)
295 }
296
297 fn parse_manifest(&self, path:&Path) -> Result<serde_json::Value> {
299 let manifest_path = path.join("package.json");
300 let alt_manifest_path = path.join("manifest.json");
301
302 let manifest_content = if manifest_path.exists() {
303 tokio::runtime::Runtime::new()
304 .unwrap()
305 .block_on(tokio::fs::read_to_string(&manifest_path))
306 .context("Failed to read package.json")?
307 } else if alt_manifest_path.exists() {
308 tokio::runtime::Runtime::new()
309 .unwrap()
310 .block_on(tokio::fs::read_to_string(&alt_manifest_path))
311 .context("Failed to read manifest.json")?
312 } else {
313 return Err(anyhow::anyhow!("No manifest found in extension path"));
314 };
315
316 let manifest:serde_json::Value = serde_json::from_str(&manifest_content).context("Failed to parse manifest")?;
317
318 Ok(manifest)
319 }
320
321 fn extract_extension_id(&self, manifest:&serde_json::Value) -> Result<String> {
323 let publisher = manifest
324 .get("publisher")
325 .and_then(|v| v.as_str())
326 .ok_or_else(|| anyhow::anyhow!("Missing publisher in manifest"))?;
327
328 let name = manifest
329 .get("name")
330 .and_then(|v| v.as_str())
331 .ok_or_else(|| anyhow::anyhow!("Missing name in manifest"))?;
332
333 Ok(format!("{}.{}", publisher, name))
334 }
335
336 fn determine_extension_type(&self, path:&Path, manifest:&serde_json::Value) -> Result<ExtensionType> {
338 let wasm_path = path.join("extension.wasm");
340 if wasm_path.exists() {
341 return Ok(ExtensionType::WASM);
342 }
343
344 let cargo_path = path.join("Cargo.toml");
346 if cargo_path.exists() {
347 return Ok(ExtensionType::Native);
348 }
349
350 let main = manifest.get("main").and_then(|v| v.as_str());
352 if let Some(main) = main {
353 let main_path = path.join(main);
354 if main_path.exists() && (main.ends_with(".js") || main.ends_with(".ts")) {
355 return Ok(ExtensionType::JavaScript);
356 }
357 }
358
359 Ok(ExtensionType::Unknown)
360 }
361
362 fn extract_activation_events(&self, manifest:&serde_json::Value) -> Vec<String> {
364 manifest
365 .get("activationEvents")
366 .and_then(|v| v.as_array())
367 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
368 .unwrap_or_default()
369 }
370
371 fn extract_capabilities(&self, manifest:&serde_json::Value) -> Vec<String> {
373 manifest
374 .get("capabilities")
375 .and_then(|v| v.as_object())
376 .map(|obj| obj.keys().cloned().collect())
377 .unwrap_or_default()
378 }
379
380 fn extract_dependencies(&self, manifest:&serde_json::Value) -> Vec<String> {
382 manifest
383 .get("extensionDependencies")
384 .and_then(|v| v.as_array())
385 .map(|arr| arr.iter().filter_map(|v| v.as_str().map(|s| s.to_string())).collect())
386 .unwrap_or_default()
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393
394 #[test]
395 fn test_extension_type() {
396 assert_eq!(ExtensionType::WASM, ExtensionType::WASM);
397 assert_eq!(ExtensionType::Native, ExtensionType::Native);
398 assert_eq!(ExtensionType::JavaScript, ExtensionType::JavaScript);
399 }
400
401 #[test]
402 fn test_extension_state() {
403 assert_eq!(ExtensionState::Loaded, ExtensionState::Loaded);
404 assert_eq!(ExtensionState::Activated, ExtensionState::Activated);
405 assert_eq!(ExtensionState::Deactivated, ExtensionState::Deactivated);
406 assert_eq!(ExtensionState::Error, ExtensionState::Error);
407 }
408
409 #[tokio::test]
410 async fn test_extension_manager_creation() {
411 let wasm_runtime = Arc::new(
412 tokio::runtime::Runtime::new()
413 .unwrap()
414 .block_on(crate::WASM::Runtime::WASMRuntime::new(
415 crate::WASM::Runtime::WASMConfig::default(),
416 ))
417 .unwrap(),
418 );
419 let config = HostConfig::default();
420 let manager = ExtensionManagerImpl::new(wasm_runtime, config);
421
422 assert_eq!(manager.list_extensions().await.len(), 0);
423 }
424
425 #[test]
426 fn test_extension_stats_default() {
427 let stats = ExtensionStats::default();
428 assert_eq!(stats.total_loaded, 0);
429 assert_eq!(stats.total_activated, 0);
430 }
431}