ergo_runtime/common/
intent_id.rs1use sha2::{Digest, Sha256};
2
3pub fn derive_intent_id(
5 graph_id: &str,
6 event_id: &str,
7 node_runtime_id: &str,
8 intent_kind: &str,
9 intent_ordinal: usize,
10) -> String {
11 let version_tag = "eid1";
12 let ordinal = intent_ordinal.to_string();
13 let mut bytes = Vec::new();
14 for segment in [
15 version_tag,
16 graph_id,
17 event_id,
18 node_runtime_id,
19 intent_kind,
20 ordinal.as_str(),
21 ] {
22 push_len_prefixed(&mut bytes, segment);
23 }
24
25 let mut hasher = Sha256::new();
26 hasher.update(&bytes);
27 let digest = hasher.finalize();
28 format!("eid1:sha256:{}", to_hex(&digest))
29}
30
31fn push_len_prefixed(out: &mut Vec<u8>, segment: &str) {
32 let bytes = segment.as_bytes();
33 let len = u32::try_from(bytes.len()).expect("intent_id segment exceeds u32 max length");
34 out.extend_from_slice(&len.to_be_bytes());
35 out.extend_from_slice(bytes);
36}
37
38fn to_hex(bytes: &[u8]) -> String {
39 let mut out = String::with_capacity(bytes.len() * 2);
40 for byte in bytes {
41 use std::fmt::Write as _;
42 let _ = write!(&mut out, "{byte:02x}");
43 }
44 out
45}
46
47#[cfg(test)]
48mod tests {
49 use super::derive_intent_id;
50
51 #[test]
52 fn derive_intent_id_is_deterministic() {
53 let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
54 let id2 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
55 assert_eq!(id1, id2);
56 }
57
58 #[test]
59 fn derive_intent_id_changes_with_event_id() {
60 let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
61 let id2 = derive_intent_id("g1", "evt2", "n0", "place_order", 0);
62 assert_ne!(id1, id2);
63 }
64
65 #[test]
66 fn derive_intent_id_changes_with_node_runtime_id() {
67 let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
68 let id2 = derive_intent_id("g1", "evt1", "n1", "place_order", 0);
69 assert_ne!(id1, id2);
70 }
71
72 #[test]
73 fn derive_intent_id_changes_with_intent_ordinal() {
74 let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
75 let id2 = derive_intent_id("g1", "evt1", "n0", "place_order", 1);
76 assert_ne!(id1, id2);
77 }
78
79 #[test]
80 fn derive_intent_id_golden_regression() {
81 let actual = derive_intent_id("graph_alpha", "evt_001", "n42", "place_order", 3);
82 let expected =
83 "eid1:sha256:157aed720bd20c1712cede0a499cf6607f687401a29d70ce5df5d2778f85a9fa";
84 assert_eq!(actual, expected);
85 }
86}