somatize_runtime/
study_io.rs1use somatize_core::error::{Result, SomaError};
15use somatize_core::study::Study;
16use std::path::Path;
17
18pub trait StudyIo: Sized {
20 fn save(&self, path: impl AsRef<Path>) -> Result<()>;
22
23 fn load(path: impl AsRef<Path>) -> Result<Self>;
26}
27
28impl StudyIo for Study {
29 fn save(&self, path: impl AsRef<Path>) -> Result<()> {
30 let json =
31 serde_json::to_vec_pretty(self).map_err(|e| SomaError::Serialization(e.to_string()))?;
32 std::fs::write(path, json)?;
33 Ok(())
34 }
35
36 fn load(path: impl AsRef<Path>) -> Result<Self> {
37 let bytes = std::fs::read(path)?;
38 serde_json::from_slice(&bytes).map_err(|e| SomaError::Serialization(e.to_string()))
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45 use somatize_core::search::SearchSpace;
46 use somatize_core::study::{Direction, Objective, SearchStrategy, Study};
47
48 fn study() -> Study {
49 Study::new(
50 "persisted",
51 SearchSpace::new(),
52 SearchStrategy::Grid { points_per_dim: 3 },
53 vec![Objective {
54 metric: "f1".into(),
55 direction: Direction::Maximize,
56 }],
57 )
58 }
59
60 #[test]
61 fn a_study_survives_a_round_trip_through_a_file() {
62 let dir = tempfile::tempdir().unwrap();
63 let path = dir.path().join("study.json");
64
65 let mut original = study();
66 original.tags = vec!["mos".into()];
67 original.planned_trials = Some(6);
68 original.git_sha = Some("abc123".into());
69 original.save(&path).unwrap();
70
71 let back = Study::load(&path).unwrap();
72 assert_eq!(back.name, "persisted");
73 assert_eq!(back.tags, vec!["mos"]);
74 assert_eq!(back.planned_trials, Some(6));
75 assert_eq!(back.git_sha.as_deref(), Some("abc123"));
76 assert!(back.created_at.is_some());
77 }
78
79 #[test]
83 fn load_errors_are_typed() {
84 let missing = Study::load("/nonexistent/dir/study.json");
85 assert!(matches!(missing, Err(SomaError::Io(_))), "{missing:?}");
86
87 let dir = tempfile::tempdir().unwrap();
88 let path = dir.path().join("study.json");
89 std::fs::write(&path, "{not json").unwrap();
90 let corrupt = Study::load(&path);
91 assert!(
92 matches!(corrupt, Err(SomaError::Serialization(_))),
93 "{corrupt:?}"
94 );
95 }
96}