1use std::{path::PathBuf, sync::Arc};
2
3use super::request::FilePurpose;
4use crate::client::{
5 endpoints::{ApiBase, EndpointConfig, paths},
6 http::{HttpClient, HttpClientConfig, parse_typed_response, send_multipart_request},
7};
8
9pub struct FileUploadRequest {
15 pub key: String,
16 url: String,
17 endpoint_config: EndpointConfig,
18 api_base: ApiBase,
19 http_config: Arc<HttpClientConfig>,
20 purpose: FilePurpose,
21 file_path: PathBuf,
22 file_name: Option<String>,
23 content_type: Option<String>,
24 _body: (),
25}
26
27impl FileUploadRequest {
28 pub fn new(key: String, purpose: FilePurpose, file_path: impl Into<PathBuf>) -> Self {
29 let endpoint_config = EndpointConfig::default();
30 let api_base = ApiBase::PaasV4;
31 let url = endpoint_config.url(&api_base, paths::FILES);
32 Self {
33 key,
34 url,
35 endpoint_config,
36 api_base,
37 http_config: Arc::new(HttpClientConfig::default()),
38 purpose,
39 file_path: file_path.into(),
40 file_name: None,
41 content_type: None,
42 _body: (),
43 }
44 }
45
46 pub fn with_file_name(mut self, name: impl Into<String>) -> Self {
47 self.file_name = Some(name.into());
48 self
49 }
50
51 pub fn with_content_type(mut self, ct: impl Into<String>) -> Self {
52 self.content_type = Some(ct.into());
53
54 self
55 }
56
57 pub fn with_base_url(mut self, base_url: impl Into<String>) -> Self {
58 self.api_base = ApiBase::Custom(base_url.into());
59 self.url = self.endpoint_config.url(&self.api_base, paths::FILES);
60 self
61 }
62
63 pub fn with_endpoint_config(mut self, endpoint_config: EndpointConfig) -> Self {
64 self.endpoint_config = endpoint_config;
65 self.url = self.endpoint_config.url(&self.api_base, paths::FILES);
66 self
67 }
68
69 pub fn with_http_config(mut self, config: HttpClientConfig) -> Self {
70 self.http_config = Arc::new(config);
71 self
72 }
73
74 pub async fn send(&self) -> crate::ZaiResult<super::response::FileObject> {
76 let resp: reqwest::Response = self.post().await?;
77 parse_typed_response::<super::response::FileObject>(resp).await
78 }
79}
80
81impl HttpClient for FileUploadRequest {
82 type Body = (); type ApiUrl = String;
84 type ApiKey = String;
85
86 fn api_url(&self) -> &Self::ApiUrl {
87 &self.url
88 }
89 fn api_key(&self) -> &Self::ApiKey {
90 &self.key
91 }
92 fn body(&self) -> &Self::Body {
93 &self._body
94 }
95
96 fn post(
99 &self,
100 ) -> impl std::future::Future<Output = crate::ZaiResult<reqwest::Response>> + Send {
101 let url = self.url.clone();
102 let key: String = self.key.clone();
103 let config = self.http_config.clone();
104 let purpose = self.purpose.clone();
105 let path = self.file_path.clone();
106 let file_name = self.file_name.clone();
107 let content_type = self.content_type.clone();
108 async move {
109 let fname = file_name
110 .or_else(|| {
111 path.file_name()
112 .and_then(|s| s.to_str())
113 .map(|s| s.to_string())
114 })
115 .unwrap_or_else(|| "upload.bin".to_string());
116
117 let bytes = std::fs::read(&path)?;
118 send_multipart_request(reqwest::Method::POST, url, key, config, move || {
119 let mut part =
120 reqwest::multipart::Part::bytes(bytes.clone()).file_name(fname.clone());
121 if let Some(ct) = content_type.as_ref() {
122 part = part.mime_str(ct).map_err(|e| {
123 crate::client::error::ZaiError::ApiError {
124 code: 1200,
125 message: format!("invalid content-type: {}", e),
126 }
127 })?;
128 }
129 Ok(reqwest::multipart::Form::new()
130 .text("purpose", purpose.as_str().to_string())
131 .part("file", part))
132 })
133 .await
134 }
135 }
136
137 fn http_config(&self) -> Arc<HttpClientConfig> {
138 self.http_config.clone()
139 }
140}