feat: support for binary body (#4466)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -43,5 +43,5 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
active: boolean;
|
active: boolean;
|
||||||
description: string;
|
description: string;
|
||||||
}[];
|
}[];
|
||||||
effectiveFinalBody: FormData | string | null;
|
effectiveFinalBody: FormData | string | File | null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ export async function getEffectiveRESTRequest(
|
|||||||
function getFinalBodyFromRequest(
|
function getFinalBodyFromRequest(
|
||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
resolvedVariables: EnvironmentVariable[]
|
resolvedVariables: EnvironmentVariable[]
|
||||||
): E.Either<HoppCLIError, string | null | FormData> {
|
): E.Either<HoppCLIError, string | null | FormData | File> {
|
||||||
if (request.body.contentType === null) {
|
if (request.body.contentType === null) {
|
||||||
return E.right(null);
|
return E.right(null);
|
||||||
}
|
}
|
||||||
@@ -437,6 +437,20 @@ function getFinalBodyFromRequest(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.body.contentType === "application/octet-stream") {
|
||||||
|
const body = request.body.body;
|
||||||
|
|
||||||
|
if (!body) {
|
||||||
|
return E.right(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(body instanceof File)) {
|
||||||
|
return E.right(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return E.right(body);
|
||||||
|
}
|
||||||
|
|
||||||
return pipe(
|
return pipe(
|
||||||
parseBodyEnvVariablesE(request.body.body, resolvedVariables),
|
parseBodyEnvVariablesE(request.body.body, resolvedVariables),
|
||||||
E.mapLeft((e) =>
|
E.mapLeft((e) =>
|
||||||
|
|||||||
@@ -574,6 +574,9 @@
|
|||||||
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
"extension_unknown_origin": "Make sure you've added the API endpoint's origin to the Hoppscotch Browser Extension list.",
|
||||||
"extention_enable_action": "Enable Browser Extension",
|
"extention_enable_action": "Enable Browser Extension",
|
||||||
"extention_not_enabled": "Extension not enabled."
|
"extention_not_enabled": "Extension not enabled."
|
||||||
|
},
|
||||||
|
"requestBody": {
|
||||||
|
"agent_doesnt_support_binary_body": "Sending binary data via agent is not supported yet"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
@@ -666,7 +669,8 @@
|
|||||||
"content_type_titles": {
|
"content_type_titles": {
|
||||||
"others": "Others",
|
"others": "Others",
|
||||||
"structured": "Structured",
|
"structured": "Structured",
|
||||||
"text": "Text"
|
"text": "Text",
|
||||||
|
"binary": "Binary"
|
||||||
},
|
},
|
||||||
"show_content_type": "Show Content Type",
|
"show_content_type": "Show Content Type",
|
||||||
"different_collection": "Cannot reorder requests from different collections",
|
"different_collection": "Cannot reorder requests from different collections",
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
>
|
>
|
||||||
{{ inspector.text.text }}
|
{{ inspector.text.text }}
|
||||||
<HoppSmartLink
|
<HoppSmartLink
|
||||||
|
v-if="inspector.doc"
|
||||||
blank
|
blank
|
||||||
:to="inspector.doc.link"
|
:to="inspector.doc.link"
|
||||||
class="text-accent transition hover:text-accentDark"
|
class="text-accent transition hover:text-accentDark"
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
body.contentType = null
|
body.contentType = null
|
||||||
|
body.body = null
|
||||||
hide()
|
hide()
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
@@ -76,6 +77,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
|
<AppInspection :inspection-results="tabResults" />
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
||||||
:title="t('request.override_help')"
|
:title="t('request.override_help')"
|
||||||
@@ -107,6 +109,10 @@
|
|||||||
v-model="body"
|
v-model="body"
|
||||||
:envs="envs"
|
:envs="envs"
|
||||||
/>
|
/>
|
||||||
|
<HttpBodyBinary
|
||||||
|
v-else-if="body.contentType === 'application/octet-stream'"
|
||||||
|
v-model="body"
|
||||||
|
/>
|
||||||
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
|
<HttpRawBody v-else-if="body.contentType !== null" v-model="body" />
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="body.contentType == null"
|
v-if="body.contentType == null"
|
||||||
@@ -144,6 +150,9 @@ import IconInfo from "~icons/lucide/info"
|
|||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
import { RESTOptionTabs } from "./RequestOptions.vue"
|
||||||
import { AggregateEnvironment } from "~/newstore/environments"
|
import { AggregateEnvironment } from "~/newstore/environments"
|
||||||
|
import { useService } from "dioc/vue"
|
||||||
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
|
import { InspectionService } from "~/services/inspection"
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
const colorMode = useColorMode()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
@@ -195,4 +204,12 @@ const isContentTypeAlreadyExist = () => {
|
|||||||
|
|
||||||
// Template refs
|
// Template refs
|
||||||
const tippyActions = ref<any | null>(null)
|
const tippyActions = ref<any | null>(null)
|
||||||
|
|
||||||
|
const tabs = useService(RESTTabService)
|
||||||
|
const inspectionService = useService(InspectionService)
|
||||||
|
|
||||||
|
const tabResults = inspectionService.getResultViewFor(
|
||||||
|
tabs.currentTabID.value,
|
||||||
|
(result) => result.locations.type === "body-content-type-header"
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { watch } from "vue"
|
||||||
|
|
||||||
|
type BinaryBody = {
|
||||||
|
contentType: "application/octet-stream"
|
||||||
|
body: File | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: BinaryBody
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "update:modelValue", value: BinaryBody): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
// in the parent component,
|
||||||
|
// there's a invalid assignment happening
|
||||||
|
// when switching between different body types only the content type is reset, not the body
|
||||||
|
// need to look into this
|
||||||
|
// eg: body: some-json-value-user-entered, contentType: "application/json" -> change content type-> body: some-json-value-user-entered, contentType: "application/octet-stream"
|
||||||
|
// this is not caught by the type system
|
||||||
|
// but this behavior right now gives us persistance, which will prevent unwanted data loss
|
||||||
|
// eg: when the user comes back to the json body, the value is still there
|
||||||
|
// so to solve this, we need to consider this too.
|
||||||
|
watch(
|
||||||
|
props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
if (!(val.body instanceof File)) {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
body: null,
|
||||||
|
contentType: "application/octet-stream",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const handleFileChange = (e: Event) => {
|
||||||
|
const target = e.target as HTMLInputElement
|
||||||
|
const file = target.files?.[0]
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
body: file,
|
||||||
|
contentType: "application/octet-stream",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
emit("update:modelValue", {
|
||||||
|
body: null,
|
||||||
|
contentType: "application/octet-stream",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
<label :for="`attachment-binary-body`" class="p-0">
|
||||||
|
<input
|
||||||
|
:id="`attachment-binary-body`"
|
||||||
|
:name="`attachment-binary-body`"
|
||||||
|
type="file"
|
||||||
|
class="cursor-pointer p-1 text-tiny text-secondaryLight transition file:mr-2 file:cursor-pointer file:rounded file:border-0 file:bg-primaryLight file:px-4 file:py-1 file:text-tiny file:text-secondary file:transition hover:text-secondaryDark hover:file:bg-primaryDark hover:file:text-secondaryDark"
|
||||||
|
@change="handleFileChange"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -259,7 +259,7 @@ import { flow, pipe } from "fp-ts/function"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as RA from "fp-ts/ReadonlyArray"
|
import * as RA from "fp-ts/ReadonlyArray"
|
||||||
import { cloneDeep, isEqual } from "lodash-es"
|
import { cloneDeep, isEqual } from "lodash-es"
|
||||||
import { reactive, ref, toRef, watch } from "vue"
|
import { reactive, Ref, ref, toRef, watch } from "vue"
|
||||||
import draggable from "vuedraggable-es"
|
import draggable from "vuedraggable-es"
|
||||||
|
|
||||||
import { computedAsync, useVModel } from "@vueuse/core"
|
import { computedAsync, useVModel } from "@vueuse/core"
|
||||||
@@ -546,16 +546,22 @@ const clearContent = () => {
|
|||||||
|
|
||||||
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, getAggregateEnvs())
|
const aggregateEnvs = useReadonlyStream(aggregateEnvs$, getAggregateEnvs())
|
||||||
|
|
||||||
const computedHeaders = computedAsync(
|
const computedHeaders: Ref<
|
||||||
async () =>
|
{
|
||||||
(await getComputedHeaders(request.value, aggregateEnvs.value, false)).map(
|
source: "auth" | "body"
|
||||||
(header, index) => ({
|
header: HoppRESTHeader
|
||||||
id: `header-${index}`,
|
id: string
|
||||||
...header,
|
}[]
|
||||||
})
|
> = ref([])
|
||||||
),
|
|
||||||
[]
|
watch([props.modelValue, aggregateEnvs], async () => {
|
||||||
)
|
computedHeaders.value = (
|
||||||
|
await getComputedHeaders(props.modelValue, aggregateEnvs.value, false)
|
||||||
|
).map((header, index) => ({
|
||||||
|
id: `header-${index}`,
|
||||||
|
...header,
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
const inheritedProperties = computedAsync(async () => {
|
const inheritedProperties = computedAsync(async () => {
|
||||||
if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers)
|
if (!props.inheritedProperties?.auth || !props.inheritedProperties.headers)
|
||||||
@@ -671,7 +677,11 @@ const headerValueResults = inspectionService.getResultViewFor(
|
|||||||
|
|
||||||
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
return results.filter((result) => {
|
return results.filter((result) => {
|
||||||
if (result.locations.type === "url" || result.locations.type === "response")
|
if (
|
||||||
|
result.locations.type === "url" ||
|
||||||
|
result.locations.type === "response" ||
|
||||||
|
result.locations.type === "body-content-type-header"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
return result.locations.index === index
|
return result.locations.index === index
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -379,7 +379,11 @@ const parameterValueResults = inspectionService.getResultViewFor(
|
|||||||
|
|
||||||
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
const getInspectorResult = (results: InspectorResult[], index: number) => {
|
||||||
return results.filter((result) => {
|
return results.filter((result) => {
|
||||||
if (result.locations.type === "url" || result.locations.type === "response")
|
if (
|
||||||
|
result.locations.type === "url" ||
|
||||||
|
result.locations.type === "response" ||
|
||||||
|
result.locations.type === "body-content-type-header"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
return result.locations.index === index
|
return result.locations.index === index
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
|
|||||||
effectiveFinalURL: string
|
effectiveFinalURL: string
|
||||||
effectiveFinalHeaders: HoppRESTHeaders
|
effectiveFinalHeaders: HoppRESTHeaders
|
||||||
effectiveFinalParams: HoppRESTParams
|
effectiveFinalParams: HoppRESTParams
|
||||||
effectiveFinalBody: FormData | string | null
|
effectiveFinalBody: FormData | string | null | File
|
||||||
effectiveFinalRequestVariables: { key: string; value: string }[]
|
effectiveFinalRequestVariables: { key: string; value: string }[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,6 +249,32 @@ export const getComputedBodyHeaders = (
|
|||||||
// Body should have a non-null content-type
|
// Body should have a non-null content-type
|
||||||
if (!req.body || req.body.contentType === null) return []
|
if (!req.body || req.body.contentType === null) return []
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.body &&
|
||||||
|
req.body.contentType === "application/octet-stream" &&
|
||||||
|
req.body.body
|
||||||
|
) {
|
||||||
|
const filename = req.body.body.name
|
||||||
|
const fileType = req.body.body.type
|
||||||
|
|
||||||
|
const contentType = fileType ? fileType : "application/octet-stream"
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
key: "content-type",
|
||||||
|
value: contentType,
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
active: true,
|
||||||
|
key: "Content-Disposition",
|
||||||
|
value: `attachment; filename="${filename}"`,
|
||||||
|
description: "",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
active: true,
|
active: true,
|
||||||
@@ -408,6 +434,10 @@ export const resolvesEnvsInBody = (
|
|||||||
): HoppRESTReqBody => {
|
): HoppRESTReqBody => {
|
||||||
if (!body.contentType) return body
|
if (!body.contentType) return body
|
||||||
|
|
||||||
|
if (body.contentType === "application/octet-stream") {
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
if (body.contentType === "multipart/form-data") {
|
if (body.contentType === "multipart/form-data") {
|
||||||
if (!body.body) {
|
if (!body.body) {
|
||||||
return {
|
return {
|
||||||
@@ -448,7 +478,7 @@ function getFinalBodyFromRequest(
|
|||||||
request: HoppRESTRequest,
|
request: HoppRESTRequest,
|
||||||
envVariables: Environment["variables"],
|
envVariables: Environment["variables"],
|
||||||
showKeyIfSecret = false
|
showKeyIfSecret = false
|
||||||
): FormData | string | null {
|
): FormData | Blob | string | null {
|
||||||
if (request.body.contentType === null) return null
|
if (request.body.contentType === null) return null
|
||||||
|
|
||||||
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
if (request.body.contentType === "application/x-www-form-urlencoded") {
|
||||||
@@ -527,6 +557,10 @@ function getFinalBodyFromRequest(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.body.contentType === "application/octet-stream") {
|
||||||
|
return request.body.body
|
||||||
|
}
|
||||||
|
|
||||||
let bodyContent = request.body.body ?? ""
|
let bodyContent = request.body.body ?? ""
|
||||||
|
|
||||||
if (isJSONContentType(request.body.contentType))
|
if (isJSONContentType(request.body.contentType))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ValidContentTypes } from "@hoppscotch/data"
|
import { ValidContentTypes } from "@hoppscotch/data"
|
||||||
|
|
||||||
export type Content = "json" | "xml" | "multipart" | "html" | "plain"
|
export type Content = "json" | "xml" | "multipart" | "html" | "plain" | "binary"
|
||||||
|
|
||||||
export const knownContentTypes: Record<ValidContentTypes, Content> = {
|
export const knownContentTypes: Record<ValidContentTypes, Content> = {
|
||||||
"application/json": "json",
|
"application/json": "json",
|
||||||
@@ -10,6 +10,7 @@ export const knownContentTypes: Record<ValidContentTypes, Content> = {
|
|||||||
"application/xml": "xml",
|
"application/xml": "xml",
|
||||||
"application/x-www-form-urlencoded": "multipart",
|
"application/x-www-form-urlencoded": "multipart",
|
||||||
"multipart/form-data": "multipart",
|
"multipart/form-data": "multipart",
|
||||||
|
"application/octet-stream": "binary",
|
||||||
"text/html": "html",
|
"text/html": "html",
|
||||||
"text/plain": "plain",
|
"text/plain": "plain",
|
||||||
"text/xml": "xml",
|
"text/xml": "xml",
|
||||||
@@ -19,6 +20,7 @@ type ContentTypeTitle =
|
|||||||
| "request.content_type_titles.text"
|
| "request.content_type_titles.text"
|
||||||
| "request.content_type_titles.structured"
|
| "request.content_type_titles.structured"
|
||||||
| "request.content_type_titles.others"
|
| "request.content_type_titles.others"
|
||||||
|
| "request.content_type_titles.binary"
|
||||||
|
|
||||||
type SegmentedContentType = {
|
type SegmentedContentType = {
|
||||||
title: ContentTypeTitle
|
title: ContentTypeTitle
|
||||||
@@ -41,6 +43,10 @@ export const segmentedContentTypes: SegmentedContentType[] = [
|
|||||||
title: "request.content_type_titles.structured",
|
title: "request.content_type_titles.structured",
|
||||||
contentTypes: ["application/x-www-form-urlencoded", "multipart/form-data"],
|
contentTypes: ["application/x-www-form-urlencoded", "multipart/form-data"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "request.content_type_titles.binary",
|
||||||
|
contentTypes: ["application/octet-stream"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: "request.content_type_titles.others",
|
title: "request.content_type_titles.others",
|
||||||
contentTypes: ["text/html", "text/plain"],
|
contentTypes: ["text/html", "text/plain"],
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ import { useService } from "dioc/vue"
|
|||||||
import { InspectionService } from "~/services/inspection"
|
import { InspectionService } from "~/services/inspection"
|
||||||
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
import { HeaderInspectorService } from "~/services/inspection/inspectors/header.inspector"
|
||||||
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
import { EnvironmentInspectorService } from "~/services/inspection/inspectors/environment.inspector"
|
||||||
|
import { InterceptorsInspectorService } from "~/services/inspection/inspectors/interceptors.inspector"
|
||||||
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
import { ResponseInspectorService } from "~/services/inspection/inspectors/response.inspector"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { RESTTabService } from "~/services/tab/rest"
|
import { RESTTabService } from "~/services/tab/rest"
|
||||||
@@ -417,6 +418,8 @@ useService(HeaderInspectorService)
|
|||||||
useService(EnvironmentInspectorService)
|
useService(EnvironmentInspectorService)
|
||||||
useService(ResponseInspectorService)
|
useService(ResponseInspectorService)
|
||||||
useService(AuthorizationInspectorService)
|
useService(AuthorizationInspectorService)
|
||||||
|
useService(InterceptorsInspectorService)
|
||||||
|
|
||||||
for (const inspectorDef of platform.additionalInspectors ?? []) {
|
for (const inspectorDef of platform.additionalInspectors ?? []) {
|
||||||
useService(inspectorDef.service)
|
useService(inspectorDef.service)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,9 @@ export type InspectorLocation =
|
|||||||
| {
|
| {
|
||||||
type: "response"
|
type: "response"
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: "body-content-type-header"
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines info about an inspector result so the UI can render it
|
* Defines info about an inspector result so the UI can render it
|
||||||
@@ -60,7 +63,7 @@ export interface InspectorResult {
|
|||||||
text: string
|
text: string
|
||||||
apply: () => void
|
apply: () => void
|
||||||
}
|
}
|
||||||
doc: {
|
doc?: {
|
||||||
text: string
|
text: string
|
||||||
link: string
|
link: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
position:
|
position:
|
||||||
locations.type === "url" ||
|
locations.type === "url" ||
|
||||||
locations.type === "body" ||
|
locations.type === "body" ||
|
||||||
locations.type === "response"
|
locations.type === "response" ||
|
||||||
|
locations.type === "body-content-type-header"
|
||||||
? "key"
|
? "key"
|
||||||
: locations.position,
|
: locations.position,
|
||||||
index: index,
|
index: index,
|
||||||
@@ -222,7 +223,8 @@ export class EnvironmentInspectorService extends Service implements Inspector {
|
|||||||
position:
|
position:
|
||||||
locations.type === "url" ||
|
locations.type === "url" ||
|
||||||
locations.type === "body" ||
|
locations.type === "body" ||
|
||||||
locations.type === "response"
|
locations.type === "response" ||
|
||||||
|
locations.type === "body-content-type-header"
|
||||||
? "key"
|
? "key"
|
||||||
: locations.position,
|
: locations.position,
|
||||||
index: index,
|
index: index,
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import { Service } from "dioc"
|
||||||
|
import { InspectionService, Inspector, InspectorResult } from ".."
|
||||||
|
import { computed, Ref } from "vue"
|
||||||
|
import {
|
||||||
|
HoppRESTRequest,
|
||||||
|
HoppRESTResponseOriginalRequest,
|
||||||
|
} from "@hoppscotch/data"
|
||||||
|
|
||||||
|
import IconAlertCircle from "~icons/lucide/alert-circle"
|
||||||
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
|
import { getI18n } from "~/modules/i18n"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This inspector is responsible for inspecting the interceptor usage.
|
||||||
|
*
|
||||||
|
* NOTE: Initializing this service registers it as a inspector with the Inspection Service.
|
||||||
|
*/
|
||||||
|
export class InterceptorsInspectorService extends Service implements Inspector {
|
||||||
|
public static readonly ID = "INTERCEPTORS_INSPECTOR_SERVICE"
|
||||||
|
|
||||||
|
inspectorID = "interceptors"
|
||||||
|
|
||||||
|
private t = getI18n()
|
||||||
|
|
||||||
|
private readonly inspection = this.bind(InspectionService)
|
||||||
|
private readonly interceptors = this.bind(InterceptorService)
|
||||||
|
|
||||||
|
onServiceInit() {
|
||||||
|
this.inspection.registerInspector(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
getInspections(
|
||||||
|
req: Readonly<Ref<HoppRESTRequest | HoppRESTResponseOriginalRequest>>
|
||||||
|
) {
|
||||||
|
return computed((): InspectorResult[] => {
|
||||||
|
const isBinaryBody =
|
||||||
|
req.value.body.contentType === "application/octet-stream"
|
||||||
|
|
||||||
|
// TODO: define the supported capabilities in the interceptor
|
||||||
|
const isAgent = this.interceptors.currentInterceptorID.value === "agent"
|
||||||
|
|
||||||
|
if (isBinaryBody && isAgent) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
isApplicable: true,
|
||||||
|
icon: IconAlertCircle,
|
||||||
|
severity: 2,
|
||||||
|
text: {
|
||||||
|
type: "text",
|
||||||
|
text: this.t(
|
||||||
|
"inspections.requestBody.agent_doesnt_support_binary_body"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
locations: {
|
||||||
|
type: "body-content-type-header",
|
||||||
|
},
|
||||||
|
id: "interceptors-inspector-binary-agent-body-content-type-header",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ export const knownContentTypes = {
|
|||||||
"text/xml": "xml",
|
"text/xml": "xml",
|
||||||
"application/x-www-form-urlencoded": "multipart",
|
"application/x-www-form-urlencoded": "multipart",
|
||||||
"multipart/form-data": "multipart",
|
"multipart/form-data": "multipart",
|
||||||
|
"application/octet-stream": "binary",
|
||||||
"text/html": "html",
|
"text/html": "html",
|
||||||
"text/plain": "plain",
|
"text/plain": "plain",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,10 @@ export const HoppRESTReqBody = z.union([
|
|||||||
body: z.array(FormDataKeyValue).catch([]),
|
body: z.array(FormDataKeyValue).catch([]),
|
||||||
showIndividualContentType: z.boolean().optional().catch(false),
|
showIndividualContentType: z.boolean().optional().catch(false),
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
contentType: z.literal("application/octet-stream"),
|
||||||
|
body: z.instanceof(File).nullable().catch(null),
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
contentType: z.union([
|
contentType: z.union([
|
||||||
z.literal("application/json"),
|
z.literal("application/json"),
|
||||||
@@ -50,6 +54,7 @@ export const HoppRESTReqBody = z.union([
|
|||||||
z.literal("application/xml"),
|
z.literal("application/xml"),
|
||||||
z.literal("text/xml"),
|
z.literal("text/xml"),
|
||||||
z.literal("application/x-www-form-urlencoded"),
|
z.literal("application/x-www-form-urlencoded"),
|
||||||
|
z.literal("binary"),
|
||||||
z.literal("text/html"),
|
z.literal("text/html"),
|
||||||
z.literal("text/plain"),
|
z.literal("text/plain"),
|
||||||
]),
|
]),
|
||||||
|
|||||||
Reference in New Issue
Block a user