batch_mode_json/
write_json_to_file.rs

1// ---------------- [ File: batch-mode-json/src/write_json_to_file.rs ]
2crate::ix!();
3
4/// Writes serialized JSON content to a file asynchronously.
5///
6/// # Arguments
7/// * `target_path` - A reference to the path where the file will be created/overwritten.
8/// * `serialized_json` - The JSON content as a string to write.
9///
10/// # Returns
11/// * `Result<(), io::Error>` - `Ok(())` if successful, or an `io::Error` otherwise.
12pub async fn write_to_file(
13    target_path: impl AsRef<Path>,
14    serialized_json: &str
15) -> Result<(), io::Error> 
16{
17    info!("writing some json content to file: {:?}", target_path.as_ref());
18
19    // Create or overwrite the target file
20    let mut target_file = File::create(&target_path).await?;
21
22    // Write the JSON content
23    target_file.write_all(serialized_json.as_bytes()).await?;
24
25    // Ensure all data is written and flushed
26    target_file.flush().await?;
27
28    Ok(())
29}
30
31#[cfg(test)]
32mod write_to_file_tests {
33    use super::*;
34
35    /// Creates a named temp file, then returns (PathBuf, NamedTempFile).
36    /// We keep the NamedTempFile in scope so it persists until the test ends,
37    /// preventing collisions or early cleanup.
38    fn named_temp_file_with_path(prefix: &str) -> (PathBuf, NamedTempFile) {
39        let file = NamedTempFile::new().expect("Failed to create NamedTempFile");
40        let path = file.path().to_path_buf();
41        // Optionally rename it so we can see the prefix in the path, 
42        // but leaving as-is is usually fine. We'll just rely on the unique name.
43        // We only do it if you want a more descriptive name in the filesystem:
44        /*
45        let renamed = file.into_temp_path();
46        renamed.persist(format!("{}_{}", prefix, Uuid::new_v4())).unwrap();
47        // but that changes usage. We'll skip it for now.
48        */
49        (path, file)
50    }
51
52    #[traced_test]
53    async fn test_write_to_file_success() {
54        info!("Starting test_write_to_file_success");
55        let (temp_path, _tempfile) = named_temp_file_with_path("success");
56
57        let json_content = r#"{"key": "value"}"#;
58        let result = write_to_file(&temp_path, json_content).await;
59        assert!(result.is_ok());
60
61        // Read back to verify
62        let written = fs::read_to_string(&temp_path).await.unwrap();
63        pretty_assert_eq!(written, json_content);
64
65        // Cleanup is automatic because NamedTempFile is in scope.
66        info!("test_write_to_file_success passed.");
67    }
68
69    #[traced_test]
70    async fn test_write_to_file_invalid_path() {
71        info!("Starting test_write_to_file_invalid_path");
72        let invalid_path = PathBuf::from("/invalid_path/test_output.json");
73        let json_content = r#"{"key": "value"}"#;
74
75        let result = write_to_file(&invalid_path, json_content).await;
76        assert!(result.is_err(), "Should fail writing to an invalid path");
77        info!("test_write_to_file_invalid_path passed.");
78    }
79
80    #[traced_test]
81    async fn returns_error_on_invalid_path() {
82        info!("Starting returns_error_on_invalid_path");
83        let invalid_path = PathBuf::from("/this/path/does/not/exist.json");
84        let json_content = r#"{"key": "value"}"#;
85
86        let result = write_to_file(&invalid_path, json_content).await;
87        debug!("Result from write_to_file: {:?}", result);
88        assert!(result.is_err(), "Expected an I/O error for invalid path");
89        info!("returns_error_on_invalid_path passed.");
90    }
91
92    #[traced_test]
93    async fn overwrites_existing_file() {
94        info!("Starting overwrites_existing_file");
95        let (temp_path, _tempfile) = named_temp_file_with_path("overwrite");
96
97        let initial = r#"{"initial": "data"}"#;
98        let updated = r#"{"updated": "data"}"#;
99
100        // Write initial
101        write_to_file(&temp_path, initial).await.unwrap();
102        // Overwrite
103        write_to_file(&temp_path, updated).await.unwrap();
104
105        // Verify final
106        let final_contents = fs::read_to_string(&temp_path).await.unwrap();
107        pretty_assert_eq!(final_contents, updated);
108
109        info!("overwrites_existing_file passed.");
110    }
111
112    #[traced_test]
113    async fn handles_empty_content() {
114        info!("Starting handles_empty_content");
115        let (temp_path, _tempfile) = named_temp_file_with_path("empty");
116        let empty_content = "";
117
118        write_to_file(&temp_path, empty_content).await.unwrap();
119
120        let read_back = fs::read_to_string(&temp_path).await.unwrap();
121        assert!(read_back.is_empty(), "File should be empty after writing empty string");
122
123        info!("handles_empty_content passed.");
124    }
125
126    #[traced_test]
127    async fn handles_concurrent_writes() {
128        info!("Starting handles_concurrent_writes");
129        // We'll do 3 concurrency writes to 3 separate files:
130        let sets = vec![
131            ("concurrent_test_1", r#"{"data": 1}"#),
132            ("concurrent_test_2", r#"{"data": 2}"#),
133            ("concurrent_test_3", r#"{"data": 3}"#),
134        ];
135
136        let mut tasks = Vec::new();
137        let mut file_paths = Vec::new();
138
139        for (prefix, content) in sets {
140            let (path, tempfile) = named_temp_file_with_path(prefix);
141            let content = content.to_string();
142            file_paths.push((path.clone(), tempfile)); // keep the NamedTempFile in scope
143            tasks.push(tokio::spawn(async move {
144                write_to_file(&path, &content).await
145            }));
146        }
147
148        for task in tasks {
149            let res = task.await.expect("Task panicked");
150            assert!(res.is_ok(), "Concurrent write task failed");
151        }
152
153        // Now verify each
154        for (path, _tempfile) in file_paths {
155            let data = fs::read_to_string(&path).await.unwrap();
156            debug!("Read from {:?}: {}", path, data);
157            assert!(data.contains("data"), "Content mismatch in concurrency test");
158        }
159
160        info!("handles_concurrent_writes passed.");
161    }
162
163    #[traced_test]
164    async fn writes_json_content_correctly() {
165        trace!("===== BEGIN_TEST: writes_json_content_correctly =====");
166
167        // Use a unique named temp file for this test
168        let (temp_path, _tempfile) = named_temp_file_with_path("writes_json_content_correctly");
169
170        let json_content = r#"{"key": "value"}"#;
171        let result = write_to_file(&temp_path, json_content).await;
172        debug!("write_to_file result: {:?}", result);
173        assert!(result.is_ok(), "Expected Ok from write_to_file");
174
175        // Read back content
176        let read_content = fs::read_to_string(&temp_path)
177            .await
178            .expect("Failed to read test file");
179        pretty_assert_eq!(read_content, json_content);
180
181        trace!("===== END_TEST: writes_json_content_correctly =====");
182    }
183}