Skip to main content

oharness_core/
trajectory.rs

1//! `TrajectoryHandle` (§9.4) lives here in core so `RunOutcome` can carry it without
2//! every downstream crate depending on `oharness-trace`. The concrete *sources* a
3//! handle can point at (file, in-memory, stream) are enumerated here; the trace
4//! crate supplies the machinery that reads them.
5
6use crate::event::Event;
7use serde::{Deserialize, Serialize};
8use std::path::PathBuf;
9use std::sync::Arc;
10
11/// Read-only reference to a trajectory. Serializes as a path/URI — never inlines
12/// the event stream. In-memory handles error on serialization unless materialized
13/// to a file first.
14#[derive(Clone)]
15pub struct TrajectoryHandle {
16    source: TrajectorySource,
17}
18
19#[derive(Clone)]
20pub enum TrajectorySource {
21    File(PathBuf),
22    InMemory(Arc<Vec<Event>>),
23}
24
25impl std::fmt::Debug for TrajectoryHandle {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match &self.source {
28            TrajectorySource::File(p) => write!(f, "TrajectoryHandle::File({})", p.display()),
29            TrajectorySource::InMemory(v) => {
30                write!(f, "TrajectoryHandle::InMemory(len={})", v.len())
31            }
32        }
33    }
34}
35
36impl TrajectoryHandle {
37    pub fn from_path(path: impl Into<PathBuf>) -> Self {
38        Self {
39            source: TrajectorySource::File(path.into()),
40        }
41    }
42
43    pub fn in_memory(events: Vec<Event>) -> Self {
44        Self {
45            source: TrajectorySource::InMemory(Arc::new(events)),
46        }
47    }
48
49    pub fn source(&self) -> &TrajectorySource {
50        &self.source
51    }
52
53    /// Returns `Some(path)` for file-backed handles, `None` for in-memory.
54    pub fn path(&self) -> Option<&std::path::Path> {
55        match &self.source {
56            TrajectorySource::File(p) => Some(p),
57            TrajectorySource::InMemory(_) => None,
58        }
59    }
60
61    pub fn summarize(&self) -> TrajectorySummary {
62        match &self.source {
63            TrajectorySource::File(p) => TrajectorySummary {
64                event_count: None,
65                path: Some(p.clone()),
66                in_memory: false,
67            },
68            TrajectorySource::InMemory(v) => TrajectorySummary {
69                event_count: Some(v.len()),
70                path: None,
71                in_memory: true,
72            },
73        }
74    }
75
76    /// In-memory handles return their events inline. File handles require callers
77    /// to read via `oharness-trace`; this helper is not available here to keep
78    /// core IO-free.
79    pub fn in_memory_events(&self) -> Option<&Arc<Vec<Event>>> {
80        match &self.source {
81            TrajectorySource::InMemory(v) => Some(v),
82            TrajectorySource::File(_) => None,
83        }
84    }
85}
86
87impl Serialize for TrajectoryHandle {
88    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
89        use serde::ser::SerializeMap;
90        match &self.source {
91            TrajectorySource::File(p) => {
92                let mut m = s.serialize_map(Some(2))?;
93                m.serialize_entry("type", "file")?;
94                m.serialize_entry("path", p)?;
95                m.end()
96            }
97            TrajectorySource::InMemory(_) => Err(serde::ser::Error::custom(
98                "in-memory TrajectoryHandle cannot be serialized; materialize to a file first",
99            )),
100        }
101    }
102}
103
104impl<'de> Deserialize<'de> for TrajectoryHandle {
105    fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
106        #[derive(Deserialize)]
107        struct Wire {
108            #[serde(rename = "type")]
109            kind: String,
110            path: Option<PathBuf>,
111        }
112        let w = Wire::deserialize(d)?;
113        match w.kind.as_str() {
114            "file" => {
115                let p = w
116                    .path
117                    .ok_or_else(|| serde::de::Error::custom("file trajectory missing `path`"))?;
118                Ok(TrajectoryHandle::from_path(p))
119            }
120            other => Err(serde::de::Error::custom(format!(
121                "unknown trajectory source `{other}`"
122            ))),
123        }
124    }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128pub struct TrajectorySummary {
129    pub event_count: Option<usize>,
130    pub path: Option<PathBuf>,
131    pub in_memory: bool,
132}
133
134#[derive(Debug, thiserror::Error)]
135pub enum TrajectoryError {
136    #[error("trajectory I/O: {0}")]
137    Io(#[from] std::io::Error),
138    #[error("trajectory JSON decode: {0}")]
139    Decode(#[from] serde_json::Error),
140    #[error("payload sidecar missing: {0}")]
141    PayloadNotFound(String),
142    #[error("unsupported trajectory source for this operation")]
143    Unsupported,
144}