use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::ids::{EventId, TraceId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TraceStatus {
Open,
Closed,
Quarantined,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct Trace {
pub id: TraceId,
pub schema_version: u16,
pub opened_at: DateTime<Utc>,
pub closed_at: Option<DateTime<Utc>>,
pub event_ids: Vec<EventId>,
pub trace_type: String,
pub status: TraceStatus,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::SCHEMA_VERSION;
use chrono::TimeZone;
fn fixture_trace() -> Trace {
Trace {
id: "trc_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap(),
schema_version: SCHEMA_VERSION,
opened_at: Utc.with_ymd_and_hms(2026, 1, 1, 12, 0, 0).unwrap(),
closed_at: Some(Utc.with_ymd_and_hms(2026, 1, 1, 12, 5, 0).unwrap()),
event_ids: vec![
"evt_01ARZ3NDEKTSV4RRFFQ69G5FAV".parse().unwrap(),
"evt_01ARZ3NDEKTSV4RRFFQ69G5FAW".parse().unwrap(),
],
trace_type: "agent_run".into(),
status: TraceStatus::Closed,
}
}
#[test]
fn trace_serde_round_trip() {
let t = fixture_trace();
let j = serde_json::to_value(&t).expect("serialize");
let back: Trace = serde_json::from_value(j.clone()).expect("deserialize");
assert_eq!(t, back);
let obj = j.as_object().expect("trace serializes as object");
for k in [
"id",
"schema_version",
"opened_at",
"closed_at",
"event_ids",
"trace_type",
"status",
] {
assert!(obj.contains_key(k), "trace JSON missing field `{k}`");
}
assert_eq!(obj["status"], serde_json::json!("closed"));
}
#[test]
fn trace_status_wire_strings() {
assert_eq!(
serde_json::to_value(TraceStatus::Open).unwrap(),
serde_json::json!("open")
);
assert_eq!(
serde_json::to_value(TraceStatus::Closed).unwrap(),
serde_json::json!("closed")
);
assert_eq!(
serde_json::to_value(TraceStatus::Quarantined).unwrap(),
serde_json::json!("quarantined")
);
}
#[test]
fn open_trace_round_trips_with_null_closed_at() {
let mut t = fixture_trace();
t.status = TraceStatus::Open;
t.closed_at = None;
let j = serde_json::to_value(&t).unwrap();
assert_eq!(j["closed_at"], serde_json::Value::Null);
let back: Trace = serde_json::from_value(j).unwrap();
assert_eq!(t, back);
}
}