save_load_traits/
impl_for_vec.rs1crate::ix!();
3
4#[async_trait]
7impl<T> SaveToFile for Vec<T>
8where
9 T: Serialize + DeserializeOwned + Send + Sync + 'static,
10{
11 type Error = SaveLoadError;
12
13 async fn save_to_file(
14 &self,
15 filename: impl AsRef<Path> + Send,
16 ) -> Result<(), Self::Error> {
17 let json_data = serde_json::to_string_pretty(&self)
18 .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
19
20 let mut file = File::create(filename).await?;
21 file.write_all(json_data.as_bytes()).await?;
22 Ok(())
23 }
24}
25
26#[async_trait]
29impl<T> LoadFromFile for Vec<T>
30where
31 T: Serialize + DeserializeOwned + Send + Sync + 'static,
32{
33 type Error = SaveLoadError;
34
35 async fn load_from_file(filename: impl AsRef<Path> + Send)
36 -> Result<Self, Self::Error>
37 {
38 let mut file = File::open(filename).await?;
39 let mut buf = Vec::new();
40 file.read_to_end(&mut buf).await?;
41
42 let list_of_items: Vec<T> = serde_json::from_slice(&buf)
43 .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
44 Ok(list_of_items)
45 }
46}
47
48#[cfg(test)]
51mod test_vec_t_save_load {
52 use super::*;
53 use tokio::fs;
54 use traced_test::traced_test;
55 use std::path::PathBuf;
56
57 #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
58 struct ExampleItem {
59 name: String,
60 value: i32,
61 }
62
63 #[traced_test]
64 async fn test_save_load_vec_example_item() {
65 let items = vec![
66 ExampleItem { name: "First".into(), value: 10 },
67 ExampleItem { name: "Second".into(), value: 20 },
68 ];
69 let tmpfile = PathBuf::from("test_vec_example_item.json");
70
71 items.save_to_file(&tmpfile).await.expect("save failed");
72 let loaded_vec = <Vec<ExampleItem>>::load_from_file(&tmpfile)
73 .await
74 .expect("load failed");
75 assert_eq!(loaded_vec, items, "Roundtrip mismatch for Vec<ExampleItem>");
76 fs::remove_file(&tmpfile).await.ok();
77 }
78
79 #[traced_test]
81 async fn test_save_load_vec_string() {
82 let data = vec!["alpha".to_string(), "beta".to_string()];
83 let tmpfile = PathBuf::from("test_vec_string.json");
84
85 data.save_to_file(&tmpfile).await.expect("save failed");
86 let loaded = <Vec<String>>::load_from_file(&tmpfile)
87 .await
88 .expect("load failed");
89 assert_eq!(loaded, data, "Roundtrip mismatch for Vec<String>");
90 fs::remove_file(&tmpfile).await.ok();
91 }
92
93 #[traced_test]
94 async fn test_load_non_existent_file() {
95 let result = <Vec<ExampleItem>>::load_from_file("no_such_vec_123.json").await;
96 assert!(result.is_err(), "Should fail for a nonexistent file");
97 }
98
99 #[traced_test]
100 async fn test_load_corrupt_json() {
101 let tmpfile = PathBuf::from("test_vec_t_corrupt.json");
102 fs::write(&tmpfile, b"not valid json")
103 .await
104 .expect("write fail");
105 let result = <Vec<ExampleItem>>::load_from_file(&tmpfile).await;
106 assert!(result.is_err(), "Should fail for corrupt JSON");
107 fs::remove_file(&tmpfile).await.ok();
108 }
109}
110
111#[cfg(test)]
113mod test_vec_string_save_and_load {
114 use super::*;
115 use tokio::fs;
116
117 #[traced_test]
119 async fn test_save_load_empty_vec() {
120 let tmpfile = PathBuf::from("test_vec_string_empty.json");
121 let empty_vec: Vec<String> = vec![];
122
123 empty_vec.save_to_file(&tmpfile).await.expect("save failed");
124 let loaded_vec = <Vec<String> as LoadFromFile>::load_from_file(&tmpfile)
125 .await
126 .expect("load failed");
127 assert_eq!(loaded_vec, empty_vec, "Loaded vec must match saved vec");
128
129 fs::remove_file(&tmpfile).await.unwrap_or(());
130 }
131
132 #[traced_test]
134 async fn test_save_load_nonempty_vec() {
135 let tmpfile = PathBuf::from("test_vec_string_nonempty.json");
136 let sample_vec = vec!["Alpha".to_string(), "Beta".to_string(), "Gamma".to_string()];
137
138 sample_vec.save_to_file(&tmpfile).await.expect("save failed");
139 let loaded_vec = <Vec<String> as LoadFromFile>::load_from_file(&tmpfile)
140 .await
141 .expect("load failed");
142 assert_eq!(loaded_vec, sample_vec, "Loaded vec must match saved vec");
143
144 fs::remove_file(&tmpfile).await.unwrap_or(());
145 }
146
147 #[traced_test]
149 async fn test_save_load_special_chars() {
150 let tmpfile = PathBuf::from("test_vec_string_special.json");
151 let special_vec = vec![
152 "Line\nbreak".to_string(),
153 "Tab\tchar".to_string(),
154 "Unicode: λ -> λ".to_string()
155 ];
156
157 special_vec.save_to_file(&tmpfile).await.expect("save failed");
158 let loaded = <Vec<String> as LoadFromFile>::load_from_file(&tmpfile)
159 .await
160 .expect("load failed");
161 assert_eq!(loaded, special_vec, "Loaded special-chars must match saved data");
162
163 fs::remove_file(&tmpfile).await.unwrap_or(());
164 }
165
166 #[traced_test]
168 async fn test_load_non_existent_file_error() {
169 let tmpfile = PathBuf::from("this_file_should_not_exist_123.json");
170 let load_result = <Vec<String> as LoadFromFile>::load_from_file(&tmpfile).await;
171 assert!(load_result.is_err(), "Expected an error when file does not exist");
172 }
173
174 #[traced_test]
176 async fn test_load_malformed_json() {
177 let tmpfile = PathBuf::from("test_vec_string_malformed.json");
178 let contents = b"This is not valid JSON at all.";
179 {
180 let mut f = tokio::fs::File::create(&tmpfile).await.expect("create fail");
181 use tokio::io::AsyncWriteExt;
182 f.write_all(contents).await.expect("write fail");
183 }
184
185 let load_result = <Vec<String> as LoadFromFile>::load_from_file(&tmpfile).await;
186 assert!(load_result.is_err(), "Expected an error for malformed JSON input");
187
188 fs::remove_file(&tmpfile).await.unwrap_or(());
189 }
190}