zai_rs/knowledge/
document_upload_file.rs1use std::{collections::BTreeMap, path::PathBuf};
2
3use validator::Validate;
4
5use super::types::UploadFileResponse;
6use crate::client::ZaiClient;
7
8#[derive(Debug, Clone, Copy)]
10pub enum DocumentSliceType {
11 TitleParagraph = 1,
13 QaPair = 2,
15 Line = 3,
17 Custom = 5,
19 Page = 6,
21 Single = 7,
23}
24impl DocumentSliceType {
25 fn as_i64(self) -> i64 {
26 self as i64
27 }
28}
29
30#[derive(Debug, Clone, Default, Validate)]
32pub struct UploadFileOptions {
33 pub knowledge_type: Option<DocumentSliceType>,
35 pub custom_separator: Option<Vec<String>>,
37 #[validate(range(min = 20, max = 2000))]
39 pub sentence_size: Option<u32>,
40 pub parse_image: Option<bool>,
42 #[validate(url)]
44 pub callback_url: Option<String>,
45 pub callback_header: Option<BTreeMap<String, String>>,
47 pub word_num_limit: Option<String>,
49 #[validate(length(min = 1))]
51 pub req_id: Option<String>,
52}
53
54pub struct DocumentUploadFileRequest {
59 knowledge_id: String,
60 files: Vec<PathBuf>,
61 options: UploadFileOptions,
62}
63
64impl DocumentUploadFileRequest {
65 pub fn new(knowledge_id: impl Into<String>) -> Self {
67 Self {
68 knowledge_id: knowledge_id.into(),
69 files: Vec::new(),
70 options: UploadFileOptions::default(),
71 }
72 }
73
74 pub fn add_file_path(mut self, path: impl Into<PathBuf>) -> Self {
76 self.files.push(path.into());
77 self
78 }
79
80 pub fn with_options(mut self, opts: UploadFileOptions) -> Self {
82 self.options = opts;
83 self
84 }
85
86 pub fn options_mut(&mut self) -> &mut UploadFileOptions {
88 &mut self.options
89 }
90
91 fn validate_cross(&self) -> crate::ZaiResult<()> {
93 if let Some(DocumentSliceType::Custom) = self.options.knowledge_type {
95 if let Some(sz) = self.options.sentence_size
97 && !(20..=2000).contains(&sz)
98 {
99 return Err(crate::client::error::ZaiError::ApiError {
100 code: crate::client::error::codes::SDK_VALIDATION,
101 message: "sentence_size must be 20..=2000 when knowledge_type=Custom (5)"
102 .to_string(),
103 });
104 }
105 }
106 if let Some(ref w) = self.options.word_num_limit
107 && !w.chars().all(|c| c.is_ascii_digit())
108 {
109 return Err(crate::client::error::ZaiError::ApiError {
110 code: crate::client::error::codes::SDK_VALIDATION,
111 message: "word_num_limit must be numeric string".to_string(),
112 });
113 }
114 if self.files.is_empty() {
115 return Err(crate::client::error::ZaiError::ApiError {
116 code: crate::client::error::codes::SDK_VALIDATION,
117 message: "at least one file path must be provided".to_string(),
118 });
119 }
120 Ok(())
121 }
122
123 pub async fn send_via(&self, client: &ZaiClient) -> crate::ZaiResult<UploadFileResponse> {
125 self.options.validate()?;
126 self.validate_cross()?;
127
128 let route = crate::client::routes::DOCUMENTS_UPLOAD;
129 let url = client
130 .endpoints()
131 .resolve_route(route, &[&self.knowledge_id])?;
132 let mut factory = crate::client::transport::multipart::MultipartBodyFactory::new();
133 if let Some(t) = self.options.knowledge_type {
134 factory = factory.field("knowledge_type", t.as_i64().to_string())?;
135 }
136 if let Some(seps) = self.options.custom_separator.as_ref() {
137 factory = factory.field("custom_separator", serde_json::to_string(seps)?)?;
138 }
139 if let Some(sz) = self.options.sentence_size {
140 factory = factory.field("sentence_size", sz.to_string())?;
141 }
142 if let Some(pi) = self.options.parse_image {
143 factory = factory.field("parse_image", pi.to_string())?;
144 }
145 if let Some(url) = self.options.callback_url.as_ref() {
146 factory = factory.field("callback_url", url.clone())?;
147 }
148 if let Some(header) = self.options.callback_header.as_ref() {
149 factory = factory.field("callback_header", serde_json::to_string(header)?)?;
150 }
151 if let Some(limit) = self.options.word_num_limit.as_ref() {
152 factory = factory.field("word_num_limit", limit.clone())?;
153 }
154 if let Some(req_id) = self.options.req_id.as_ref() {
155 factory = factory.field("req_id", req_id.clone())?;
156 }
157 for path in &self.files {
158 let part = crate::client::transport::multipart::FilePart::from_path(path)?;
159 factory = factory.file_named("files", part)?;
160 }
161 client
162 .send_multipart::<UploadFileResponse>(route.method(), url, &factory)
163 .await
164 }
165}