save_load_traits/
impl_for_vec.rs

1// ---------------- [ File: save-load-traits/src/impl_for_vec.rs ]
2crate::ix!();
3
4/// Implements SaveToFile for `Vec<T>`, writing its contents as JSON array,
5/// where `T: Serialize + DeserializeOwned + Send + Sync + 'static`.
6#[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/// Implements LoadFromFile for `Vec<T>`, reading its contents from JSON array,
27/// where `T: Serialize + DeserializeOwned + Send + Sync + 'static`.
28#[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/// Exhaustive test suite verifying we can store/load any `Vec<T>` using these
49/// blanket impls. In real usage, `T` must implement `Serialize + DeserializeOwned + Send + Sync`.
50#[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    /// Demonstrates we can also store/load basic types if needed, e.g. `Vec<String>`.
80    #[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/// Exhaustive test suite to verify reading/writing Vec<String> from/to file.
112#[cfg(test)]
113mod test_vec_string_save_and_load {
114    use super::*;
115    use tokio::fs;
116
117    /// Test saving and loading an empty Vec<String>.
118    #[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    /// Test saving and loading a non-empty Vec<String>.
133    #[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    /// Test saving a Vec<String> containing special characters and verifying the round trip.
148    #[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    /// Test that loading from a non-existent file returns an error.
167    #[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    /// Test that loading a malformed JSON file yields an error.
175    #[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}