Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add individual content types for formadata #4550

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add individual content types for formadata
  • Loading branch information
amk-dev authored and jamesgeorge007 committed Nov 24, 2024
commit 3ef4b18354a3218cb3f77a051f4a8e2e1600491c
33 changes: 33 additions & 0 deletions packages/hoppscotch-common/src/components/http/BodyParameters.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
{{ t("request.body") }}
</label>
<div class="flex">
<div class="flex items-center gap-2">
<HoppSmartCheckbox
:on="useIndividualContentTypes"
@change="
() => {
useIndividualContentTypes = !useIndividualContentTypes
}
"
>Use Individual Content Types</HoppSmartCheckbox
>
</div>
<HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }"
to="https://docs.hoppscotch.io/documentation/getting-started/rest/uploading-data"
Expand Down Expand Up @@ -101,6 +112,25 @@
"
/>
</span>
<span v-if="useIndividualContentTypes" class="flex flex-1">
<SmartEnvInput
v-model="entry.contentType"
:placeholder="
entry.contentType ? entry.contentType : `Auto (Content Type)`
"
:auto-complete-env="true"
:envs="envs"
@change="
updateBodyParam(index, {
key: entry.key,
value: entry.value,
active: entry.active,
isFile: entry.isFile,
contentType: $event,
})
"
/>
</span>
<span>
<label :for="`attachment${index}`" class="p-0">
<input
Expand Down Expand Up @@ -222,6 +252,8 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)

const bodyParams = pluckRef(body, "body")

const useIndividualContentTypes = ref(true)

// The UI representation of the parameters list (has the empty end param)
const workingParams = ref<WorkingFormDataKeyValue[]>([
{
Expand All @@ -231,6 +263,7 @@ const workingParams = ref<WorkingFormDataKeyValue[]>([
value: "",
active: true,
isFile: false,
contentType: undefined,
},
},
])
Expand Down
17 changes: 16 additions & 1 deletion packages/hoppscotch-common/src/helpers/functional/formData.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
type FormDataEntry = {
key: string
contentType?: string
value: string | Blob
}

export const toFormData = (values: FormDataEntry[]) => {
const formData = new FormData()

values.forEach(({ key, value }) => formData.append(key, value))
values.forEach(({ key, value, contentType }) => {
if (contentType) {
formData.append(
key,
new Blob([value], {
type: contentType,
}),
key
)

return
}

formData.append(key, value)
})

return formData
}
2 changes: 2 additions & 0 deletions packages/hoppscotch-common/src/helpers/utils/EffectiveURL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -512,11 +512,13 @@ function getFinalBodyFromRequest(
? x.value.map((v) => ({
key: parseTemplateString(x.key, envVariables),
value: v as string | Blob,
contentType: x.contentType,
}))
: [
{
key: parseTemplateString(x.key, envVariables),
value: parseTemplateString(x.value, envVariables),
contentType: x.contentType,
},
]
),
Expand Down
12 changes: 7 additions & 5 deletions packages/hoppscotch-data/src/rest/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,18 @@ import V2_VERSION, { HoppRESTRequestVariables } from "./v/2"
import V3_VERSION from "./v/3"
import V4_VERSION from "./v/4"
import V5_VERSION from "./v/5"
import V6_VERSION, { HoppRESTReqBody } from "./v/6"
import V6_VERSION from "./v/6"
import V7_VERSION, { HoppRESTHeaders, HoppRESTParams } from "./v/7"
import V8_VERSION, { HoppRESTAuth, HoppRESTRequestResponses } from "./v/8"
import V9_VERSION, { HoppRESTReqBody } from "./v/9"

export * from "./content-types"

export {
FormDataKeyValue,
HoppRESTAuthBasic,
HoppRESTAuthBearer,
HoppRESTAuthInherit,
HoppRESTAuthNone,
HoppRESTReqBodyFormData,
} from "./v/1"

export { HoppRESTRequestVariables } from "./v/2"
Expand Down Expand Up @@ -54,13 +53,15 @@ export {
HoppRESTRequestResponses,
} from "./v/8"

export { FormDataKeyValue } from "./v/9"

const versionedObject = z.object({
// v is a stringified number
v: z.string().regex(/^\d+$/).transform(Number),
})

export const HoppRESTRequest = createVersionedEntity({
latestVersion: 8,
latestVersion: 9,
versionMap: {
0: V0_VERSION,
1: V1_VERSION,
Expand All @@ -71,6 +72,7 @@ export const HoppRESTRequest = createVersionedEntity({
6: V6_VERSION,
7: V7_VERSION,
8: V8_VERSION,
9: V9_VERSION,
},
getVersion(data) {
// For V1 onwards we have the v string storing the number
Expand Down Expand Up @@ -113,7 +115,7 @@ const HoppRESTRequestEq = Eq.struct<HoppRESTRequest>({
responses: lodashIsEqualEq,
})

export const RESTReqSchemaVersion = "8"
export const RESTReqSchemaVersion = "9"

export type HoppRESTParam = HoppRESTRequest["params"][number]
export type HoppRESTHeader = HoppRESTRequest["headers"][number]
Expand Down
69 changes: 69 additions & 0 deletions packages/hoppscotch-data/src/rest/v/9.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { defineVersion } from "verzod"
import { z } from "zod"

import { V8_SCHEMA } from "./8"

export const FormDataKeyValue = z
.object({
key: z.string(),
active: z.boolean(),
contentType: z.string().optional().catch(undefined),
})
.and(
z.union([
z.object({
isFile: z.literal(true),
value: z.array(z.instanceof(Blob).nullable()),
}),
z.object({
isFile: z.literal(false),
value: z.string(),
}),
])
)

export type FormDataKeyValue = z.infer<typeof FormDataKeyValue>

export const HoppRESTReqBody = z.union([
z.object({
contentType: z.literal(null),
body: z.literal(null).catch(null),
}),
z.object({
contentType: z.literal("multipart/form-data"),
body: z.array(FormDataKeyValue).catch([]),
}),
z.object({
contentType: z.union([
z.literal("application/json"),
z.literal("application/ld+json"),
z.literal("application/hal+json"),
z.literal("application/vnd.api+json"),
z.literal("application/xml"),
z.literal("text/xml"),
z.literal("application/x-www-form-urlencoded"),
z.literal("text/html"),
z.literal("text/plain"),
]),
body: z.string().catch(""),
}),
])

export type HoppRESTReqBody = z.infer<typeof HoppRESTReqBody>

export const V9_SCHEMA = V8_SCHEMA.extend({
v: z.literal("9"),
body: HoppRESTReqBody,
})

export default defineVersion({
schema: V9_SCHEMA,
initial: false,
up(old: z.infer<typeof V8_SCHEMA>) {
// No migration, the new contentType added to each formdata field is optional
return {
...old,
v: "9" as const,
}
},
})