Skip to main content

trueno_db/experiment/
run_record.rs

1//! Run Record - execution instance of an experiment
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// Status of a run.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8pub enum RunStatus {
9    /// Run is created but not yet started.
10    Pending,
11    /// Run is currently executing.
12    Running,
13    /// Run completed successfully.
14    Success,
15    /// Run failed with an error.
16    Failed,
17    /// Run was cancelled by user or system.
18    Cancelled,
19}
20
21/// Run Record represents a single execution of an experiment.
22///
23/// Each experiment can have multiple runs. A run tracks the execution
24/// lifecycle from start to completion.
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
26pub struct RunRecord {
27    run_id: String,
28    experiment_id: String,
29    status: RunStatus,
30    started_at: Option<DateTime<Utc>>,
31    ended_at: Option<DateTime<Utc>>,
32    renacer_span_id: Option<String>,
33}
34
35impl RunRecord {
36    /// Create a new run record in Pending status.
37    ///
38    /// # Arguments
39    ///
40    /// * `run_id` - Unique identifier for the run
41    /// * `experiment_id` - ID of the parent experiment
42    #[must_use]
43    pub fn new(run_id: impl Into<String>, experiment_id: impl Into<String>) -> Self {
44        Self {
45            run_id: run_id.into(),
46            experiment_id: experiment_id.into(),
47            status: RunStatus::Pending,
48            started_at: None,
49            ended_at: None,
50            renacer_span_id: None,
51        }
52    }
53
54    /// Create a builder for constructing a run record with optional fields.
55    #[must_use]
56    pub fn builder(
57        run_id: impl Into<String>,
58        experiment_id: impl Into<String>,
59    ) -> RunRecordBuilder {
60        RunRecordBuilder::new(run_id, experiment_id)
61    }
62
63    /// Get the run ID.
64    #[must_use]
65    pub fn run_id(&self) -> &str {
66        &self.run_id
67    }
68
69    /// Get the parent experiment ID.
70    #[must_use]
71    pub fn experiment_id(&self) -> &str {
72        &self.experiment_id
73    }
74
75    /// Get the current run status.
76    #[must_use]
77    pub const fn status(&self) -> RunStatus {
78        self.status
79    }
80
81    /// Get the start timestamp, if the run has started.
82    #[must_use]
83    pub const fn started_at(&self) -> Option<DateTime<Utc>> {
84        self.started_at
85    }
86
87    /// Get the end timestamp, if the run has completed.
88    #[must_use]
89    pub const fn ended_at(&self) -> Option<DateTime<Utc>> {
90        self.ended_at
91    }
92
93    /// Get the renacer span ID for distributed tracing, if set.
94    #[must_use]
95    pub fn renacer_span_id(&self) -> Option<&str> {
96        self.renacer_span_id.as_deref()
97    }
98
99    /// Start the run, transitioning from Pending to Running.
100    ///
101    /// Sets the `started_at` timestamp to now.
102    pub fn start(&mut self) {
103        self.status = RunStatus::Running;
104        self.started_at = Some(Utc::now());
105    }
106
107    /// Complete the run with the given final status.
108    ///
109    /// Sets the `ended_at` timestamp to now.
110    ///
111    /// # Arguments
112    ///
113    /// * `status` - Final status (Success, Failed, or Cancelled)
114    pub fn complete(&mut self, status: RunStatus) {
115        self.status = status;
116        self.ended_at = Some(Utc::now());
117    }
118}
119
120/// Builder for `RunRecord`.
121#[derive(Debug)]
122#[allow(clippy::struct_field_names)]
123pub struct RunRecordBuilder {
124    run_id: String,
125    experiment_id: String,
126    renacer_span_id: Option<String>,
127}
128
129impl RunRecordBuilder {
130    /// Create a new builder with required fields.
131    #[must_use]
132    pub fn new(run_id: impl Into<String>, experiment_id: impl Into<String>) -> Self {
133        Self { run_id: run_id.into(), experiment_id: experiment_id.into(), renacer_span_id: None }
134    }
135
136    /// Set the renacer span ID for distributed tracing.
137    #[must_use]
138    pub fn renacer_span_id(mut self, span_id: impl Into<String>) -> Self {
139        self.renacer_span_id = Some(span_id.into());
140        self
141    }
142
143    /// Build the `RunRecord`.
144    #[must_use]
145    pub fn build(self) -> RunRecord {
146        RunRecord {
147            run_id: self.run_id,
148            experiment_id: self.experiment_id,
149            status: RunStatus::Pending,
150            started_at: None,
151            ended_at: None,
152            renacer_span_id: self.renacer_span_id,
153        }
154    }
155}
156
157#[cfg(test)]
158mod tests {
159    use super::*;
160
161    #[test]
162    fn test_run_status_default() {
163        let run = RunRecord::new("run-1", "exp-1");
164        assert_eq!(run.status(), RunStatus::Pending);
165    }
166
167    #[test]
168    fn test_run_lifecycle() {
169        let mut run = RunRecord::new("run-1", "exp-1");
170        run.start();
171        assert_eq!(run.status(), RunStatus::Running);
172        run.complete(RunStatus::Success);
173        assert_eq!(run.status(), RunStatus::Success);
174    }
175}