Skip to main content

zai_rs/tool/file_parser_create/
data.rs

1//! File-parser task request builder.
2
3use std::path::Path;
4
5use super::{request::*, response::*};
6use crate::{
7    ZaiResult,
8    client::{ZaiClient, error::codes},
9};
10
11/// Request that uploads a local file and creates a parsing task.
12///
13/// # Examples
14///
15/// ```rust,no_run
16/// use zai_rs::tool::file_parser_create::{FileParseRequest, ToolType};
17/// use std::path::Path;
18///
19/// # fn build() -> zai_rs::ZaiResult<()> {
20/// let file_path = Path::new("document.pdf");
21///
22/// let request = FileParseRequest::new(file_path, ToolType::Lite)?;
23/// # let _ = request;
24/// # Ok(())
25/// # }
26/// ```
27pub struct FileParseRequest {
28    /// Path to the file to parse.
29    file_path: std::path::PathBuf,
30    /// Parser implementation to use.
31    tool_type: ToolType,
32    /// Optional declared file type; omission lets the service detect it.
33    file_type: Option<FileType>,
34}
35
36impl FileParseRequest {
37    /// Create a request after verifying that `file_path` is a regular file.
38    pub fn new(file_path: &Path, tool_type: ToolType) -> crate::ZaiResult<Self> {
39        let metadata = std::fs::symlink_metadata(file_path).map_err(|error| {
40            if error.kind() == std::io::ErrorKind::NotFound {
41                crate::client::error::ZaiError::FileError {
42                    code: codes::SDK_FILE_NOT_FOUND,
43                    message: format!("File does not exist: {}", file_path.display()),
44                }
45            } else {
46                error.into()
47            }
48        })?;
49        if metadata.file_type().is_symlink() || !metadata.is_file() {
50            return Err(crate::client::error::ZaiError::FileError {
51                code: codes::SDK_FILE_NOT_FOUND,
52                message: format!("Path is not a regular file: {}", file_path.display()),
53            });
54        }
55
56        Ok(Self {
57            file_path: file_path.to_path_buf(),
58            tool_type,
59            file_type: None,
60        })
61    }
62
63    /// Declare the input file type instead of relying on service detection.
64    pub fn with_file_type(mut self, file_type: FileType) -> crate::ZaiResult<Self> {
65        if !file_type.is_supported_by(&self.tool_type) {
66            return Err(crate::client::validation::invalid(format!(
67                "file type {file_type:?} is not supported by tool type {:?}",
68                self.tool_type
69            )));
70        }
71        self.file_type = Some(file_type);
72        Ok(self)
73    }
74
75    /// Create a request and infer the declared file type from the path suffix.
76    pub fn new_with_auto_type(file_path: &Path, tool_type: ToolType) -> crate::ZaiResult<Self> {
77        let file_type = FileType::from_path(file_path).ok_or_else(|| {
78            crate::client::error::ZaiError::FileError {
79                code: codes::SDK_FILE_TYPE_UNSUPPORTED,
80                message: format!(
81                    "Could not determine file type from path: {}",
82                    file_path.display()
83                ),
84            }
85        })?;
86
87        Self::new(file_path, tool_type)?.with_file_type(file_type)
88    }
89
90    /// Borrow the local file path.
91    pub fn file_path(&self) -> &Path {
92        &self.file_path
93    }
94
95    /// Return the selected parser implementation.
96    pub const fn tool_type(&self) -> ToolType {
97        self.tool_type
98    }
99
100    /// Return the declared input file type.
101    pub const fn file_type(&self) -> Option<FileType> {
102        self.file_type
103    }
104
105    /// Upload the file and create the parser task through `client`.
106    pub async fn send_via(&self, client: &ZaiClient) -> ZaiResult<FileParserCreateResponse> {
107        let part = crate::client::transport::multipart::FilePart::from_path(&self.file_path)?;
108
109        let route = crate::client::routes::FILES_PARSE;
110        let url = client.endpoints().resolve_route(route, &[])?;
111        let mut factory = crate::client::transport::multipart::MultipartBodyFactory::new()
112            .field("tool_type", self.tool_type.as_str())?;
113        if let Some(file_type) = self.file_type {
114            factory = factory.field("file_type", file_type.extension().to_ascii_uppercase())?;
115        }
116        let factory = factory.file_named("file", part)?;
117        let create_response = client
118            .send_multipart::<FileParserCreateResponse>(route.method(), url, &factory)
119            .await
120            .map_err(|e| e.context("file parser create"))?;
121
122        if !create_response.is_success() {
123            return Err(crate::client::error::ZaiError::ApiError {
124                code: codes::SDK_EXTERNAL_TOOL,
125                message: create_response.message.as_deref().map_or_else(
126                    || "file parser did not return a usable task id".to_owned(),
127                    |message| {
128                        format!(
129                            "file parser task creation failed: {}",
130                            crate::client::error::mask_sensitive_info(message)
131                        )
132                    },
133                ),
134            });
135        }
136
137        Ok(create_response)
138    }
139}