Skip to content

Commit

Permalink
feat(api/http): properly handle Files in Body.form, closes #7390 (#7394)
Browse files Browse the repository at this point in the history
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
closes #7390
  • Loading branch information
amrbashir committed Jul 13, 2023
1 parent fdaee9a commit 2eab150
Show file tree
Hide file tree
Showing 7 changed files with 102 additions and 78 deletions.
5 changes: 5 additions & 0 deletions .changes/api-formAsync.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tauri-apps/api': 'patch:bug'
---

Fix `Body.form` static not reading and sending entries of type `Blob` (including subclasses such as `File`)
12 changes: 6 additions & 6 deletions core/tauri/scripts/bundle.global.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/api/dist/assets/index.css

Large diffs are not rendered by default.

58 changes: 29 additions & 29 deletions examples/api/dist/assets/index.js

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions examples/api/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tooling/api/docs/js-api.json

Large diffs are not rendered by default.

87 changes: 53 additions & 34 deletions tooling/api/src/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,50 @@ interface FilePart<T> {

type Part = string | Uint8Array | FilePart<Uint8Array>

type FormInput = Record<string, Part> | FormData
type FormBody = Record<string, string | number[] | FilePart<number[]>>

async function formBody(data: FormInput): Promise<FormBody> {
const form: FormBody = {}

const append = async (
key: string,
v: string | Uint8Array | FilePart<Uint8Array> | File
): Promise<void> => {
if (v !== null) {
let r
if (typeof v === 'string') {
r = v
} else if (v instanceof Uint8Array || Array.isArray(v)) {
r = Array.from(v)
} else if (v instanceof File) {
r = {
file: Array.from(new Uint8Array(await v.arrayBuffer())),
mime: v.type,
fileName: v.name
}
} else if (typeof v.file === 'string') {
r = { file: v.file, mime: v.mime, fileName: v.fileName }
} else {
r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName }
}
form[String(key)] = r
}
}

if (data instanceof FormData) {
for (const [key, value] of data) {
await append(key, value)
}
} else {
for (const [key, value] of Object.entries(data)) {
await append(key, value)
}
}

return form
}

/**
* The body object to be used on POST and PUT requests.
*
Expand All @@ -108,6 +152,7 @@ class Body {
* but you can set it to `multipart/form-data` if the Cargo feature `http-multipart` is enabled.
*
* Note that a file path must be allowed in the `fs` allowlist scope.
*
* @example
* ```typescript
* import { Body } from "@tauri-apps/api/http"
Expand All @@ -131,40 +176,8 @@ class Body {
*
* @returns The body object ready to be used on the POST and PUT requests.
*/
static form(data: Record<string, Part> | FormData): Body {
const form: Record<string, string | number[] | FilePart<number[]>> = {}

const append = (
key: string,
v: string | Uint8Array | FilePart<Uint8Array> | File
): void => {
if (v !== null) {
let r
if (typeof v === 'string') {
r = v
} else if (v instanceof Uint8Array || Array.isArray(v)) {
r = Array.from(v)
} else if (v instanceof File) {
r = { file: v.name, mime: v.type, fileName: v.name }
} else if (typeof v.file === 'string') {
r = { file: v.file, mime: v.mime, fileName: v.fileName }
} else {
r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName }
}
form[String(key)] = r
}
}

if (data instanceof FormData) {
for (const [key, value] of data) {
append(key, value)
}
} else {
for (const [key, value] of Object.entries(data)) {
append(key, value)
}
}
return new Body('Form', form)
static form(data: FormInput): Body {
return new Body('Form', data)
}

/**
Expand Down Expand Up @@ -343,6 +356,11 @@ class Client {
if (jsonResponse) {
options.responseType = ResponseType.Text
}

if (options.body?.type === 'Form') {
options.body.payload = await formBody(options.body.payload as FormInput)
}

return invokeTauriCommand<IResponse<T>>({
__tauriModule: 'Http',
message: {
Expand Down Expand Up @@ -546,6 +564,7 @@ export type {
Duration,
ClientOptions,
Part,
FormInput,
HttpVerb,
HttpOptions,
RequestOptions,
Expand Down

0 comments on commit 2eab150

Please sign in to comment.