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 entries
58 .entry("component.exec".to_string())
59 .or_insert(ComponentMetadata {
60 id: "component.exec".to_string(),
61 required_fields: Vec::new(),
62 });
63 continue;
64 }
65 }
66 }
68 ManifestCatalog { entries }
69 }
70}
71
72impl ComponentCatalog for ManifestCatalog {
73 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
74 self.entries.get(component_id).cloned()
75 }
76}
77
78#[derive(Debug, Default, Clone)]
80pub struct MemoryCatalog {
81 entries: HashMap<String, ComponentMetadata>,
82}
83
84impl MemoryCatalog {
85 pub fn insert(&mut self, meta: ComponentMetadata) {
86 self.entries.insert(meta.id.clone(), meta);
87 }
88}
89
90impl ComponentCatalog for MemoryCatalog {
91 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
92 self.entries.get(component_id).cloned()
93 }
94}
95
96impl ComponentCatalog for Box<dyn ComponentCatalog> {
97 fn resolve(&self, component_id: &str) -> Option<ComponentMetadata> {
98 self.as_ref().resolve(component_id)
99 }
100}
101
102pub fn normalize_manifest_value(value: &mut Value) {
104 if let Some(ops) = value.get_mut("operations").and_then(Value::as_array_mut) {
105 let mut normalized = Vec::with_capacity(ops.len());
106 for entry in ops.drain(..) {
107 if let Value::String(s) = entry {
108 normalized.push(json!({ "name": s }));
109 } else {
110 normalized.push(entry);
111 }
112 }
113 *ops = normalized;
114 }
115}