remoteit_api/
file_upload.rs1use bon::{Builder, bon, builder};
8use std::path::PathBuf;
9
10use crate::auth::{build_auth_header, get_date};
11
12#[derive(Debug, Clone, Builder)]
14pub struct FileUpload {
15 pub file_name: String,
17 #[builder(into)]
19 pub file_path: PathBuf,
20 pub executable: bool,
22 pub short_desc: Option<String>,
24 pub long_desc: Option<String>,
26}
27
28#[derive(serde::Deserialize, Clone, Debug)]
30#[serde(rename_all = "camelCase")]
31pub struct UploadFileResponse {
32 pub file_id: String,
34 pub file_version_id: String,
36 pub version: u32,
38 pub name: String,
40 pub executable: bool,
42 pub owner_id: String,
44 pub file_arguments: Vec<serde_json::Value>,
47}
48
49#[derive(serde::Deserialize, Clone, Debug)]
51pub struct ErrorResponse {
52 pub message: String,
54}
55
56#[derive(thiserror::Error, Debug)]
57#[allow(missing_docs)]
58pub enum UploadFileError {
59 #[error("IO error while uploading file: {0}")]
60 IO(#[from] std::io::Error),
61 #[error("Failed to send upload file request: {0}")]
62 Reqwest(#[from] reqwest::Error),
63 #[error("Failed to parse response JSON: {0}")]
64 ParseJson(reqwest::Error),
65 #[error("The API returned an error: {0:?}")]
66 ApiError(ErrorResponse),
67}
68
69#[cfg(feature = "blocking")]
70#[bon]
71impl crate::R3Client {
72 #[builder]
86 pub fn upload_file(
87 &self,
88 file_upload: FileUpload,
89 ) -> Result<UploadFileResponse, UploadFileError> {
90 use crate::BASE_URL;
91 use crate::FILE_UPLOAD_PATH;
92
93 let client = reqwest::blocking::Client::new();
94 let mut form = reqwest::blocking::multipart::Form::new()
95 .file(file_upload.file_name, file_upload.file_path)?
96 .text("executable", file_upload.executable.to_string());
97
98 if let Some(short_descr) = file_upload.short_desc {
99 form = form.text("shortDesc", short_descr);
100 }
101 if let Some(long_descr) = file_upload.long_desc {
102 form = form.text("longDesc", long_descr);
103 }
104
105 #[cfg(debug_assertions)]
106 dbg!(&form);
107
108 let content_type = format!("multipart/form-data; boundary={}", form.boundary());
109 let date = get_date();
110 let auth_header = build_auth_header()
111 .key_id(self.credentials.access_key_id())
112 .key(self.credentials.key())
113 .content_type(&content_type)
114 .method(&reqwest::Method::POST)
115 .path(FILE_UPLOAD_PATH)
116 .date(&date)
117 .call();
118
119 let response = client
120 .post(format!("{BASE_URL}{FILE_UPLOAD_PATH}"))
121 .header("Date", date)
122 .header("Authorization", auth_header)
123 .header("Content-Type", content_type)
124 .multipart(form)
125 .send()?;
126
127 if response.status().is_success() {
128 let file_upload_response = response
129 .json::<UploadFileResponse>()
130 .map_err(UploadFileError::ParseJson)?;
131 Ok(file_upload_response)
132 } else {
133 let response: ErrorResponse = response.json().map_err(UploadFileError::ParseJson)?;
134 Err(UploadFileError::ApiError(response))
135 }
136 }
137}
138
139#[cfg(feature = "async")]
140#[bon]
141impl crate::R3Client {
142 #[builder]
156 pub async fn upload_file_async(
157 &self,
158 file_upload: FileUpload,
159 ) -> Result<UploadFileResponse, UploadFileError> {
160 use crate::BASE_URL;
161 use crate::FILE_UPLOAD_PATH;
162
163 let client = reqwest::Client::new();
164
165 let file_name = file_upload
166 .file_path
167 .file_name()
168 .map(|val| val.to_string_lossy().to_string())
169 .unwrap_or_default();
170
171 let file = tokio::fs::File::open(&file_upload.file_name).await?;
172
173 let reader = reqwest::Body::wrap_stream(tokio_util::codec::FramedRead::new(
174 file,
175 tokio_util::codec::BytesCodec::new(),
176 ));
177 let mut form = reqwest::multipart::Form::new()
178 .part(
179 file_upload.file_name,
180 reqwest::multipart::Part::stream(reader).file_name(file_name),
181 )
182 .text("executable", file_upload.executable.to_string());
183
184 if let Some(short_descr) = file_upload.short_desc {
185 form = form.text("shortDesc", short_descr);
186 }
187 if let Some(long_descr) = file_upload.long_desc {
188 form = form.text("longDesc", long_descr);
189 }
190
191 #[cfg(debug_assertions)]
192 dbg!(&form);
193
194 let content_type = format!("multipart/form-data; boundary={}", form.boundary());
195 let date = get_date();
196 let auth_header = build_auth_header()
197 .key_id(self.credentials.access_key_id())
198 .key(self.credentials.key())
199 .content_type(&content_type)
200 .method(&reqwest::Method::POST)
201 .path(FILE_UPLOAD_PATH)
202 .date(&date)
203 .call();
204
205 let response = client
206 .post(format!("{BASE_URL}{FILE_UPLOAD_PATH}"))
207 .header("Date", date)
208 .header("Authorization", auth_header)
209 .header("Content-Type", content_type)
210 .multipart(form)
211 .send()
212 .await?;
213
214 if response.status().is_success() {
215 let file_upload_response = response
216 .json::<UploadFileResponse>()
217 .await
218 .map_err(UploadFileError::ParseJson)?;
219 Ok(file_upload_response)
220 } else {
221 let response: ErrorResponse =
222 response.json().await.map_err(UploadFileError::ParseJson)?;
223 Err(UploadFileError::ApiError(response))
224 }
225 }
226}