Skip to content

Commit 412dcc6

Browse files
feat(upload): Add HTTP method for upload
1 parent 12da195 commit 412dcc6

File tree

4 files changed

+123
-21
lines changed

4 files changed

+123
-21
lines changed

examples/api/src/views/Upload.svelte

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { download, upload } from '@tauri-apps/plugin-upload'
2+
import { download, upload, HttpMethod } from '@tauri-apps/plugin-upload'
33
import { open } from '@tauri-apps/plugin-dialog'
44
import { JsonView } from '@zerodevx/svelte-json-view'
55
import { appDataDir } from '@tauri-apps/api/path'
@@ -16,6 +16,22 @@
1616
1717
let uploadUrl = 'https://httpbin.org/post'
1818
let uploadFilePath = ''
19+
let uploadMethod = HttpMethod.Post
20+
21+
// Update URL when method changes
22+
$: {
23+
switch (uploadMethod) {
24+
case HttpMethod.Post:
25+
uploadUrl = 'https://httpbin.org/post'
26+
break
27+
case HttpMethod.Put:
28+
uploadUrl = 'https://httpbin.org/put'
29+
break
30+
case HttpMethod.Patch:
31+
uploadUrl = 'https://httpbin.org/patch'
32+
break
33+
}
34+
}
1935
let uploadProgress = null
2036
let uploadResult = null
2137
let isUploading = false
@@ -197,7 +213,8 @@
197213
},
198214
new Map([
199215
['User-Agent', 'Tauri Upload Plugin Demo']
200-
])
216+
]),
217+
uploadMethod
201218
)
202219
203220
uploadResult = {
@@ -340,12 +357,36 @@
340357
</div>
341358
</div>
342359

360+
<div>
361+
<label for="upload-method" class="block text-sm font-medium text-gray-700 mb-1">HTTP Method:</label>
362+
<select
363+
id="upload-method"
364+
bind:value={uploadMethod}
365+
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-green-500"
366+
disabled={isUploading}
367+
>
368+
<option value={HttpMethod.Post}>POST</option>
369+
<option value={HttpMethod.Put}>PUT</option>
370+
<option value={HttpMethod.Patch}>PATCH</option>
371+
</select>
372+
<p class="text-xs text-gray-500 mt-1">Choose the HTTP method for the upload request</p>
373+
</div>
374+
375+
<div class="bg-blue-50 border border-blue-200 p-3 rounded-md">
376+
<div class="text-sm text-blue-800">
377+
<strong>Upload Configuration:</strong>
378+
<div class="font-mono text-xs mt-1">
379+
Method: {uploadMethod} | URL: {uploadUrl || 'Not set'}
380+
</div>
381+
</div>
382+
</div>
383+
343384
<button
344385
on:click={startUpload}
345386
class="w-full px-4 py-2 bg-green-500 text-white rounded-md hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
346387
disabled={isUploading || !uploadUrl || !uploadFilePath}
347388
>
348-
{isUploading ? 'Uploading...' : 'Upload File'}
389+
{isUploading ? `Uploading (${uploadMethod})...` : `Upload File (${uploadMethod})`}
349390
</button>
350391

351392
{#if uploadProgress}

plugins/upload/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,22 @@ Afterwards all the plugin's APIs are available through the JavaScript guest bind
6262
```javascript
6363
import { upload } from '@tauri-apps/plugin-upload'
6464

65+
// Upload with default POST method
6566
upload(
6667
'https://example.com/file-upload',
6768
'./path/to/my/file.txt',
6869
(progress, total) => console.log(`Uploaded ${progress} of ${total} bytes`), // a callback that will be called with the upload progress
6970
{ 'Content-Type': 'text/plain' } // optional headers to send with the request
7071
)
72+
73+
// Upload with specific HTTP method (POST, PUT, or PATCH)
74+
upload(
75+
'https://example.com/file-upload',
76+
'./path/to/my/file.txt',
77+
(progress, total) => console.log(`Uploaded ${progress} of ${total} bytes`),
78+
{ 'Content-Type': 'text/plain' },
79+
'PUT' // optional HTTP method - defaults to 'POST' if not specified
80+
)
7181
```
7282

7383
```javascript

plugins/upload/guest-js/index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@ interface ProgressPayload {
1313

1414
type ProgressHandler = (progress: ProgressPayload) => void
1515

16+
enum HttpMethod {
17+
Post = 'POST',
18+
Put = 'PUT',
19+
Patch = 'PATCH'
20+
}
21+
1622
async function upload(
1723
url: string,
1824
filePath: string,
1925
progressHandler?: ProgressHandler,
20-
headers?: Map<string, string>
26+
headers?: Map<string, string>,
27+
method?: HttpMethod
2128
): Promise<string> {
2229
const ids = new Uint32Array(1)
2330
window.crypto.getRandomValues(ids)
@@ -33,6 +40,7 @@ async function upload(
3340
url,
3441
filePath,
3542
headers: headers ?? {},
43+
method: method ?? HttpMethod.Post,
3644
onProgress
3745
})
3846
}
@@ -67,4 +75,4 @@ async function download(
6775
})
6876
}
6977

70-
export { download, upload }
78+
export { download, upload, HttpMethod }

plugins/upload/src/lib.rs

Lines changed: 59 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ mod transfer_stats;
1515
use transfer_stats::TransferStats;
1616

1717
use futures_util::TryStreamExt;
18-
use serde::{ser::Serializer, Serialize};
18+
use serde::{ser::Serializer, Deserialize, Serialize};
1919
use tauri::{
2020
command,
2121
ipc::Channel,
@@ -32,6 +32,14 @@ use read_progress_stream::ReadProgressStream;
3232

3333
use std::collections::HashMap;
3434

35+
#[derive(Debug, Clone, Serialize, Deserialize)]
36+
#[serde(rename_all = "UPPERCASE")]
37+
pub enum HttpMethod {
38+
Post,
39+
Put,
40+
Patch,
41+
}
42+
3543
type Result<T> = std::result::Result<T, Error>;
3644

3745
#[derive(Debug, thiserror::Error)]
@@ -120,19 +128,26 @@ async fn upload(
120128
url: String,
121129
file_path: String,
122130
headers: HashMap<String, String>,
131+
method: Option<HttpMethod>,
123132
on_progress: Channel<ProgressPayload>,
124133
) -> Result<String> {
125134
tokio::spawn(async move {
126135
// Read the file
127136
let file = File::open(&file_path).await?;
128137
let file_len = file.metadata().await.unwrap().len();
129138

139+
// Get HTTP method (defaults to POST)
140+
let http_method = method.unwrap_or(HttpMethod::Post);
141+
130142
// Create the request and attach the file to the body
131143
let client = reqwest::Client::new();
132-
let mut request = client
133-
.post(&url)
134-
.header(reqwest::header::CONTENT_LENGTH, file_len)
135-
.body(file_to_body(on_progress, file, file_len));
144+
let mut request = match http_method {
145+
HttpMethod::Put => client.put(&url),
146+
HttpMethod::Patch => client.patch(&url),
147+
HttpMethod::Post => client.post(&url),
148+
}
149+
.header(reqwest::header::CONTENT_LENGTH, file_len)
150+
.body(file_to_body(on_progress, file, file_len));
136151

137152
// Loop through the headers keys and values
138153
// and add them to the request object.
@@ -211,8 +226,8 @@ mod tests {
211226

212227
#[tokio::test]
213228
async fn should_error_on_upload_if_status_not_success() {
214-
let mocked_server = spawn_upload_server_mocked(500).await;
215-
let result = upload_file(mocked_server.url).await;
229+
let mocked_server = spawn_upload_server_mocked(500, "POST").await;
230+
let result = upload_file(mocked_server.url, None).await;
216231
mocked_server.mocked_endpoint.assert();
217232
assert!(result.is_err());
218233
match result.unwrap_err() {
@@ -223,7 +238,7 @@ mod tests {
223238

224239
#[tokio::test]
225240
async fn should_error_on_upload_if_file_not_found() {
226-
let mocked_server = spawn_upload_server_mocked(200).await;
241+
let mocked_server = spawn_upload_server_mocked(200, "POST").await;
227242
let file_path = "/nonexistent/file.txt".to_string();
228243
let headers = HashMap::new();
229244
let sender: Channel<ProgressPayload> =
@@ -232,7 +247,7 @@ mod tests {
232247
Ok(())
233248
});
234249

235-
let result = upload(mocked_server.url, file_path, headers, sender).await;
250+
let result = upload(mocked_server.url, file_path, headers, None, sender).await;
236251
assert!(result.is_err());
237252
match result.unwrap_err() {
238253
Error::Io(_) => {}
@@ -241,9 +256,9 @@ mod tests {
241256
}
242257

243258
#[tokio::test]
244-
async fn should_upload_file_successfully() {
245-
let mocked_server = spawn_upload_server_mocked(200).await;
246-
let result = upload_file(mocked_server.url).await;
259+
async fn should_upload_file_with_post_method() {
260+
let mocked_server = spawn_upload_server_mocked(200, "POST").await;
261+
let result = upload_file(mocked_server.url, Some(HttpMethod::Post)).await;
247262
mocked_server.mocked_endpoint.assert();
248263
assert!(
249264
result.is_ok(),
@@ -254,6 +269,34 @@ mod tests {
254269
assert_eq!(response_body, "upload successful");
255270
}
256271

272+
#[tokio::test]
273+
async fn should_upload_file_with_put_method() {
274+
let mocked_server = spawn_upload_server_mocked(200, "PUT").await;
275+
let result = upload_file(mocked_server.url, Some(HttpMethod::Put)).await;
276+
mocked_server.mocked_endpoint.assert();
277+
assert!(
278+
result.is_ok(),
279+
"failed to upload file with PUT: {}",
280+
result.unwrap_err()
281+
);
282+
let response_body = result.unwrap();
283+
assert_eq!(response_body, "upload successful");
284+
}
285+
286+
#[tokio::test]
287+
async fn should_upload_file_with_patch_method() {
288+
let mocked_server = spawn_upload_server_mocked(200, "PATCH").await;
289+
let result = upload_file(mocked_server.url, Some(HttpMethod::Patch)).await;
290+
mocked_server.mocked_endpoint.assert();
291+
assert!(
292+
result.is_ok(),
293+
"failed to upload file with PATCH: {}",
294+
result.unwrap_err()
295+
);
296+
let response_body = result.unwrap();
297+
assert_eq!(response_body, "upload successful");
298+
}
299+
257300
async fn download_file(url: String) -> Result<()> {
258301
let file_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test/test.txt").to_string();
259302
let headers = HashMap::new();
@@ -265,15 +308,15 @@ mod tests {
265308
download(url, file_path, headers, None, sender).await
266309
}
267310

268-
async fn upload_file(url: String) -> Result<String> {
311+
async fn upload_file(url: String, method: Option<HttpMethod>) -> Result<String> {
269312
let file_path = concat!(env!("CARGO_MANIFEST_DIR"), "/test/test.txt").to_string();
270313
let headers = HashMap::new();
271314
let sender: Channel<ProgressPayload> =
272315
Channel::new(|msg: InvokeResponseBody| -> tauri::Result<()> {
273316
let _ = msg;
274317
Ok(())
275318
});
276-
upload(url, file_path, headers, sender).await
319+
upload(url, file_path, headers, method, sender).await
277320
}
278321

279322
async fn spawn_server_mocked(return_status: usize) -> MockedServer {
@@ -294,11 +337,11 @@ mod tests {
294337
}
295338
}
296339

297-
async fn spawn_upload_server_mocked(return_status: usize) -> MockedServer {
340+
async fn spawn_upload_server_mocked(return_status: usize, method: &str) -> MockedServer {
298341
let mut _server = Server::new_async().await;
299342
let path = "/upload_test";
300343
let mock = _server
301-
.mock("POST", path)
344+
.mock(method, path)
302345
.with_status(return_status)
303346
.with_body("upload successful")
304347
.match_header("content-length", "20")

0 commit comments

Comments
 (0)