batch_mode_process_response/
save_failed_entries.rs

1// ---------------- [ File: batch-mode-process-response/src/save_failed_entries.rs ]
2crate::ix!();
3
4#[tracing::instrument(level="trace", skip_all)]
5pub async fn save_failed_entries(
6    workspace: &dyn BatchWorkspaceInterface,
7    failed_entries: &[&BatchResponseRecord],
8) -> Result<(), ErrorSavingFailedBatchEntries> {
9    trace!("Entering save_failed_entries.");
10
11    info!("saving failed entries: {:#?}", failed_entries);
12
13    // 1) Serialize failed entries to JSON Lines format
14    let mut serialized_entries = String::new();
15    for entry in failed_entries {
16        let json_line = serde_json::to_string(entry)
17            .map_err(ErrorSavingFailedBatchEntries::from)?;
18        serialized_entries.push_str(&json_line);
19        serialized_entries.push('\n');
20    }
21
22    // 2) Build the path
23    let file_path = workspace.failed_items_dir().join("failed_entries.jsonl");
24    debug!("Appending failed entries to file at path: {:?}", file_path);
25
26    // Ensure the directory exists (fix for the "No such file or directory" error)
27    if let Some(parent_dir) = file_path.parent() {
28        tokio::fs::create_dir_all(parent_dir)
29            .await
30            .map_err(ErrorSavingFailedBatchEntries::from)?;
31    }
32
33    // 3) Open the file for append
34    use tokio::io::AsyncWriteExt;
35    let mut file = tokio::fs::OpenOptions::new()
36        .create(true)
37        .append(true)
38        .open(&file_path)
39        .await
40        .map_err(ErrorSavingFailedBatchEntries::from)?;
41
42    file.write_all(serialized_entries.as_bytes())
43        .await
44        .map_err(ErrorSavingFailedBatchEntries::from)?;
45
46    info!("save_failed_entries completed successfully.");
47    Ok(())
48}
49
50#[cfg(test)]
51mod save_failed_entries_tests {
52    use super::*;
53    use std::fs;
54
55    #[traced_test]
56    async fn test_save_failed_entries() {
57        // This test ensures we can append failed records to `failed_entries.jsonl`.
58        trace!("===== BEGIN TEST: test_save_failed_entries =====");
59
60        let workspace = BatchWorkspace::new_temp().await.unwrap();
61        info!("Created workspace: {:?}", workspace);
62
63        let fail_details = BatchErrorDetailsBuilder::default()
64            .error_type(ErrorType::Unknown("some_error_type".to_string()))
65            .code(Some("xxx".to_string()))
66            .message("some error".to_string())
67            .build()
68            .unwrap();
69
70        let fail_errbody = BatchErrorResponseBodyBuilder::default()
71            .error(fail_details)
72            .build()
73            .unwrap();
74
75        let fail_respcontent = BatchResponseContentBuilder::default()
76            .status_code(400_u16)
77            .request_id(ResponseRequestId::new("resp_fail_1"))
78            .body(BatchResponseBody::Error(fail_errbody))
79            .build()
80            .unwrap();
81
82        let fail_rec = BatchResponseRecordBuilder::default()
83            .id(BatchRequestId::new("id"))
84            .custom_id(CustomRequestId::new("fail_1"))
85            .response(fail_respcontent)
86            .build()
87            .unwrap();
88
89        let failed_records = vec![ &fail_rec ];
90
91        // 4) Attempt to save them
92        let result = save_failed_entries(workspace.as_ref(), &failed_records).await;
93        assert!(result.is_ok(), "Saving failed entries should succeed");
94
95        // 5) Verify that the file was actually written
96        let file_path = workspace.failed_items_dir().join("failed_entries.jsonl");
97        trace!("Asserting that failed-entries file path exists: {:?}", file_path);
98        assert!(
99            file_path.exists(),
100            "failed_entries.jsonl must be created in ephemeral failed_items_dir"
101        );
102
103        // 6) Check that it contains the expected JSON line
104        let contents = std::fs::read_to_string(&file_path)
105            .expect("Could not read appended failed_entries.jsonl");
106        assert!(contents.contains("\"fail_1\""));
107
108        trace!("===== END TEST: test_save_failed_entries =====");
109    }
110}