use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunId(String);
impl RunId {
#[must_use]
pub fn new() -> Self {
let now = Utc::now();
let base = now.format("%Y-%m-%d_%H-%M-%S%.3fZ").to_string();
Self(base)
}
#[must_use]
pub fn for_test(id: &str) -> Self {
Self(id.to_string())
}
#[must_use]
pub fn from_checkpoint(id: &str) -> Self {
Self(id.to_string())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
#[must_use]
pub fn with_collision_counter(&self, counter: u32) -> Self {
Self(format!("{}-{:02}", self.0, counter))
}
}
impl fmt::Display for RunId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl Default for RunId {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_run_id_format() {
let run_id = RunId::new();
let s = run_id.as_str();
assert!(s.len() >= 24, "Run ID should be at least 24 chars");
assert!(s.ends_with('Z'), "Run ID should end with Z");
assert!(
s.contains('_'),
"Run ID should contain underscore separator"
);
assert!(
s.contains('-'),
"Run ID should contain date/time separators"
);
assert!(
s.contains('.'),
"Run ID should contain millisecond separator"
);
}
#[test]
fn test_run_id_from_checkpoint() {
let original = "2026-02-06_14-03-27.123Z";
let run_id = RunId::from_checkpoint(original);
assert_eq!(run_id.as_str(), original);
}
#[test]
fn test_run_id_with_collision_counter() {
let base = RunId::new();
let collided = base.with_collision_counter(1);
assert!(
collided.as_str().ends_with("-01"),
"Collision counter should be appended"
);
assert!(collided.as_str().starts_with(base.as_str()));
}
#[test]
fn test_run_id_display() {
let run_id = RunId::new();
let displayed = format!("{run_id}");
assert_eq!(displayed, run_id.as_str());
}
#[test]
fn test_run_id_sortable() {
let earlier = RunId::for_test("2024-01-01_00-00-01.000Z");
let later = RunId::for_test("2024-01-01_00-00-02.000Z");
assert!(
earlier.as_str() < later.as_str(),
"RunId format should be lexicographically sortable in chronological order"
);
}
}