worldinterface_core/
id.rs1use 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
60pub 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}