zai_rs/tool/file_parser_result/
data.rs1use tracing::{debug, trace, warn};
4
5use super::{request::*, response::*};
6use crate::{
7 ZaiResult,
8 client::{ZaiClient, error::codes},
9};
10
11pub struct FileParseResultRequest {
29 task_id: String,
31}
32
33impl FileParseResultRequest {
34 pub fn new(task_id: impl Into<String>) -> Self {
36 Self {
37 task_id: task_id.into(),
38 }
39 }
40
41 pub fn task_id(&self) -> &str {
43 &self.task_id
44 }
45
46 pub async fn get_result_via(
48 &self,
49 client: &ZaiClient,
50 format_type: FormatType,
51 ) -> ZaiResult<FileParseResultResponse> {
52 if self.task_id.trim().is_empty() {
53 return Err(crate::ZaiError::ApiError {
54 code: codes::SDK_VALIDATION,
55 message: "task_id cannot be blank".to_string(),
56 });
57 }
58 let route = crate::client::routes::FILES_PARSE_RESULT;
59 let format_type = format_type.to_string();
60 let url = client
61 .endpoints()
62 .resolve_route(route, &[&self.task_id, &format_type])?;
63 trace!(format = %format_type, "Fetching file parser result");
64 client
65 .send_empty::<FileParseResultResponse>(route.method(), url)
66 .await
67 }
68
69 pub async fn wait_for_result_via(
74 &self,
75 client: &ZaiClient,
76 format_type: FormatType,
77 timeout_seconds: u64,
78 poll_interval_seconds: u64,
79 ) -> ZaiResult<FileParseResultResponse> {
80 if poll_interval_seconds == 0 {
81 return Err(crate::client::error::ZaiError::ApiError {
82 code: codes::SDK_VALIDATION,
83 message: "poll_interval_seconds must be at least 1".to_string(),
84 });
85 }
86 if timeout_seconds == 0 {
87 return Err(crate::client::validation::invalid(
88 "timeout_seconds must be at least 1",
89 ));
90 }
91 debug!(
92 timeout_seconds,
93 poll_interval_seconds, "Polling file parser result"
94 );
95 let timeout = tokio::time::Duration::from_secs(timeout_seconds);
96 let deadline = tokio::time::Instant::now()
97 .checked_add(timeout)
98 .ok_or_else(|| crate::client::error::ZaiError::ApiError {
99 code: codes::SDK_VALIDATION,
100 message: "timeout_seconds is too large".to_string(),
101 })?;
102
103 loop {
104 trace!("Checking file parser result status");
105 let result =
106 tokio::time::timeout_at(deadline, self.get_result_via(client, format_type))
107 .await
108 .map_err(|_| polling_timeout())??;
109
110 match result.status {
111 ParserStatus::Succeeded => {
112 debug!("File parsing completed successfully");
113 return Ok(result);
114 },
115 ParserStatus::Failed => {
116 warn!("File parsing task reported failure");
117 return Err(parsing_failure(&result.message));
118 },
119 ParserStatus::Processing => {
120 let now = tokio::time::Instant::now();
121 let elapsed = timeout.saturating_sub(deadline.saturating_duration_since(now));
122 trace!(elapsed = ?elapsed, "File parser result still processing");
123 if now >= deadline {
124 warn!(
125 elapsed_seconds = elapsed.as_secs(),
126 timeout_seconds, "Polling timed out waiting for parsing result"
127 );
128 return Err(polling_timeout());
129 }
130 let interval = tokio::time::Duration::from_secs(poll_interval_seconds);
131 let wake_at = now.checked_add(interval).unwrap_or(deadline).min(deadline);
132 tokio::time::sleep_until(wake_at).await;
133 if wake_at == deadline {
134 return Err(polling_timeout());
135 }
136 },
137 }
138 }
139 }
140
141 pub async fn get_all_results_via(
146 &self,
147 client: &ZaiClient,
148 ) -> ZaiResult<(FileParseResultResponse, FileParseResultResponse)> {
149 let (text_result, download_result) = tokio::try_join!(
150 self.get_result_via(client, FormatType::Text),
151 self.get_result_via(client, FormatType::DownloadLink),
152 )?;
153 Ok((text_result, download_result))
154 }
155}
156
157fn polling_timeout() -> crate::client::error::ZaiError {
158 crate::client::error::ZaiError::ApiError {
159 code: codes::SDK_TIMEOUT,
160 message: "Timeout waiting for parsing result".to_string(),
161 }
162}
163
164fn parsing_failure(message: &str) -> crate::client::error::ZaiError {
165 crate::client::error::ZaiError::ApiError {
166 code: codes::SDK_EXTERNAL_TOOL,
167 message: format!(
168 "Parsing failed: {}",
169 crate::client::error::mask_sensitive_info(message)
170 ),
171 }
172}
173
174#[cfg(test)]
175mod tests {
176 use super::*;
177
178 #[test]
179 fn provider_failure_messages_are_sanitized() {
180 let error = parsing_failure("Authorization: Bearer private-token-value");
181 let rendered = error.to_string();
182 assert!(!rendered.contains("private-token-value"));
183 assert!(rendered.contains("[AUTH_REDACTED]"));
184 }
185}