ontocore_plugin/
discovery.rs1use crate::manifest::{parse_manifest, DiscoveredPlugin};
2use std::fs;
3use std::path::{Path, PathBuf};
4use thiserror::Error;
5
6pub const PLUGIN_DIR: &str = ".ontocore/plugins";
8
9#[derive(Debug, Error)]
10pub enum PluginDiscoveryError {
11 #[error("IO error reading {path}: {source}")]
12 Io { path: PathBuf, source: std::io::Error },
13 #[error("invalid manifest {path}: {message}")]
14 InvalidManifest { path: PathBuf, message: String },
15}
16
17pub fn discover_plugins(workspace: &Path) -> Result<Vec<DiscoveredPlugin>, PluginDiscoveryError> {
19 let plugins_dir = workspace.join(PLUGIN_DIR);
20 if !plugins_dir.is_dir() {
21 return Ok(Vec::new());
22 }
23
24 let mut discovered = Vec::new();
25 for entry in fs::read_dir(&plugins_dir)
26 .map_err(|source| PluginDiscoveryError::Io { path: plugins_dir.clone(), source })?
27 {
28 let entry = entry
29 .map_err(|source| PluginDiscoveryError::Io { path: plugins_dir.clone(), source })?;
30 let path = entry.path();
31 if path.extension().and_then(|e| e.to_str()) != Some("toml") {
32 continue;
33 }
34 let text = fs::read_to_string(&path)
35 .map_err(|source| PluginDiscoveryError::Io { path: path.clone(), source })?;
36 let manifest = parse_manifest(&text).map_err(|e| {
37 PluginDiscoveryError::InvalidManifest { path: path.clone(), message: e.to_string() }
38 })?;
39 discovered.push(DiscoveredPlugin { manifest, manifest_path: path });
40 }
41
42 discovered.sort_by(|a, b| a.manifest.name.cmp(&b.manifest.name));
43 Ok(discovered)
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 #[test]
51 fn discovers_manifests_in_workspace() {
52 let dir = tempfile::tempdir().unwrap();
53 let plugins = dir.path().join(PLUGIN_DIR);
54 fs::create_dir_all(&plugins).unwrap();
55 fs::write(
56 plugins.join("demo.toml"),
57 r#"
58[plugin]
59name = "demo"
60version = "0.1.0"
61kind = "validator"
62"#,
63 )
64 .unwrap();
65
66 let found = discover_plugins(dir.path()).expect("discover");
67 assert_eq!(found.len(), 1);
68 assert_eq!(found[0].manifest.name, "demo");
69 }
70
71 #[test]
72 fn empty_when_plugin_dir_missing() {
73 let dir = tempfile::tempdir().unwrap();
74 let found = discover_plugins(dir.path()).expect("discover");
75 assert!(found.is_empty());
76 }
77}