greentic_flow/
registry.rs

1use anyhow::Context;
2use serde::{Deserialize, Serialize};
3use std::{
4    collections::{HashMap, HashSet},
5    env, fs,
6    path::Path,
7};
8
9use crate::path_safety::normalize_under_root;
10
11/// Catalog of known adapters and their supported operations.
12#[derive(Clone, Debug, Serialize, Deserialize, Default)]
13pub struct AdapterCatalog {
14    /// Map of `<namespace>.<adapter>` to the operations that adapter exposes.
15    pub adapters: HashMap<String, HashSet<String>>,
16}
17
18impl AdapterCatalog {
19    /// Load a registry from disk, accepting JSON by default and TOML when the `toml` feature is enabled.
20    pub fn load_from_file(path: impl AsRef<Path>) -> anyhow::Result<Self> {
21        let path_ref = path.as_ref();
22        let registry_root = env::current_dir().context("unable to resolve registry root")?;
23        let safe_path = normalize_under_root(&registry_root, path_ref)?;
24        let txt = fs::read_to_string(&safe_path).with_context(|| {
25            format!("unable to read adapter registry at {}", safe_path.display())
26        })?;
27        if let Ok(value) = serde_json::from_str::<Self>(&txt) {
28            return Ok(value);
29        }
30
31        #[cfg(feature = "toml")]
32        {
33            if let Ok(value) = toml::from_str::<Self>(&txt) {
34                return Ok(value);
35            }
36        }
37
38        #[cfg(feature = "toml")]
39        {
40            anyhow::bail!(
41                "unsupported registry format in {}: expected JSON or TOML",
42                path_ref.display()
43            );
44        }
45
46        #[cfg(not(feature = "toml"))]
47        {
48            anyhow::bail!(
49                "unsupported registry format in {}: expected JSON (enable `toml` feature for TOML support)",
50                path_ref.display()
51            );
52        }
53    }
54
55    /// Check if the catalog contains the given adapter operation.
56    pub fn contains(&self, namespace: &str, adapter: &str, operation: &str) -> bool {
57        let key = format!("{namespace}.{adapter}");
58        self.adapters
59            .get(&key)
60            .map(|ops| ops.contains(operation))
61            .unwrap_or(false)
62    }
63}