Skip to main content

ontocore_plugin/
discovery.rs

1use crate::manifest::{parse_manifest, DiscoveredPlugin};
2use std::fs;
3use std::path::{Path, PathBuf};
4use thiserror::Error;
5
6/// Relative directory scanned for plugin manifests inside a workspace.
7pub 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
17/// Discover plugin manifests under `{workspace}/.ontocore/plugins/*.toml`.
18pub 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}