use std::sync::atomic::{AtomicU64, Ordering};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ToolOutcome {
Succeeded,
Failed,
Blocked,
Denied,
Indeterminate,
}
impl ToolOutcome {
pub fn wire(self) -> &'static str {
match self {
Self::Succeeded => "succeeded",
Self::Failed => "failed",
Self::Blocked => "blocked",
Self::Denied => "denied",
Self::Indeterminate => "indeterminate",
}
}
}
static MINTED: AtomicU64 = AtomicU64::new(0);
pub fn mint_execution_id() -> String {
format!("x{}", minted_suffix())
}
fn minted_suffix() -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|since| since.as_nanos())
.unwrap_or_default();
let seq = MINTED.fetch_add(1, Ordering::Relaxed);
format!("{nanos:016x}-{seq:08x}")
}
pub fn mint_visit_id() -> String {
format!("v{}", minted_suffix())
}
pub fn mint_attempt_id() -> String {
format!("a{}", minted_suffix())
}
#[cfg(test)]
mod tests {
use super::{ToolOutcome, mint_execution_id, mint_visit_id};
#[test]
fn minted_ids_are_unique_and_ordered() {
let ids: Vec<String> = (0..64).map(|_| mint_execution_id()).collect();
let unique: std::collections::HashSet<&String> = ids.iter().collect();
assert_eq!(unique.len(), ids.len(), "no two alike: {ids:?}");
let mut sorted = ids.clone();
sorted.sort();
assert_eq!(sorted, ids, "minted in order, so they read in order");
let (clock, seq) = ids[0]
.strip_prefix('x')
.expect("an execution id says which kind it is")
.split_once('-')
.expect("two parts");
assert_eq!(clock.len(), 16, "{clock}");
assert_eq!(seq.len(), 8, "{seq}");
}
#[test]
fn the_kinds_of_id_are_told_apart_on_sight() {
assert!(mint_execution_id().starts_with('x'));
assert!(mint_visit_id().starts_with('v'));
assert!(super::mint_attempt_id().starts_with('a'));
}
#[test]
fn each_outcome_has_its_own_word() {
assert_eq!(ToolOutcome::Succeeded.wire(), "succeeded");
assert_eq!(ToolOutcome::Failed.wire(), "failed");
assert_eq!(ToolOutcome::Blocked.wire(), "blocked");
assert_eq!(ToolOutcome::Denied.wire(), "denied");
assert_eq!(ToolOutcome::Indeterminate.wire(), "indeterminate");
}
#[test]
fn an_outcome_round_trips_as_its_word() {
for outcome in [
ToolOutcome::Succeeded,
ToolOutcome::Failed,
ToolOutcome::Blocked,
ToolOutcome::Denied,
ToolOutcome::Indeterminate,
] {
let json = serde_json::to_string(&outcome).expect("it serializes");
assert_eq!(json, format!("\"{}\"", outcome.wire()));
let back: ToolOutcome = serde_json::from_str(&json).expect("it reads back");
assert_eq!(back, outcome);
}
}
}