Skip to main content

greentic_flow/
flow_meta.rs

1use anyhow::{Result, anyhow};
2use serde_json::Value;
3use std::time::{SystemTime, UNIX_EPOCH};
4
5pub const META_NAMESPACE: &str = "greentic";
6
7pub fn now_epoch_seconds() -> u64 {
8    SystemTime::now()
9        .duration_since(UNIX_EPOCH)
10        .map(|d| d.as_secs())
11        .unwrap_or(0)
12}
13
14fn ensure_object(value: &mut Option<Value>) -> &mut serde_json::Map<String, Value> {
15    if !matches!(value, Some(Value::Object(_))) {
16        *value = Some(Value::Object(serde_json::Map::new()));
17    }
18    match value.as_mut().unwrap() {
19        Value::Object(map) => map,
20        _ => unreachable!(),
21    }
22}
23
24fn ensure_child_map<'a>(
25    parent: &'a mut serde_json::Map<String, Value>,
26    key: &str,
27) -> &'a mut serde_json::Map<String, Value> {
28    let entry = parent
29        .entry(key.to_string())
30        .or_insert_with(|| Value::Object(serde_json::Map::new()));
31    match entry {
32        Value::Object(map) => map,
33        _ => {
34            *entry = Value::Object(serde_json::Map::new());
35            match entry {
36                Value::Object(map) => map,
37                _ => unreachable!(),
38            }
39        }
40    }
41}
42
43pub fn ensure_greentic_meta(meta: &mut Option<Value>) -> &mut serde_json::Map<String, Value> {
44    let root = ensure_object(meta);
45    ensure_child_map(root, META_NAMESPACE)
46}
47
48pub fn set_component_entry(
49    meta: &mut Option<Value>,
50    node_id: &str,
51    component_id: &str,
52    abi_version: &str,
53    digest: Option<&str>,
54    exported_ops: &[String],
55    contract: Option<&ComponentContractMeta>,
56) {
57    let greentic = ensure_greentic_meta(meta);
58    let components = ensure_child_map(greentic, "components");
59    let mut added_at = now_epoch_seconds();
60    if let Some(Value::Object(existing)) = components.get(node_id)
61        && let Some(Value::Number(number)) = existing.get("added_at")
62        && let Some(value) = number.as_u64()
63    {
64        added_at = value;
65    }
66    let mut entry = serde_json::Map::new();
67    entry.insert(
68        "component_id".to_string(),
69        Value::String(component_id.to_string()),
70    );
71    entry.insert(
72        "abi_version".to_string(),
73        Value::String(abi_version.to_string()),
74    );
75    if let Some(contract) = contract {
76        entry.insert(
77            "describe_hash".to_string(),
78            Value::String(contract.describe_hash.clone()),
79        );
80        entry.insert(
81            "operation_id".to_string(),
82            Value::String(contract.operation_id.clone()),
83        );
84        entry.insert(
85            "schema_hash".to_string(),
86            Value::String(contract.schema_hash.clone()),
87        );
88        if let Some(version) = &contract.component_version {
89            entry.insert(
90                "component_version".to_string(),
91                Value::String(version.clone()),
92            );
93        }
94        if let Some(world) = &contract.world {
95            entry.insert("world".to_string(), Value::String(world.clone()));
96        }
97        if let Some(config_schema_cbor) = &contract.config_schema_cbor {
98            entry.insert(
99                "config_schema_cbor".to_string(),
100                Value::String(config_schema_cbor.clone()),
101            );
102        }
103    }
104    if let Some(d) = digest {
105        entry.insert("resolved_digest".to_string(), Value::String(d.to_string()));
106    }
107    entry.insert(
108        "exported_ops_seen".to_string(),
109        Value::Array(
110            exported_ops
111                .iter()
112                .map(|s| Value::String(s.clone()))
113                .collect(),
114        ),
115    );
116    entry.insert(
117        "added_at".to_string(),
118        Value::Number(serde_json::Number::from(added_at)),
119    );
120    entry.insert(
121        "updated_at".to_string(),
122        Value::Number(serde_json::Number::from(now_epoch_seconds())),
123    );
124    components.insert(node_id.to_string(), Value::Object(entry));
125}
126
127pub struct ComponentContractMeta {
128    pub describe_hash: String,
129    pub operation_id: String,
130    pub schema_hash: String,
131    pub component_version: Option<String>,
132    pub world: Option<String>,
133    pub config_schema_cbor: Option<String>,
134}
135
136pub fn clear_component_entry(meta: &mut Option<Value>, node_id: &str) {
137    let Some(Value::Object(root)) = meta else {
138        return;
139    };
140    let Some(Value::Object(greentic)) = root.get_mut(META_NAMESPACE) else {
141        return;
142    };
143    if let Some(Value::Object(components)) = greentic.get_mut("components") {
144        components.remove(node_id);
145    }
146    if let Some(Value::Object(secrets)) = greentic.get_mut("secrets_hints") {
147        secrets.remove(node_id);
148    }
149    if let Some(Value::Object(bindings)) = greentic.get_mut("bindings_hints") {
150        bindings.remove(node_id);
151    }
152}
153
154pub fn ensure_hints_empty(meta: &mut Option<Value>, node_id: &str) {
155    let greentic = ensure_greentic_meta(meta);
156    {
157        let secrets = ensure_child_map(greentic, "secrets_hints");
158        secrets
159            .entry(node_id.to_string())
160            .or_insert_with(|| Value::Array(Vec::new()));
161    }
162    {
163        let bindings = ensure_child_map(greentic, "bindings_hints");
164        bindings
165            .entry(node_id.to_string())
166            .or_insert_with(|| Value::Array(Vec::new()));
167    }
168}
169
170pub fn find_node_for_component(meta: &Option<Value>, component_id: &str) -> Result<String> {
171    let Some(Value::Object(root)) = meta else {
172        return Err(anyhow!("flow metadata missing; provide --step"));
173    };
174    let Some(Value::Object(greentic)) = root.get(META_NAMESPACE) else {
175        return Err(anyhow!("flow metadata missing; provide --step"));
176    };
177    let Some(Value::Object(components)) = greentic.get("components") else {
178        return Err(anyhow!("flow metadata missing; provide --step"));
179    };
180    let mut matches = Vec::new();
181    for (node_id, entry) in components {
182        if let Value::Object(obj) = entry
183            && obj
184                .get("component_id")
185                .and_then(Value::as_str)
186                .is_some_and(|id| id == component_id)
187        {
188            matches.push(node_id.clone());
189        }
190    }
191    match matches.len() {
192        0 => Err(anyhow!(
193            "no node found for component id '{component_id}'; provide --step"
194        )),
195        1 => Ok(matches.remove(0)),
196        _ => Err(anyhow!(
197            "multiple nodes found for component id '{component_id}'; provide --step"
198        )),
199    }
200}