ralph_workflow/logging/
run_id.rs1use chrono::Utc;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct RunId(String);
16
17impl RunId {
18 pub fn new() -> Self {
22 let now = Utc::now();
23 let base = now.format("%Y-%m-%d_%H-%M-%S%.3fZ").to_string();
24 Self(base)
25 }
26
27 pub fn for_test(id: &str) -> Self {
38 Self(id.to_string())
39 }
40
41 pub fn from_checkpoint(id: &str) -> Self {
46 Self(id.to_string())
47 }
48
49 pub fn as_str(&self) -> &str {
51 &self.0
52 }
53
54 pub fn with_collision_counter(&self, counter: u32) -> Self {
65 Self(format!("{}-{:02}", self.0, counter))
66 }
67}
68
69impl fmt::Display for RunId {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "{}", self.0)
72 }
73}
74
75impl Default for RunId {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_run_id_format() {
87 let run_id = RunId::new();
88 let s = run_id.as_str();
89
90 assert!(s.len() >= 24, "Run ID should be at least 24 chars");
93 assert!(s.ends_with('Z'), "Run ID should end with Z");
94 assert!(
95 s.contains('_'),
96 "Run ID should contain underscore separator"
97 );
98 assert!(
99 s.contains('-'),
100 "Run ID should contain date/time separators"
101 );
102 assert!(
103 s.contains('.'),
104 "Run ID should contain millisecond separator"
105 );
106 }
107
108 #[test]
109 fn test_run_id_from_checkpoint() {
110 let original = "2026-02-06_14-03-27.123Z";
111 let run_id = RunId::from_checkpoint(original);
112 assert_eq!(run_id.as_str(), original);
113 }
114
115 #[test]
116 fn test_run_id_with_collision_counter() {
117 let base = RunId::new();
118 let collided = base.with_collision_counter(1);
119
120 assert!(
121 collided.as_str().ends_with("-01"),
122 "Collision counter should be appended"
123 );
124 assert!(collided.as_str().starts_with(base.as_str()));
125 }
126
127 #[test]
128 fn test_run_id_display() {
129 let run_id = RunId::new();
130 let displayed = format!("{}", run_id);
131 assert_eq!(displayed, run_id.as_str());
132 }
133
134 #[test]
135 fn test_run_id_sortable() {
136 let first = RunId::new();
138 std::thread::sleep(std::time::Duration::from_millis(10));
139 let second = RunId::new();
140
141 assert!(
143 first.as_str() < second.as_str(),
144 "Run IDs should sort chronologically"
145 );
146 }
147}