Skip to main content

trueno_db/experiment/
experiment_record.rs

1//! Experiment Record - root entity for experiment tracking
2
3use chrono::{DateTime, Utc};
4use serde::{Deserialize, Serialize};
5
6/// Experiment Record represents a tracked experiment.
7///
8/// This is the root entity in the experiment tracking schema.
9/// Each experiment can have multiple runs.
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
11pub struct ExperimentRecord {
12    experiment_id: String,
13    name: String,
14    created_at: DateTime<Utc>,
15    config: Option<serde_json::Value>,
16}
17
18impl ExperimentRecord {
19    /// Create a new experiment record with the given ID and name.
20    ///
21    /// # Arguments
22    ///
23    /// * `experiment_id` - Unique identifier for the experiment
24    /// * `name` - Human-readable name for the experiment
25    ///
26    /// # Returns
27    ///
28    /// A new `ExperimentRecord` with the current timestamp.
29    #[must_use]
30    pub fn new(experiment_id: impl Into<String>, name: impl Into<String>) -> Self {
31        Self {
32            experiment_id: experiment_id.into(),
33            name: name.into(),
34            created_at: Utc::now(),
35            config: None,
36        }
37    }
38
39    /// Create a builder for constructing an experiment record with optional fields.
40    #[must_use]
41    pub fn builder(
42        experiment_id: impl Into<String>,
43        name: impl Into<String>,
44    ) -> ExperimentRecordBuilder {
45        ExperimentRecordBuilder::new(experiment_id, name)
46    }
47
48    /// Get the experiment ID.
49    #[must_use]
50    pub fn experiment_id(&self) -> &str {
51        &self.experiment_id
52    }
53
54    /// Get the experiment name.
55    #[must_use]
56    pub fn name(&self) -> &str {
57        &self.name
58    }
59
60    /// Get the creation timestamp.
61    #[must_use]
62    pub const fn created_at(&self) -> DateTime<Utc> {
63        self.created_at
64    }
65
66    /// Get the experiment configuration, if any.
67    #[must_use]
68    pub const fn config(&self) -> Option<&serde_json::Value> {
69        self.config.as_ref()
70    }
71}
72
73/// Builder for `ExperimentRecord`.
74#[derive(Debug)]
75pub struct ExperimentRecordBuilder {
76    experiment_id: String,
77    name: String,
78    created_at: DateTime<Utc>,
79    config: Option<serde_json::Value>,
80}
81
82impl ExperimentRecordBuilder {
83    /// Create a new builder with required fields.
84    #[must_use]
85    pub fn new(experiment_id: impl Into<String>, name: impl Into<String>) -> Self {
86        Self {
87            experiment_id: experiment_id.into(),
88            name: name.into(),
89            created_at: Utc::now(),
90            config: None,
91        }
92    }
93
94    /// Set the experiment configuration.
95    #[must_use]
96    pub fn config(mut self, config: serde_json::Value) -> Self {
97        self.config = Some(config);
98        self
99    }
100
101    /// Set a custom creation timestamp (useful for deserialization/testing).
102    #[must_use]
103    pub const fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
104        self.created_at = created_at;
105        self
106    }
107
108    /// Build the `ExperimentRecord`.
109    #[must_use]
110    pub fn build(self) -> ExperimentRecord {
111        ExperimentRecord {
112            experiment_id: self.experiment_id,
113            name: self.name,
114            created_at: self.created_at,
115            config: self.config,
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123
124    #[test]
125    fn test_experiment_record_new() {
126        let record = ExperimentRecord::new("test-id", "test-name");
127        assert_eq!(record.experiment_id(), "test-id");
128        assert_eq!(record.name(), "test-name");
129    }
130
131    #[test]
132    fn test_experiment_record_builder() {
133        let config = serde_json::json!({"key": "value"});
134        let record =
135            ExperimentRecord::builder("test-id", "test-name").config(config.clone()).build();
136
137        assert_eq!(record.config(), Some(&config));
138    }
139}