Skip to main content

worldinterface_core/
id.rs

1use std::fmt;
2use std::str::FromStr;
3
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7macro_rules! define_id {
8    ($name:ident, $doc:expr) => {
9        #[doc = $doc]
10        #[derive(
11            Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize,
12        )]
13        #[serde(transparent)]
14        pub struct $name(Uuid);
15
16        impl Default for $name {
17            fn default() -> Self {
18                Self::new()
19            }
20        }
21
22        impl $name {
23            pub fn new() -> Self {
24                Self(Uuid::new_v4())
25            }
26        }
27
28        impl From<Uuid> for $name {
29            fn from(uuid: Uuid) -> Self {
30                Self(uuid)
31            }
32        }
33
34        impl AsRef<Uuid> for $name {
35            fn as_ref(&self) -> &Uuid {
36                &self.0
37            }
38        }
39
40        impl fmt::Display for $name {
41            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42                write!(f, "{}", self.0)
43            }
44        }
45
46        impl FromStr for $name {
47            type Err = uuid::Error;
48            fn from_str(s: &str) -> Result<Self, Self::Err> {
49                Ok(Self(s.parse::<Uuid>()?))
50            }
51        }
52    };
53}
54
55define_id!(FlowId, "Identity of a named flow definition.");
56define_id!(FlowRunId, "Identity of a flow execution instance.");
57define_id!(NodeId, "Identity of a node within a FlowSpec.");
58define_id!(StepRunId, "Identity of a step execution (wraps an ActionQueue RunId conceptually).");
59
60/// Well-known NodeId for webhook trigger input data in ContextStore.
61///
62/// This is a deterministic UUID v5 derived from the URL namespace and a fixed name.
63/// Flow nodes reference trigger data via `{{trigger.body}}`, `{{trigger.headers}}`, etc.
64/// Internally, the parameter resolver reads from ContextStore at
65/// `(flow_run_id, trigger_input_node_id())`.
66pub fn trigger_input_node_id() -> NodeId {
67    NodeId::from(Uuid::new_v5(&Uuid::NAMESPACE_URL, b"wi:trigger:input"))
68}
69
70#[cfg(test)]
71mod tests {
72    use std::collections::hash_map::DefaultHasher;
73    use std::hash::{Hash, Hasher};
74
75    use super::*;
76
77    #[test]
78    fn ids_are_unique() {
79        let a = FlowRunId::new();
80        let b = FlowRunId::new();
81        assert_ne!(a, b);
82    }
83
84    #[test]
85    fn id_roundtrips_through_json() {
86        let id = FlowRunId::new();
87        let json = serde_json::to_string(&id).unwrap();
88        let back: FlowRunId = serde_json::from_str(&json).unwrap();
89        assert_eq!(id, back);
90    }
91
92    #[test]
93    fn id_display_is_uuid() {
94        let id = NodeId::new();
95        let display = id.to_string();
96        Uuid::parse_str(&display).unwrap();
97    }
98
99    #[test]
100    fn id_from_uuid_roundtrip() {
101        let uuid = Uuid::new_v4();
102        let id = FlowId::from(uuid);
103        assert_eq!(*id.as_ref(), uuid);
104    }
105
106    #[test]
107    fn equal_ids_from_same_uuid() {
108        let uuid = Uuid::new_v4();
109        let a = NodeId::from(uuid);
110        let b = NodeId::from(uuid);
111        assert_eq!(a, b);
112    }
113
114    #[test]
115    fn id_from_str_roundtrip() {
116        let id = FlowRunId::new();
117        let s = id.to_string();
118        let parsed: FlowRunId = s.parse().unwrap();
119        assert_eq!(id, parsed);
120    }
121
122    #[test]
123    fn id_from_str_rejects_invalid() {
124        let result = "not-a-uuid".parse::<FlowRunId>();
125        assert!(result.is_err());
126    }
127
128    #[test]
129    fn trigger_input_node_id_is_deterministic() {
130        let a = trigger_input_node_id();
131        let b = trigger_input_node_id();
132        assert_eq!(a, b);
133    }
134
135    #[test]
136    fn trigger_input_node_id_is_not_nil() {
137        let id = trigger_input_node_id();
138        assert_ne!(*id.as_ref(), Uuid::nil());
139    }
140
141    #[test]
142    fn equal_ids_have_same_hash() {
143        let uuid = Uuid::new_v4();
144        let a = StepRunId::from(uuid);
145        let b = StepRunId::from(uuid);
146
147        let hash = |id: &StepRunId| {
148            let mut h = DefaultHasher::new();
149            id.hash(&mut h);
150            h.finish()
151        };
152        assert_eq!(hash(&a), hash(&b));
153    }
154}