save_load_traits/
impl_for_string.rs

1// ---------------- [ File: save-load-traits/src/impl_for_string.rs ]
2crate::ix!();
3
4#[async_trait]
5impl SaveToFile for String {
6    type Error = SaveLoadError;
7
8    async fn save_to_file(
9        &self,
10        filename: impl AsRef<Path> + Send
11    ) -> Result<(), Self::Error> {
12        let json_data = serde_json::to_string_pretty(&self)
13            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
14        let mut file = File::create(filename).await?;
15        file.write_all(json_data.as_bytes()).await?;
16        Ok(())
17    }
18}
19
20/// Implements LoadFromFile for Vec<String>, reading its contents from a raw JSON file.
21#[async_trait]
22impl LoadFromFile for String {
23    type Error = SaveLoadError;
24
25    async fn load_from_file(filename: impl AsRef<Path> + Send)
26        -> Result<Self, Self::Error>
27    {
28        let mut file = File::open(filename).await?;
29        let mut buf = Vec::new();
30        file.read_to_end(&mut buf).await?;
31        let string: String =
32            serde_json::from_slice(&buf)
33                .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
34        Ok(string)
35    }
36}
37
38#[cfg(test)]
39mod string_file_persistence_behavior {
40    use super::*;
41    use tracing::{info, debug, trace};
42    use traced_test::traced_test;
43    use tempfile::NamedTempFile;
44    use std::path::Path;
45
46    #[traced_test]
47    async fn roundtrip_save_and_load_preserves_exact_string() {
48        let original = String::from("Hello, world!");
49        info!("Testing roundtrip save and load for string: {}", original);
50        let tmp = NamedTempFile::new().expect("unable to create temporary file");
51        let path = tmp.path().to_path_buf();
52        trace!("Saving string to file at {:?}", path);
53        original.save_to_file(&path).await.expect("save_to_file failed");
54        trace!("Loading string from file at {:?}", path);
55        let loaded = String::load_from_file(&path).await.expect("load_from_file failed");
56        info!("Loaded string: {}", loaded);
57        assert_eq!(loaded, original);
58    }
59
60    #[traced_test]
61    async fn save_creates_file_with_pretty_json_representation() {
62        let original = String::from("Test JSON");
63        debug!("Original string for JSON formatting test: {}", original);
64        let tmp = NamedTempFile::new().expect("unable to create temporary file");
65        let path = tmp.path().to_path_buf();
66        original.save_to_file(&path).await.expect("save_to_file failed");
67        let content = tokio::fs::read_to_string(&path).await.expect("failed to read file");
68        let expected = serde_json::to_string_pretty(&original).expect("serde_json formatting failed");
69        debug!("File content: {}", content);
70        assert_eq!(content, expected);
71    }
72
73    #[traced_test]
74    async fn load_from_nonexistent_path_returns_not_found_error() {
75        let nonexistent = Path::new("unlikely_to_exist_12345.json");
76        let result = String::load_from_file(&nonexistent).await;
77        assert!(result.is_err(), "Expected error for nonexistent file");
78        let err = result.unwrap_err();
79        debug!("Received error: {:?}", err);
80    }
81
82    #[traced_test]
83    async fn load_with_invalid_json_data_returns_invalid_data_error() {
84        let tmp = NamedTempFile::new().expect("unable to create temporary file");
85        let path = tmp.path().to_path_buf();
86        // write invalid JSON directly
87        tokio::fs::write(&path, b"not a valid json").await.expect("failed to write invalid data");
88        let result = String::load_from_file(&path).await;
89        assert!(result.is_err(), "Expected error for invalid JSON data");
90        let err = result.unwrap_err();
91        debug!("Received error: {:?}", err);
92    }
93}