greentic_flow/
component_catalog.rs1use std::{collections::HashMap, fs, path::Path};
2
3use serde::Deserialize;
4use serde_json::{Value, json};
5#[derive(Debug, Clone)]
8pub struct ComponentMetadata {
9 pub id: String,
10 pub required_fields: Vec<String>,
11}
12
13pub trait ComponentCatalog: Send + Sync {
14 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata>;
15}
16
17#[derive(Debug, Default, Clone)]
19pub struct ManifestCatalog {
20 entries: HashMap<String, ComponentMetadata>,
21}
22
23#[derive(Deserialize)]
24struct Manifest {
25 id: String,
26 #[serde(default)]
27 config_schema: Option<Schema>,
28}
29
30#[derive(Deserialize, Default)]
31struct Schema {
32 #[serde(default)]
33 required: Vec<String>,
34}
35
36impl ManifestCatalog {
37 pub fn load_from_paths(paths: &[impl AsRef<Path>]) -> Self {
38 let mut entries = HashMap::new();
39 for path in paths {
40 let path = path.as_ref();
41 if let Ok(text) = fs::read_to_string(path)
42 && let Ok(mut value) = serde_json::from_str::<Value>(&text)
43 {
44 normalize_manifest_value(&mut value);
45 if let Ok(manifest) = serde_json::from_value::<Manifest>(value) {
46 entries.insert(
47 manifest.id.clone(),
48 ComponentMetadata {
49 id: manifest.id,
50 required_fields: manifest
51 .config_schema
52 .unwrap_or_default()
53 .required
54 .clone(),
55 },
56 );
57 continue;
58 }
59 }
60 }
62 ManifestCatalog { entries }
63 }
64}
65
66impl ComponentCatalog for ManifestCatalog {
67 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
68 self.entries.get(component_id).cloned()
69 }
70}
71
72#[derive(Debug, Default, Clone)]
74pub struct MemoryCatalog {
75 entries: HashMap<String, ComponentMetadata>,
76}
77
78impl MemoryCatalog {
79 pub fn insert(&mut self, meta: ComponentMetadata) {
80 self.entries.insert(meta.id.clone(), meta);
81 }
82}
83
84impl ComponentCatalog for MemoryCatalog {
85 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
86 self.entries.get(component_id).cloned()
87 }
88}
89
90impl ComponentCatalog for Box<dyn ComponentCatalog> {
91 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
92 self.as_ref().resolve(component_id)
93 }
94}
95
96pub fn normalize_manifest_value(value: &mut Value) {
98 if let Some(ops) = value.get_mut("operations").and_then(Value::as_array_mut) {
99 let mut normalized = Vec::with_capacity(ops.len());
100 for entry in ops.drain(..) {
101 if let Value::String(s) = entry {
102 normalized.push(json!({ "name": s }));
103 } else {
104 normalized.push(entry);
105 }
106 }
107 *ops = normalized;
108 }
109}