1use std::borrow::Borrow;
7use std::fmt;
8use std::ops::Deref;
9use std::str::FromStr;
10
11use serde::{Deserialize, Deserializer, Serialize};
12
13use crate::error::{Error, Result};
14
15pub const HEADER_RUN_ID: &str = "workflow.run_id";
17pub const HEADER_STEP: &str = "workflow.step";
19pub const RESERVED_HEADER_PREFIX: &str = "workflow.";
23
24pub const RESERVED_KV_PREFIX: &str = "workflow/";
29
30pub const HEADER_TERMINAL: &str = "workflow.terminal";
34
35pub const HEADER_SIGNAL_WAIT: &str = "workflow.signal_wait";
38pub const HEADER_SIGNAL_DELIVERED: &str = "workflow.signal_delivered";
42
43pub const HEADER_GROUP: &str = "workflow.group";
46pub const HEADER_GROUP_KEY: &str = "workflow.group_key";
49
50pub(crate) const DEDUP_PREFIX: &str = "run:";
51
52pub const MAX_RUN_ID_LEN: usize = 128;
55
56pub(crate) const RUN_KV_PREFIX: &[u8] = b"workflow/runs/";
58
59pub(crate) const STEP_KV_PREFIX: &[u8] = b"workflow/steps/";
64
65pub(crate) const SIGNAL_WAIT_KV_PREFIX: &[u8] = b"workflow/signal-wait/";
68
69pub(crate) const SIGNAL_BUF_KV_PREFIX: &[u8] = b"workflow/signal-buf/";
72
73pub(crate) const SIGNAL_DELIVERED_KV_PREFIX: &[u8] = b"workflow/signal-delivered/";
77
78pub(crate) const TERMINAL_KV_PREFIX: &[u8] = b"workflow/terminals/";
82
83pub(crate) const OUTCOME_KV_PREFIX: &[u8] = b"workflow/outcomes/";
88
89pub(crate) const GROUP_KV_PREFIX: &[u8] = b"workflow/groups/";
94
95pub(crate) fn group_members_kv_prefix(group_id: &RunId) -> Vec<u8> {
97 prefixed(GROUP_KV_PREFIX, &format!("{group_id}/"))
98}
99
100pub(crate) fn group_member_kv_key(group_id: &RunId, key: &str) -> Vec<u8> {
102 prefixed(&group_members_kv_prefix(group_id), key)
103}
104
105pub(crate) const GROUP_TERMINAL_KV_PREFIX: &[u8] = b"workflow/group-terminals/";
109
110pub(crate) fn hash_input(input: &[u8]) -> [u8; 32] {
112 use sha2::{Digest, Sha256};
113 Sha256::digest(input).into()
114}
115
116pub(crate) fn hex_sha256(parts: &[&[u8]]) -> String {
118 use sha2::{Digest, Sha256};
119 use std::fmt::Write;
120 let mut hasher = Sha256::new();
121 for part in parts {
122 hasher.update(part);
123 }
124 let mut hex = String::with_capacity(64);
125 for byte in hasher.finalize() {
126 let _ = write!(&mut hex, "{byte:02x}");
127 }
128 hex
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
141#[serde(transparent)]
142pub struct RunId(String);
143
144impl RunId {
145 pub fn new(id: impl Into<String>) -> Result<Self> {
149 let id = id.into();
150 let reason = if id.is_empty() {
153 "run id must not be empty"
154 } else if id.len() > MAX_RUN_ID_LEN {
155 "run id exceeds maximum length of 128 bytes"
156 } else if !id
157 .bytes()
158 .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-')
159 {
160 "run id must contain only `[A-Za-z0-9_-]`"
161 } else {
162 return Ok(Self(id));
163 };
164 Err(Error::InvalidRunId { run_id: id, reason })
165 }
166
167 pub(crate) fn generate() -> Self {
169 Self(ulid::Ulid::new().to_string())
170 }
171
172 pub(crate) fn digest(parts: &[&[u8]]) -> Self {
175 Self(hex_sha256(parts))
176 }
177
178 pub fn as_str(&self) -> &str {
180 &self.0
181 }
182
183 pub fn into_string(self) -> String {
185 self.0
186 }
187}
188
189impl Deref for RunId {
190 type Target = str;
191
192 fn deref(&self) -> &str {
193 &self.0
194 }
195}
196
197impl AsRef<str> for RunId {
198 fn as_ref(&self) -> &str {
199 &self.0
200 }
201}
202
203impl Borrow<str> for RunId {
204 fn borrow(&self) -> &str {
205 &self.0
206 }
207}
208
209impl fmt::Display for RunId {
210 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
211 f.write_str(&self.0)
212 }
213}
214
215impl FromStr for RunId {
216 type Err = Error;
217
218 fn from_str(id: &str) -> Result<Self> {
219 Self::new(id)
220 }
221}
222
223impl PartialEq<str> for RunId {
224 fn eq(&self, other: &str) -> bool {
225 self.0 == other
226 }
227}
228
229impl PartialEq<&str> for RunId {
230 fn eq(&self, other: &&str) -> bool {
231 self.0 == *other
232 }
233}
234
235impl PartialEq<String> for RunId {
236 fn eq(&self, other: &String) -> bool {
237 &self.0 == other
238 }
239}
240
241impl From<RunId> for String {
242 fn from(id: RunId) -> Self {
243 id.0
244 }
245}
246
247impl<'de> Deserialize<'de> for RunId {
248 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> std::result::Result<Self, D::Error> {
249 let id = String::deserialize(deserializer)?;
250 Self::new(id).map_err(serde::de::Error::custom)
251 }
252}
253
254fn prefixed(prefix: &[u8], suffix: &str) -> Vec<u8> {
256 let mut k = Vec::with_capacity(prefix.len() + suffix.len());
257 k.extend_from_slice(prefix);
258 k.extend_from_slice(suffix.as_bytes());
259 k
260}
261
262pub(crate) fn run_kv_key(run_id: &RunId) -> Vec<u8> {
263 prefixed(RUN_KV_PREFIX, run_id)
264}
265
266pub(crate) fn step_kv_key(run_id: &RunId) -> Vec<u8> {
267 prefixed(STEP_KV_PREFIX, run_id)
268}
269
270pub(crate) fn outcome_kv_key(run_id: &RunId) -> Vec<u8> {
271 prefixed(OUTCOME_KV_PREFIX, run_id)
272}
273
274pub(crate) fn signal_wait_kv_key(correlation_key: &str) -> Vec<u8> {
275 prefixed(SIGNAL_WAIT_KV_PREFIX, correlation_key)
276}
277
278pub(crate) fn signal_buf_kv_key(correlation_key: &str) -> Vec<u8> {
279 prefixed(SIGNAL_BUF_KV_PREFIX, correlation_key)
280}
281
282pub(crate) fn signal_delivered_kv_key(run_id: &RunId, step_number: u32) -> Vec<u8> {
283 prefixed(
284 SIGNAL_DELIVERED_KV_PREFIX,
285 &format!("{run_id}/{step_number}"),
286 )
287}
288
289#[cfg(test)]
290mod tests {
291 use super::*;
292
293 #[test]
294 fn internal_kv_prefixes_are_under_the_reserved_prefix() {
295 for prefix in [
296 RUN_KV_PREFIX,
297 STEP_KV_PREFIX,
298 SIGNAL_WAIT_KV_PREFIX,
299 SIGNAL_BUF_KV_PREFIX,
300 SIGNAL_DELIVERED_KV_PREFIX,
301 TERMINAL_KV_PREFIX,
302 OUTCOME_KV_PREFIX,
303 GROUP_KV_PREFIX,
304 GROUP_TERMINAL_KV_PREFIX,
305 ] {
306 assert!(
307 prefix.starts_with(RESERVED_KV_PREFIX.as_bytes()),
308 "internal kv prefix `{}` is outside the reserved prefix",
309 String::from_utf8_lossy(prefix),
310 );
311 }
312 }
313
314 #[test]
315 fn run_id_rejects_empty_long_and_unsafe_ids() {
316 for bad in [
317 "",
318 "run/1",
319 "run 1",
320 "run:1",
321 &"a".repeat(MAX_RUN_ID_LEN + 1),
322 ] {
323 assert!(
324 matches!(RunId::new(bad), Err(Error::InvalidRunId { .. })),
325 "`{bad}` must be rejected",
326 );
327 }
328 assert!(RunId::new("a".repeat(MAX_RUN_ID_LEN)).is_ok());
329 assert!(rmp_serde::from_slice::<RunId>(&rmp_serde::to_vec("").unwrap()).is_err());
330 assert_eq!(
331 rmp_serde::from_slice::<RunId>(&rmp_serde::to_vec("run-1").unwrap()).unwrap(),
332 "run-1"
333 );
334 }
335}