save_load_traits/
impl_default_macro.rs1crate::ix!();
3
4#[macro_export]
5macro_rules! impl_default_save_to_file_traits {
6 ($ty:ty) => {
7 #[async_trait]
8 impl LoadFromFile for $ty {
9 type Error = SaveLoadError;
10
11 async fn load_from_file(
12 filename: impl AsRef<std::path::Path> + Send,
13 ) -> Result<Self, Self::Error> {
14 debug!("Attempting to load `{}` from file: {:?}", stringify!($ty), filename.as_ref());
15
16 let content = match tokio::fs::read_to_string(filename.as_ref()).await {
17 Ok(c) => {
18 trace!("File content successfully read for `{}`: {}", stringify!($ty), c);
19 c
20 },
21 Err(e) => {
22 error!("Failed to read file for `{}`: {:?}", stringify!($ty), e);
23 return Err(SaveLoadError::IoError(e));
24 }
25 };
26
27 match serde_json::from_str(&content) {
28 Ok(instance) => {
29 info!("Successfully deserialized `{}` from file: {:?}", stringify!($ty), filename.as_ref());
30 Ok(instance)
31 },
32 Err(e) => {
33 error!("Deserialization error for `{}`: {:?}", stringify!($ty), e);
34 return Err(SaveLoadError::JsonParseError(e.into()));
35 }
36 }
37 }
38 }
39
40 #[async_trait]
41 impl SaveToFile for $ty {
42 type Error = SaveLoadError;
43
44 async fn save_to_file(
45 &self,
46 filename: impl AsRef<std::path::Path> + Send,
47 ) -> Result<(), Self::Error> {
48 debug!("Attempting to save `{}` to file: {:?}", stringify!($ty), filename.as_ref());
49
50 let serialized = match serde_json::to_string_pretty(self) {
51 Ok(json_str) => {
52 trace!("Successfully serialized `{}` to JSON: {}", stringify!($ty), json_str);
53 json_str
54 },
55 Err(e) => {
56 error!("Serialization error for `{}`: {:?}", stringify!($ty), e);
57 return Err(SaveLoadError::JsonParseError(e.into()));
58 }
59 };
60
61 if let Err(e) = tokio::fs::write(filename.as_ref(), &serialized).await {
62 error!("Failed to write `{}` to file: {:?}", stringify!($ty), e);
63 return Err(SaveLoadError::IoError(e));
64 }
65
66 info!("Successfully saved `{}` to file: {:?}", stringify!($ty), filename.as_ref());
67 Ok(())
68 }
69 }
70 }
71}
72
73#[cfg(test)]
74mod default_impl_save_to_file_traits_when_serde_test {
75 use super::*;
76
77 #[derive(Serialize, Deserialize, Debug, PartialEq)]
78 struct TestData {
79 foo: String,
80 bar: i32,
81 }
82
83 impl_default_save_to_file_traits!(TestData);
85
86 #[traced_test]
87 async fn test_save_and_load() {
88 let data = TestData {
89 foo: "Hello".to_string(),
90 bar: 42,
91 };
92
93 let dir = tempdir().expect("Failed to create temp directory");
94 let file_path = dir.path().join("test.json");
95
96 info!("Attempting to save `TestData` to temporary file.");
97 data.save_to_file(&file_path)
98 .await
99 .expect("Failed to save data");
100
101 info!("Attempting to load `TestData` back from the same file.");
102 let loaded = TestData::load_from_file(&file_path)
103 .await
104 .expect("Failed to load data");
105 pretty_assert_eq!(data, loaded, "Loaded data did not match saved data.");
106 }
107
108 #[traced_test]
109 async fn test_load_from_non_existent_file() {
110 let non_existent_path = "definitely_does_not_exist.json";
111 warn!("Trying to load `TestData` from a non-existent file. Expecting an error.");
112 let result = TestData::load_from_file(non_existent_path).await;
113 match result {
114 Err(SaveLoadError::IoError(_)) => {
115 info!("Got expected IoError for non-existent file.")
116 },
117 other => panic!("Expected an IoError, got {:?}", other),
118 }
119 }
120
121 #[traced_test]
122 async fn test_save_invalid_path() {
123 let dir = tempdir().expect("Failed to create temp directory");
124 let invalid_file_path = dir.path(); let data = TestData {
127 foo: "Hello".to_string(),
128 bar: 42,
129 };
130
131 warn!("Trying to save `TestData` to a directory path instead of a file. Expecting an error.");
132 let result = data.save_to_file(&invalid_file_path).await;
133 match result {
134 Err(SaveLoadError::IoError(_)) => {
135 info!("Got expected IoError for invalid save path.")
136 },
137 other => panic!("Expected an IoError, got {:?}", other),
138 }
139 }
140
141 #[traced_test]
142 async fn test_load_corrupt_file() {
143 let dir = tempdir().expect("Failed to create temp directory");
144 let file_path = dir.path().join("corrupt.json");
145
146 debug!("Writing invalid JSON to file to test parse errors.");
147 tokio::fs::write(&file_path, b"not valid json")
148 .await
149 .expect("Failed to write corrupt data to file");
150
151 warn!("Trying to load `TestData` from a corrupt JSON file. Expecting a JsonParseError.");
152 let result = TestData::load_from_file(&file_path).await;
153 match result {
154 Err(SaveLoadError::JsonParseError(_)) => {
155 info!("Got expected JsonParseError for corrupt file.")
156 },
157 other => panic!("Expected a JsonParseError, got {:?}", other),
158 }
159 }
160}