chore(deps): bump

This commit is contained in:
liyasthomas
2022-01-03 11:34:42 +05:30
42 changed files with 1236 additions and 2425 deletions

View File

@@ -131,7 +131,7 @@
v-tippy="{ theme: 'tooltip' }"
:title="t('action.clear_all')"
svg="trash-2"
@click.native="clearHeaders()"
@click.native="clearContent()"
/>
<ButtonSecondary
v-tippy="{ theme: 'tooltip' }"
@@ -145,14 +145,14 @@
:title="t('add.new')"
svg="plus"
:disabled="bulkMode"
@click.native="addRequestHeader"
@click.native="addHeader"
/>
</div>
</div>
<div v-if="bulkMode" ref="bulkEditor"></div>
<div v-else>
<div
v-for="(header, index) in headers"
v-for="(header, index) in workingHeaders"
:key="`header-${String(index)}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight"
>
@@ -172,7 +172,7 @@
"
class="flex-1 !flex"
@input="
updateRequestHeader(index, {
updateHeader(index, {
key: $event,
value: header.value,
active: header.active,
@@ -186,7 +186,7 @@
:value="header.value"
autofocus
@change="
updateRequestHeader(index, {
updateHeader(index, {
key: header.key,
value: $event.target.value,
active: header.active,
@@ -212,7 +212,7 @@
"
color="green"
@click.native="
updateRequestHeader(index, {
updateHeader(index, {
key: header.key,
value: header.value,
active: !header.active,
@@ -226,12 +226,12 @@
:title="t('action.remove')"
svg="trash"
color="red"
@click.native="removeRequestHeader(index)"
@click.native="deleteHeader(index)"
/>
</span>
</div>
<div
v-if="headers.length === 0"
v-if="workingHeaders.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
@@ -248,7 +248,7 @@
filled
svg="plus"
class="mb-4"
@click.native="addRequestHeader"
@click.native="addHeader"
/>
</div>
</div>
@@ -263,16 +263,11 @@
</template>
<script setup lang="ts">
import {
computed,
onMounted,
reactive,
ref,
watch,
} from "@nuxtjs/composition-api"
import { Ref, computed, reactive, ref, watch } from "@nuxtjs/composition-api"
import clone from "lodash/clone"
import * as gql from "graphql"
import { GQLHeader, makeGQLRequest } from "@hoppscotch/data"
import isEqual from "lodash/isEqual"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import {
useNuxt,
@@ -282,18 +277,15 @@ import {
useToast,
} from "~/helpers/utils/composables"
import {
addGQLHeader,
gqlHeaders$,
gqlQuery$,
gqlResponse$,
gqlURL$,
gqlVariables$,
removeGQLHeader,
setGQLHeaders,
setGQLQuery,
setGQLResponse,
setGQLVariables,
updateGQLHeader,
} from "~/newstore/GQLSession"
import { commonHeaders } from "~/helpers/headers"
import { GQLConnection } from "~/helpers/GQLConnection"
@@ -314,33 +306,18 @@ const props = defineProps<{
}>()
const toast = useToast()
const nuxt = useNuxt()
const bulkMode = ref(false)
const bulkHeaders = ref("")
watch(bulkHeaders, () => {
try {
const transformation = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("#"),
}))
setGQLHeaders(transformation as GQLHeader[])
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const url = useReadonlyStream(gqlURL$, "")
const gqlQueryString = useStream(gqlQuery$, "", setGQLQuery)
const variableString = useStream(gqlVariables$, "", setGQLVariables)
const headers = useStream(gqlHeaders$, [], setGQLHeaders)
const bulkMode = ref(false)
const bulkHeaders = ref("")
const bulkEditor = ref<any | null>(null)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
useCodemirror(bulkEditor, bulkHeaders, {
extendedEditorConfig: {
mode: "text/x-yaml",
@@ -351,6 +328,166 @@ useCodemirror(bulkEditor, bulkHeaders, {
environmentHighlights: false,
})
// The functional headers list (the headers actually in the system)
const headers = useStream(gqlHeaders$, [], setGQLHeaders) as Ref<GQLHeader[]>
// The UI representation of the headers list (has the empty end header)
const workingHeaders = ref<GQLHeader[]>([
{
key: "",
value: "",
active: true,
},
])
// Rule: Working Headers always have one empty header or the last element is always an empty header
watch(workingHeaders, (headersList) => {
if (
headersList.length > 0 &&
headersList[headersList.length - 1].key !== ""
) {
workingHeaders.value.push({
key: "",
value: "",
active: true,
})
}
})
// Sync logic between headers and working headers
watch(
headers,
(newHeadersList) => {
// Sync should overwrite working headers
const filteredWorkingHeaders = workingHeaders.value.filter(
(e) => e.key !== ""
)
if (!isEqual(newHeadersList, filteredWorkingHeaders)) {
workingHeaders.value = newHeadersList
}
},
{ immediate: true }
)
watch(workingHeaders, (newWorkingHeaders) => {
const fixedHeaders = newWorkingHeaders.filter((e) => e.key !== "")
if (!isEqual(headers.value, fixedHeaders)) {
headers.value = fixedHeaders
}
})
// Bulk Editor Syncing with Working Headers
watch(bulkHeaders, () => {
try {
const transformation = bulkHeaders.value
.split("\n")
.filter((x) => x.trim().length > 0 && x.includes(":"))
.map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
const filteredHeaders = workingHeaders.value.filter((x) => x.key !== "")
if (!isEqual(filteredHeaders, transformation)) {
workingHeaders.value = transformation
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
watch(workingHeaders, (newHeadersList) => {
// If we are in bulk mode, don't apply direct changes
if (bulkMode.value) return
try {
const currentBulkHeaders = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
const filteredHeaders = newHeadersList.filter((x) => x.key !== "")
if (!isEqual(currentBulkHeaders, filteredHeaders)) {
bulkHeaders.value = filteredHeaders
.map((header) => {
return `${header.active ? "" : "#"}${header.key}: ${header.value}`
})
.join("\n")
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const addHeader = () => {
workingHeaders.value.push({
key: "",
value: "",
active: true,
})
}
const updateHeader = (index: number, header: GQLHeader) => {
workingHeaders.value = workingHeaders.value.map((h, i) =>
i === index ? header : h
)
}
const deleteHeader = (index: number) => {
const headersBeforeDeletion = clone(workingHeaders.value)
if (
!(
headersBeforeDeletion.length > 0 &&
index === headersBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
workingHeaders.value = headersBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingHeaders.value.splice(index, 1)
}
const clearContent = () => {
// set headers list to the initial state
workingHeaders.value = [
{
key: "",
value: "",
active: true,
},
]
bulkHeaders.value = ""
}
const activeGQLHeadersCount = computed(
() =>
headers.value.filter((x) => x.active && (x.key !== "" || x.value !== ""))
@@ -395,42 +532,6 @@ const prettifyVariablesIcon = ref("wand")
const showSaveRequestModal = ref(false)
watch(
headers,
() => {
if (!bulkMode.value)
if (
(headers.value[headers.value.length - 1]?.key !== "" ||
headers.value[headers.value.length - 1]?.value !== "") &&
headers.value.length
)
addRequestHeader()
},
{ deep: true }
)
const editBulkHeadersLine = (index: number, item?: GQLHeader | null) => {
bulkHeaders.value = headers.value
.reduce((all, header, pIndex) => {
const current =
index === pIndex && item != null
? `${item.active ? "" : "#"}${item.key}: ${item.value}`
: `${header.active ? "" : "#"}${header.key}: ${header.value}`
return [...all, current]
}, [] as string[])
.join("\n")
}
const clearBulkEditor = () => {
bulkHeaders.value = ""
}
onMounted(() => {
if (!headers.value?.length) {
addRequestHeader()
}
})
const copyQuery = () => {
copyToClipboard(gqlQueryString.value)
copyQueryIcon.value = "check"
@@ -535,50 +636,6 @@ const prettifyVariableString = () => {
setTimeout(() => (prettifyVariablesIcon.value = "wand"), 1000)
}
const addRequestHeader = () => {
const empty = { key: "", value: "", active: true }
const index = headers.value.length
addGQLHeader(empty)
editBulkHeadersLine(index, empty)
}
const updateRequestHeader = (
index: number,
item: { key: string; value: string; active: boolean }
) => {
updateGQLHeader(index, item)
editBulkHeadersLine(index, item)
}
const removeRequestHeader = (index: number) => {
const headersBeforeDeletion = headers.value
removeGQLHeader(index)
editBulkHeadersLine(index, null)
const deletedItem = headersBeforeDeletion[index]
if (deletedItem.key || deletedItem.value) {
toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
setGQLHeaders(headersBeforeDeletion as GQLHeader[])
editBulkHeadersLine(index, deletedItem)
toastObject.goAway(0)
},
},
],
})
}
}
const clearHeaders = () => {
headers.value = []
clearBulkEditor()
}
const clearGQLQuery = () => {
gqlQueryString.value = ""
}

View File

@@ -54,13 +54,12 @@
/>
<div v-if="param.isFile" class="file-chips-container hide-scrollbar">
<div class="space-x-2 file-chips-wrapper">
<SmartDeletableChip
<SmartFileChip
v-for="(file, fileIndex) in param.value"
:key="`param-${index}-file-${fileIndex}`"
@chip-delete="chipDelete(index, fileIndex)"
>
{{ file.name }}
</SmartDeletableChip>
</SmartFileChip>
</div>
</div>
<span v-else class="flex flex-1">
@@ -85,21 +84,17 @@
/>
</span>
<span>
<label for="attachment" class="p-0">
<ButtonSecondary
class="w-full"
svg="paperclip"
@click.native="$refs.attachment[index].click()"
<label :for="`attachment${index}`" class="p-0">
<input
:id="`attachment${index}`"
:ref="`attachment${index}`"
:name="`attachment${index}`"
type="file"
multiple
class="p-1 transition cursor-pointer file:transition file:cursor-pointer text-secondaryLight hover:text-secondaryDark file:mr-2 file:py-1 file:px-4 file:rounded file:border-0 file:text-tiny text-tiny file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
@change="setRequestAttachment(index, param, $event)"
/>
</label>
<input
ref="attachment"
class="input"
name="attachment"
type="file"
multiple
@change="setRequestAttachment(index, param, $event)"
/>
</span>
<span>
<ButtonSecondary
@@ -163,8 +158,8 @@
</div>
</template>
<script lang="ts">
import { defineComponent, onMounted, Ref, watch } from "@nuxtjs/composition-api"
<script setup lang="ts">
import { onMounted, Ref, watch } from "@nuxtjs/composition-api"
import { FormDataKeyValue } from "@hoppscotch/data"
import { pluckRef } from "~/helpers/utils/composables"
import {
@@ -175,87 +170,66 @@ import {
useRESTRequestBody,
} from "~/newstore/RESTSession"
export default defineComponent({
setup() {
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
FormDataKeyValue[]
>
const bodyParams = pluckRef<any, any>(useRESTRequestBody(), "body") as Ref<
FormDataKeyValue[]
>
const addBodyParam = () => {
addFormDataEntry({ key: "", value: "", active: true, isFile: false })
}
const addBodyParam = () => {
addFormDataEntry({ key: "", value: "", active: true, isFile: false })
}
const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
updateFormDataEntry(index, entry)
}
const updateBodyParam = (index: number, entry: FormDataKeyValue) => {
updateFormDataEntry(index, entry)
}
const deleteBodyParam = (index: number) => {
deleteFormDataEntry(index)
}
const deleteBodyParam = (index: number) => {
deleteFormDataEntry(index)
}
const clearContent = () => {
deleteAllFormDataEntries()
}
const clearContent = () => {
deleteAllFormDataEntries()
}
const chipDelete = (paramIndex: number, fileIndex: number) => {
const entry = bodyParams.value[paramIndex]
if (entry.isFile) {
entry.value.splice(fileIndex, 1)
if (entry.value.length === 0) {
updateFormDataEntry(paramIndex, {
...entry,
isFile: false,
value: "",
})
return
}
}
updateFormDataEntry(paramIndex, entry)
}
const setRequestAttachment = (
index: number,
entry: FormDataKeyValue,
event: InputEvent
) => {
const fileEntry: FormDataKeyValue = {
...entry,
isFile: true,
value: Array.from((event.target as HTMLInputElement).files!),
}
updateFormDataEntry(index, fileEntry)
}
watch(
bodyParams,
() => {
if (
bodyParams.value.length > 0 &&
(bodyParams.value[bodyParams.value.length - 1].key !== "" ||
bodyParams.value[bodyParams.value.length - 1].value !== "")
)
addBodyParam()
},
{ deep: true }
)
onMounted(() => {
if (!bodyParams.value?.length) {
addBodyParam()
}
const setRequestAttachment = (
index: number,
entry: FormDataKeyValue,
event: InputEvent
) => {
// check if file exists or not
if ((event.target as HTMLInputElement).files?.length === 0) {
updateFormDataEntry(index, {
...entry,
isFile: false,
value: "",
})
return
}
return {
bodyParams,
addBodyParam,
updateBodyParam,
deleteBodyParam,
clearContent,
setRequestAttachment,
chipDelete,
}
const fileEntry: FormDataKeyValue = {
...entry,
isFile: true,
value: Array.from((event.target as HTMLInputElement).files!),
}
updateFormDataEntry(index, fileEntry)
}
watch(
bodyParams,
() => {
if (
bodyParams.value.length > 0 &&
(bodyParams.value[bodyParams.value.length - 1].key !== "" ||
bodyParams.value[bodyParams.value.length - 1].value !== "")
)
addBodyParam()
},
{ deep: true }
)
onMounted(() => {
if (!bodyParams.value?.length) {
addBodyParam()
}
})
</script>
@@ -268,8 +242,7 @@ export default defineComponent({
.file-chips-wrapper {
@apply flex;
@apply px-4;
@apply py-1;
@apply p-1;
@apply w-0;
}
}

View File

@@ -13,25 +13,40 @@
<template #trigger>
<span class="select-wrapper">
<ButtonSecondary
:label="codegens.find((x) => x.id === codegenType).name"
:label="
CodegenDefinitions.find((x) => x.name === codegenType).caption
"
outline
class="flex-1 pr-8"
/>
</span>
</template>
<SmartItem
v-for="(gen, index) in codegens"
:key="`gen-${index}`"
:label="gen.name"
:info-icon="gen.id === codegenType ? 'done' : ''"
:active-info-icon="gen.id === codegenType"
@click.native="
() => {
codegenType = gen.id
options.tippy().hide()
}
"
/>
<div class="flex flex-col space-y-2">
<div class="sticky top-0">
<input
v-model="searchQuery"
type="search"
autocomplete="off"
class="flex w-full p-4 py-2 !bg-popover input"
:placeholder="`${t('action.search')}`"
/>
</div>
<div class="flex flex-col">
<SmartItem
v-for="codegen in filteredCodegenDefinitions"
:key="codegen.name"
:label="codegen.caption"
:info-icon="codegen.name === codegenType ? 'done' : ''"
:active-info-icon="codegen.name === codegenType"
@click.native="
() => {
codegenType = codegen.name
options.tippy().hide()
}
"
/>
</div>
</div>
</tippy>
<div class="flex justify-between flex-1">
<label for="generatedCode" class="p-4">
@@ -39,7 +54,13 @@
</label>
</div>
<div
v-if="codegenType"
v-if="errorState"
class="bg-primaryLight rounded font-mono w-full py-2 px-4 text-red-400 overflow-auto whitespace-normal"
>
{{ t("error.something_went_wrong") }}
</div>
<div
v-else-if="codegenType"
ref="generatedCode"
class="border rounded border-dividerLight"
></div>
@@ -63,13 +84,22 @@
<script setup lang="ts">
import { computed, ref, watch } from "@nuxtjs/composition-api"
import { codegens, generateCodegenContext } from "~/helpers/codegen/codegen"
import * as O from "fp-ts/Option"
import { useCodemirror } from "~/helpers/editor/codemirror"
import { copyToClipboard } from "~/helpers/utils/clipboard"
import { getEffectiveRESTRequest } from "~/helpers/utils/EffectiveURL"
import { getCurrentEnvironment } from "~/newstore/environments"
import {
getEffectiveRESTRequest,
resolvesEnvsInBody,
} from "~/helpers/utils/EffectiveURL"
import { Environment, getAggregateEnvs } from "~/newstore/environments"
import { getRESTRequest } from "~/newstore/RESTSession"
import { useI18n, useToast } from "~/helpers/utils/composables"
import {
CodegenDefinitions,
CodegenName,
generateCode,
} from "~/helpers/new-codegen"
import { makeRESTRequest } from "~/../hoppscotch-data/dist"
const t = useI18n()
@@ -86,18 +116,44 @@ const toast = useToast()
const options = ref<any | null>(null)
const request = ref(getRESTRequest())
const codegenType = ref("curl")
const codegenType = ref<CodegenName>("shell-curl")
const copyIcon = ref("copy")
const errorState = ref(false)
const requestCode = computed(() => {
const effectiveRequest = getEffectiveRESTRequest(
request.value as any,
getCurrentEnvironment()
const aggregateEnvs = getAggregateEnvs()
const env: Environment = {
name: "Env",
variables: aggregateEnvs,
}
const effectiveRequest = getEffectiveRESTRequest(request.value, env)
if (!props.show) return ""
const result = generateCode(
codegenType.value,
makeRESTRequest({
...effectiveRequest,
body: resolvesEnvsInBody(effectiveRequest.body, env),
headers: effectiveRequest.effectiveFinalHeaders.map((header) => ({
...header,
active: true,
})),
params: effectiveRequest.effectiveFinalParams.map((param) => ({
...param,
active: true,
})),
endpoint: effectiveRequest.effectiveFinalURL,
})
)
return codegens
.find((x) => x.id === codegenType.value)!
.generator(generateCodegenContext(effectiveRequest))
if (O.isSome(result)) {
errorState.value = false
return result.value
} else {
errorState.value = true
return ""
}
})
const generatedCode = ref<any | null>(null)
@@ -129,4 +185,12 @@ const copyRequestCode = () => {
toast.success(`${t("state.copied_to_clipboard")}`)
setTimeout(() => (copyIcon.value = "copy"), 1000)
}
const searchQuery = ref("")
const filteredCodegenDefinitions = computed(() => {
return CodegenDefinitions.filter((obj) =>
Object.values(obj).some((val) => val.includes(searchQuery.value))
)
})
</script>

View File

@@ -39,7 +39,7 @@
<div v-if="bulkMode" ref="bulkEditor"></div>
<div v-else>
<div
v-for="(header, index) in headers$"
v-for="(header, index) in workingHeaders"
:key="`header-${index}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight"
>
@@ -106,9 +106,7 @@
updateHeader(index, {
key: header.key,
value: header.value,
active: header.hasOwnProperty('active')
? !header.active
: false,
active: !header.active,
})
"
/>
@@ -124,8 +122,8 @@
</span>
</div>
<div
v-if="headers$.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
v-if="workingHeaders.length === 0"
class="flex flex-col text-secondaryLight p-4 items-center justify-center"
>
<img
:src="`/images/states/${$colorMode.value}/add_category.svg`"
@@ -149,32 +147,24 @@
</template>
<script setup lang="ts">
import { onBeforeUpdate, ref, watch } from "@nuxtjs/composition-api"
import { Ref, ref, watch } from "@nuxtjs/composition-api"
import isEqual from "lodash/isEqual"
import clone from "lodash/clone"
import { HoppRESTHeader } from "@hoppscotch/data"
import { useCodemirror } from "~/helpers/editor/codemirror"
import {
addRESTHeader,
deleteAllRESTHeaders,
deleteRESTHeader,
restHeaders$,
setRESTHeaders,
updateRESTHeader,
} from "~/newstore/RESTSession"
import { restHeaders$, setRESTHeaders } from "~/newstore/RESTSession"
import { commonHeaders } from "~/helpers/headers"
import {
useReadonlyStream,
useI18n,
useToast,
} from "~/helpers/utils/composables"
import { useI18n, useStream, useToast } from "~/helpers/utils/composables"
const t = useI18n()
const toast = useToast()
const bulkMode = ref(false)
const bulkHeaders = ref("")
const bulkEditor = ref<any | null>(null)
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
useCodemirror(bulkEditor, bulkHeaders, {
extendedEditorConfig: {
mode: "text/x-yaml",
@@ -185,92 +175,165 @@ useCodemirror(bulkEditor, bulkHeaders, {
environmentHighlights: true,
})
// The functional headers list (the headers actually in the system)
const headers = useStream(restHeaders$, [], setRESTHeaders) as Ref<
HoppRESTHeader[]
>
// The UI representation of the headers list (has the empty end header)
const workingHeaders = ref<HoppRESTHeader[]>([
{
key: "",
value: "",
active: true,
},
])
// Rule: Working Headers always have one empty header or the last element is always an empty header
watch(workingHeaders, (headersList) => {
if (
headersList.length > 0 &&
headersList[headersList.length - 1].key !== ""
) {
workingHeaders.value.push({
key: "",
value: "",
active: true,
})
}
})
// Sync logic between headers and working headers
watch(
headers,
(newHeadersList) => {
// Sync should overwrite working headers
const filteredWorkingHeaders = workingHeaders.value.filter(
(e) => e.key !== ""
)
if (!isEqual(newHeadersList, filteredWorkingHeaders)) {
workingHeaders.value = newHeadersList
}
},
{ immediate: true }
)
watch(workingHeaders, (newWorkingHeaders) => {
const fixedHeaders = newWorkingHeaders.filter((e) => e.key !== "")
if (!isEqual(headers.value, fixedHeaders)) {
headers.value = fixedHeaders
}
})
// Bulk Editor Syncing with Working Headers
watch(bulkHeaders, () => {
try {
const transformation = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("#"),
}))
setRESTHeaders(transformation as HoppRESTHeader[])
const transformation = bulkHeaders.value
.split("\n")
.filter((x) => x.trim().length > 0 && x.includes(":"))
.map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
const filteredHeaders = workingHeaders.value.filter((x) => x.key !== "")
if (!isEqual(filteredHeaders, transformation)) {
workingHeaders.value = transformation
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const headers$ = useReadonlyStream(restHeaders$, [])
watch(workingHeaders, (newHeadersList) => {
// If we are in bulk mode, don't apply direct changes
if (bulkMode.value) return
watch(
headers$,
(newValue) => {
if (!bulkMode.value)
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
addHeader()
},
{ deep: true }
)
try {
const currentBulkHeaders = bulkHeaders.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
onBeforeUpdate(() => editBulkHeadersLine(-1, null))
const filteredHeaders = newHeadersList.filter((x) => x.key !== "")
const editBulkHeadersLine = (index: number, item?: HoppRESTHeader | null) => {
bulkHeaders.value = headers$.value
.reduce((all, header, pIndex) => {
const current =
index === pIndex && item != null
? `${item.active ? "" : "#"}${item.key}: ${item.value}`
: `${header.active ? "" : "#"}${header.key}: ${header.value}`
return [...all, current]
}, [])
.join("\n")
}
const clearBulkEditor = () => {
bulkHeaders.value = ""
}
if (!isEqual(currentBulkHeaders, filteredHeaders)) {
bulkHeaders.value = filteredHeaders
.map((header) => {
return `${header.active ? "" : "#"}${header.key}: ${header.value}`
})
.join("\n")
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const addHeader = () => {
const empty = { key: "", value: "", active: true }
const index = headers$.value.length
addRESTHeader(empty)
editBulkHeadersLine(index, empty)
workingHeaders.value.push({
key: "",
value: "",
active: true,
})
}
const updateHeader = (index: number, item: HoppRESTHeader) => {
updateRESTHeader(index, item)
editBulkHeadersLine(index, item)
const updateHeader = (index: number, header: HoppRESTHeader) => {
workingHeaders.value = workingHeaders.value.map((h, i) =>
i === index ? header : h
)
}
const deleteHeader = (index: number) => {
const headersBeforeDeletion = headers$.value
const headersBeforeDeletion = clone(workingHeaders.value)
deleteRESTHeader(index)
editBulkHeadersLine(index, null)
if (
!(
headersBeforeDeletion.length > 0 &&
index === headersBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
const deletedItem = headersBeforeDeletion[index]
if (deletedItem.key || deletedItem.value) {
toast.success(`${t("state.deleted")}`, {
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
setRESTHeaders(headersBeforeDeletion as HoppRESTHeader[])
editBulkHeadersLine(index, deletedItem)
workingHeaders.value = headersBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingHeaders.value.splice(index, 1)
}
const clearContent = () => {
deleteAllRESTHeaders()
clearBulkEditor()
// set headers list to the initial state
workingHeaders.value = [
{
key: "",
value: "",
active: true,
},
]
bulkHeaders.value = ""
}
</script>

View File

@@ -39,7 +39,7 @@
<div v-if="bulkMode" ref="bulkEditor"></div>
<div v-else>
<div
v-for="(param, index) in params$"
v-for="(param, index) in workingParams"
:key="`param-${index}`"
class="flex border-b divide-x divide-dividerLight border-dividerLight"
>
@@ -117,7 +117,7 @@
</span>
</div>
<div
v-if="params$.length === 0"
v-if="workingParams.length === 0"
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
>
<img
@@ -142,22 +142,13 @@
</template>
<script setup lang="ts">
import { ref, watch, onBeforeUpdate } from "@nuxtjs/composition-api"
import { ref, watch } from "@nuxtjs/composition-api"
import { HoppRESTParam } from "@hoppscotch/data"
import isEqual from "lodash/isEqual"
import clone from "lodash/clone"
import { useCodemirror } from "~/helpers/editor/codemirror"
import {
useReadonlyStream,
useI18n,
useToast,
} from "~/helpers/utils/composables"
import {
restParams$,
addRESTParam,
updateRESTParam,
deleteRESTParam,
deleteAllRESTParams,
setRESTParams,
} from "~/newstore/RESTSession"
import { useI18n, useToast, useStream } from "~/helpers/utils/composables"
import { restParams$, setRESTParams } from "~/newstore/RESTSession"
const t = useI18n()
@@ -166,19 +157,7 @@ const toast = useToast()
const bulkMode = ref(false)
const bulkParams = ref("")
watch(bulkParams, () => {
try {
const transformation = bulkParams.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trim().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trim(),
active: !item.trim().startsWith("#"),
}))
setRESTParams(transformation as HoppRESTParam[])
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
const bulkEditor = ref<any | null>(null)
@@ -192,78 +171,160 @@ useCodemirror(bulkEditor, bulkParams, {
environmentHighlights: true,
})
const params$ = useReadonlyStream(restParams$, [])
// The functional parameters list (the parameters actually applied to the session)
const params = useStream(restParams$, [], setRESTParams)
watch(
params$,
(newValue) => {
if (!bulkMode.value)
if (
(newValue[newValue.length - 1]?.key !== "" ||
newValue[newValue.length - 1]?.value !== "") &&
newValue.length
)
addParam()
// The UI representation of the parameters list (has the empty end param)
const workingParams = ref<HoppRESTParam[]>([
{
key: "",
value: "",
active: true,
},
{ deep: true }
])
// Rule: Working Params always have last element is always an empty param
watch(workingParams, (paramsList) => {
if (paramsList.length > 0 && paramsList[paramsList.length - 1].key !== "") {
workingParams.value.push({
key: "",
value: "",
active: true,
})
}
})
// Sync logic between params and working params
watch(
params,
(newParamsList) => {
// Sync should overwrite working params
const filteredWorkingParams = workingParams.value.filter(
(e) => e.key !== ""
)
if (!isEqual(newParamsList, filteredWorkingParams)) {
workingParams.value = newParamsList
}
},
{ immediate: true }
)
onBeforeUpdate(() => editBulkParamsLine(-1, null))
watch(workingParams, (newWorkingParams) => {
const fixedParams = newWorkingParams.filter((e) => e.key !== "")
if (!isEqual(params.value, fixedParams)) {
params.value = fixedParams
}
})
const editBulkParamsLine = (index: number, item?: HoppRESTParam | null) => {
bulkParams.value = params$.value
.reduce((all, param, pIndex) => {
const current =
index === pIndex && item != null
? `${item.active ? "" : "#"}${item.key}: ${item.value}`
: `${param.active ? "" : "#"}${param.key}: ${param.value}`
return [...all, current]
}, [])
.join("\n")
}
// Bulk Editor Syncing with Working Params
watch(bulkParams, () => {
try {
const transformation = bulkParams.value
.split("\n")
.filter((x) => x.trim().length > 0 && x.includes(":"))
.map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
const clearBulkEditor = () => {
bulkParams.value = ""
}
const filteredParams = workingParams.value.filter((x) => x.key !== "")
if (!isEqual(filteredParams, transformation)) {
workingParams.value = transformation
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
watch(workingParams, (newParamsList) => {
// If we are in bulk mode, don't apply direct changes
if (bulkMode.value) return
try {
const currentBulkParams = bulkParams.value.split("\n").map((item) => ({
key: item.substring(0, item.indexOf(":")).trimLeft().replace(/^#/, ""),
value: item.substring(item.indexOf(":") + 1).trimLeft(),
active: !item.trim().startsWith("#"),
}))
const filteredParams = newParamsList.filter((x) => x.key !== "")
if (!isEqual(currentBulkParams, filteredParams)) {
bulkParams.value = filteredParams
.map((param) => {
return `${param.active ? "" : "#"}${param.key}: ${param.value}`
})
.join("\n")
}
} catch (e) {
toast.error(`${t("error.something_went_wrong")}`)
console.error(e)
}
})
const addParam = () => {
const empty = { key: "", value: "", active: true }
const index = params$.value.length
addRESTParam(empty)
editBulkParamsLine(index, empty)
workingParams.value.push({
key: "",
value: "",
active: true,
})
}
const updateParam = (index: number, item: HoppRESTParam) => {
updateRESTParam(index, item)
editBulkParamsLine(index, item)
const updateParam = (index: number, param: HoppRESTParam) => {
workingParams.value = workingParams.value.map((h, i) =>
i === index ? param : h
)
}
const deleteParam = (index: number) => {
const parametersBeforeDeletion = params$.value
const paramsBeforeDeletion = clone(workingParams.value)
deleteRESTParam(index)
editBulkParamsLine(index, null)
if (
!(
paramsBeforeDeletion.length > 0 &&
index === paramsBeforeDeletion.length - 1
)
) {
if (deletionToast.value) {
deletionToast.value.goAway(0)
deletionToast.value = null
}
const deletedItem = parametersBeforeDeletion[index]
if (deletedItem.key || deletedItem.value) {
toast.success(`${t("state.deleted")}`, {
deletionToast.value = toast.success(`${t("state.deleted")}`, {
action: [
{
text: `${t("action.undo")}`,
onClick: (_, toastObject) => {
setRESTParams(parametersBeforeDeletion as HoppRESTParam[])
editBulkParamsLine(index, deletedItem)
workingParams.value = paramsBeforeDeletion
toastObject.goAway(0)
deletionToast.value = null
},
},
],
onComplete: () => {
deletionToast.value = null
},
})
}
workingParams.value.splice(index, 1)
}
const clearContent = () => {
deleteAllRESTParams()
clearBulkEditor()
// set params list to the initial state
workingParams.value = [
{
key: "",
value: "",
active: true,
},
]
bulkParams.value = ""
}
</script>

View File

@@ -120,9 +120,11 @@ export default defineComponent({
},
handleChange() {
this.debouncedHandler = debounce(function () {
if (this.internalValue !== this.$refs.editor.textContent) {
this.internalValue = this.$refs.editor.textContent
this.processHighlights()
if (this.$refs.editor) {
if (this.internalValue !== this.$refs.editor.textContent) {
this.internalValue = this.$refs.editor.textContent
this.processHighlights()
}
}
}, 5)
this.debouncedHandler()

View File

@@ -1,12 +1,7 @@
<template>
<span class="chip">
<i class="opacity-75 material-icons">attachment</i>
<span class="px-2 truncate max-w-64"><slot></slot></span>
<ButtonSecondary
class="rounded close-button"
svg="x"
@click.native="$emit('chip-delete')"
/>
<span class="px-2 truncate max-w-32"><slot></slot></span>
</span>
</template>
@@ -18,11 +13,6 @@
@apply rounded;
@apply pl-2;
@apply pr-0.5;
@apply bg-transparent;
@apply border border-divider;
}
.close-button {
@apply p-0.5;
@apply bg-primaryDark;
}
</style>

View File

@@ -1,7 +1,6 @@
import clone from "lodash/clone"
import { FormDataKeyValue, HoppRESTRequest } from "@hoppscotch/data"
import { isJSONContentType } from "./utils/contenttypes"
import { defaultRESTRequest } from "~/newstore/RESTSession"
import { getDefaultRESTRequest } from "~/newstore/RESTSession"
/**
* Handles translations for all the hopp.io REST Shareable URL params
@@ -14,7 +13,7 @@ export function translateExtURLParams(
}
function parseV0ExtURL(urlParams: Record<string, any>): HoppRESTRequest {
const resolvedReq = clone(defaultRESTRequest)
const resolvedReq = getDefaultRESTRequest()
if (urlParams.method && typeof urlParams.method === "string") {
resolvedReq.method = urlParams.method
@@ -91,7 +90,7 @@ function parseV0ExtURL(urlParams: Record<string, any>): HoppRESTRequest {
}
function parseV1ExtURL(urlParams: Record<string, any>): HoppRESTRequest {
const resolvedReq = clone(defaultRESTRequest)
const resolvedReq = getDefaultRESTRequest()
if (urlParams.headers && typeof urlParams.headers === "string") {
resolvedReq.headers = JSON.parse(urlParams.headers)

View File

@@ -1,91 +0,0 @@
import { codegens } from "../codegen"
const TEST_URL = "https://httpbin.org"
const TEST_PATH_NAME = "/path/to"
const TEST_QUERY_STRING = "?a=b"
const TEST_HTTP_USER = "mockUser"
const TEST_HTTP_PASSWORD = "mockPassword"
const TEST_BEARER_TOKEN = "abcdefghijklmn"
const TEST_RAW_REQUEST_BODY = "foo=bar&baz=qux"
const TEST_RAW_PARAMS_JSON = '{"foo": "bar", "baz": "qux"}'
const TEST_RAW_PARAMS_XML = `<?xml version='1.0' encoding='utf-8'?>
<xml>
<element foo="bar"></element>
</xml>`
const TEST_HEADERS = [
{ key: "h1", value: "h1v" },
{ key: "h2", value: "h2v" },
]
codegens.forEach((codegen) => {
describe(`generate request for ${codegen.name}`, () => {
const testCases = [
[
"generate GET request",
{
url: TEST_URL,
pathName: TEST_PATH_NAME,
queryString: TEST_QUERY_STRING,
auth: "Basic Auth",
httpUser: TEST_HTTP_USER,
httpPassword: TEST_HTTP_PASSWORD,
method: "GET",
rawInput: false,
rawParams: "",
rawRequestBody: "",
headers: TEST_HEADERS,
},
],
[
"generate POST request for JSON",
{
url: TEST_URL,
pathName: TEST_PATH_NAME,
queryString: TEST_QUERY_STRING,
auth: "Bearer Token",
bearerToken: TEST_BEARER_TOKEN,
method: "POST",
rawInput: true,
rawParams: TEST_RAW_PARAMS_JSON,
rawRequestBody: "",
contentType: "application/json",
headers: TEST_HEADERS,
},
],
[
"generate POST request for XML",
{
url: TEST_URL,
pathName: TEST_PATH_NAME,
queryString: TEST_QUERY_STRING,
auth: "OAuth 2.0",
bearerToken: TEST_BEARER_TOKEN,
method: "POST",
rawInput: true,
rawParams: TEST_RAW_PARAMS_XML,
rawRequestBody: "",
contentType: "application/xml",
headers: TEST_HEADERS,
},
],
[
"generate PUT request for www-form-urlencoded",
{
url: TEST_URL,
pathName: TEST_PATH_NAME,
queryString: TEST_QUERY_STRING,
method: "PUT",
rawInput: false,
rawRequestBody: TEST_RAW_REQUEST_BODY,
contentType: "application/x-www-form-urlencoded",
headers: [],
},
],
]
test.each(testCases)("%s", (_, context) => {
const result = codegen.generator(context)
expect(result).toMatchSnapshot()
})
})
})

View File

@@ -1,209 +0,0 @@
import {
FormDataKeyValue,
HoppRESTHeader,
HoppRESTParam,
} from "@hoppscotch/data"
import { EffectiveHoppRESTRequest } from "../utils/EffectiveURL"
import { CLibcurlCodegen } from "./generators/c-libcurl"
import { CsRestsharpCodegen } from "./generators/cs-restsharp"
import { CurlCodegen } from "./generators/curl"
import { GoNativeCodegen } from "./generators/go-native"
import { JavaOkhttpCodegen } from "./generators/java-okhttp"
import { JavaUnirestCodegen } from "./generators/java-unirest"
import { JavascriptFetchCodegen } from "./generators/javascript-fetch"
import { JavascriptJqueryCodegen } from "./generators/javascript-jquery"
import { JavascriptXhrCodegen } from "./generators/javascript-xhr"
import { NodejsAxiosCodegen } from "./generators/nodejs-axios"
import { NodejsNativeCodegen } from "./generators/nodejs-native"
import { NodejsRequestCodegen } from "./generators/nodejs-request"
import { NodejsUnirestCodegen } from "./generators/nodejs-unirest"
import { PhpCurlCodegen } from "./generators/php-curl"
import { PowershellRestmethodCodegen } from "./generators/powershell-restmethod"
import { PythonHttpClientCodegen } from "./generators/python-http-client"
import { PythonRequestsCodegen } from "./generators/python-requests"
import { RubyNetHttpCodeGen } from "./generators/ruby-net-http"
import { SalesforceApexCodegen } from "./generators/salesforce-apex"
import { ShellHttpieCodegen } from "./generators/shell-httpie"
import { ShellWgetCodegen } from "./generators/shell-wget"
/* Register code generators here.
* A code generator is defined as an object with the following structure.
*
* id: string
* name: string
* language: string // a string identifier used in ace editor for syntax highlighting
* // see node_modules/ace-builds/src-noconflict/mode-** files for valid value
* generator: (ctx) => string
*
*/
export const codegens = [
CLibcurlCodegen,
CsRestsharpCodegen,
CurlCodegen,
GoNativeCodegen,
JavaOkhttpCodegen,
JavaUnirestCodegen,
JavascriptFetchCodegen,
JavascriptJqueryCodegen,
JavascriptXhrCodegen,
NodejsAxiosCodegen,
NodejsNativeCodegen,
NodejsRequestCodegen,
NodejsUnirestCodegen,
PhpCurlCodegen,
PowershellRestmethodCodegen,
PythonHttpClientCodegen,
PythonRequestsCodegen,
RubyNetHttpCodeGen,
SalesforceApexCodegen,
ShellHttpieCodegen,
ShellWgetCodegen,
]
export type HoppCodegenContext = {
name: string
method: string
uri: string
url: string
pathName: string
auth: any // TODO: Change this
httpUser: string | null
httpPassword: string | null
bearerToken: string | null
headers: HoppRESTHeader[]
params: HoppRESTParam[]
bodyParams: FormDataKeyValue[]
rawParams: string | null
rawInput: boolean
rawRequestBody: string | null
contentType: string | null
queryString: string
}
export function generateCodeWithGenerator(
codegenID: string,
context: HoppCodegenContext
) {
if (codegenID) {
const gen = codegens.find(({ id }) => id === codegenID)
return gen ? gen.generator(context) : ""
}
return ""
}
function getCodegenAuth(
request: EffectiveHoppRESTRequest
): Pick<
HoppCodegenContext,
"auth" | "bearerToken" | "httpUser" | "httpPassword"
> {
if (!request.auth.authActive || request.auth.authType === "none") {
return {
auth: "None",
httpUser: null,
httpPassword: null,
bearerToken: null,
}
}
if (request.auth.authType === "basic") {
return {
auth: "Basic Auth",
httpUser: request.auth.username,
httpPassword: request.auth.password,
bearerToken: null,
}
} else {
return {
auth: "Bearer Token",
httpUser: null,
httpPassword: null,
bearerToken: request.auth.token,
}
}
}
function addhttps(url: string) {
if (!/^(?:f|ht)tps?:\/\//.test(url)) {
url = "https://" + url
}
return url
}
function getCodegenGeneralRESTInfo(
request: EffectiveHoppRESTRequest
): Pick<
HoppCodegenContext,
| "name"
| "uri"
| "url"
| "method"
| "queryString"
| "pathName"
| "params"
| "headers"
> {
let urlObj: URL
try {
urlObj = new URL(request.effectiveFinalURL)
} catch (error) {
urlObj = new URL(addhttps(request.effectiveFinalURL))
}
request.effectiveFinalParams.forEach(({ key, value }) => {
urlObj.searchParams.append(key, value)
})
// Remove authorization headers if auth is specified (because see #1798)
const finalHeaders =
request.auth.authActive && request.auth.authType !== "none"
? request.effectiveFinalHeaders
.filter((x) => x.key.toLowerCase() !== "authorization")
.map((x) => ({ ...x, active: true }))
: request.effectiveFinalHeaders.map((x) => ({ ...x, active: true }))
return {
name: request.name,
uri: request.effectiveFinalURL,
headers: finalHeaders,
params: request.effectiveFinalParams.map((x) => ({ ...x, active: true })),
method: request.method,
url: urlObj.origin,
queryString: `${urlObj.searchParams}`,
pathName: urlObj.pathname,
}
}
function getCodegenReqBodyData(
request: EffectiveHoppRESTRequest
): Pick<
HoppCodegenContext,
"rawRequestBody" | "rawInput" | "contentType" | "bodyParams" | "rawParams"
> {
return {
contentType: request.body.contentType,
rawInput: request.body.contentType !== "multipart/form-data",
rawRequestBody:
request.body.contentType !== "multipart/form-data"
? request.body.body
: null,
bodyParams:
request.body.contentType === "multipart/form-data"
? request.body.body
: [],
rawParams:
request.body.contentType !== "multipart/form-data"
? request.body.body
: null,
}
}
export function generateCodegenContext(
request: EffectiveHoppRESTRequest
): HoppCodegenContext {
return {
...getCodegenAuth(request),
...getCodegenGeneralRESTInfo(request),
...getCodegenReqBodyData(request),
}
}

View File

@@ -1,79 +0,0 @@
export const CLibcurlCodegen = {
id: "c-libcurl",
name: "C libcurl",
language: "c_cpp",
generator: ({
auth,
httpUser,
httpPassword,
method,
url,
pathName,
queryString,
bearerToken,
headers,
rawInput,
rawParams,
rawRequestBody,
contentType,
}) => {
const requestString = []
requestString.push("CURL *hnd = curl_easy_init();")
requestString.push(
`curl_easy_setopt(hnd, CURLOPT_CUSTOMREQUEST, "${method}");`
)
requestString.push(
`curl_easy_setopt(hnd, CURLOPT_URL, "${url}${pathName}?${queryString}");`
)
requestString.push(`struct curl_slist *headers = NULL;`)
// append header attributes
if (headers) {
headers.forEach(({ key, value }) => {
if (key)
requestString.push(
`headers = curl_slist_append(headers, "${key}: ${value}");`
)
})
}
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
`headers = curl_slist_append(headers, "Authorization: Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}");`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(
`headers = curl_slist_append(headers, "Authorization: Bearer ${bearerToken}");`
)
}
// set headers
if (headers?.length) {
requestString.push("curl_easy_setopt(hnd, CURLOPT_HTTPHEADER, headers);")
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
if (contentType && contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
} else {
requestBody = requestBody ? JSON.stringify(requestBody) : null
}
// set request-body
if (requestBody) {
requestString.push(
`curl_easy_setopt(hnd, CURLOPT_POSTFIELDS, ${requestBody});`
)
}
}
requestString.push(`CURLcode ret = curl_easy_perform(hnd);`)
return requestString.join("\n")
},
}

View File

@@ -1,102 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const CsRestsharpCodegen = {
id: "cs-restsharp",
name: "C# RestSharp",
language: "csharp",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
if (requestBody) {
requestBody = requestBody.replace(/"/g, '""') // escape quotes for C# verbatim string compatibility
}
// prepare data
let requestDataFormat
let requestContentType
if (isJSONContentType(contentType)) {
requestDataFormat = "DataFormat.Json"
requestContentType = "text/json"
} else {
requestDataFormat = "DataFormat.Xml"
requestContentType = "text/xml"
}
const verbs = [
{ verb: "GET", csMethod: "Get" },
{ verb: "POST", csMethod: "Post" },
{ verb: "PUT", csMethod: "Put" },
{ verb: "PATCH", csMethod: "Patch" },
{ verb: "DELETE", csMethod: "Delete" },
]
// create client and request
requestString.push(`var client = new RestClient("${url}");\n\n`)
requestString.push(
`var request = new RestRequest("${pathName}?${queryString}", ${requestDataFormat});\n\n`
)
// authentification
if (auth === "Basic Auth") {
requestString.push(
`client.Authenticator = new HttpBasicAuthenticator("${httpUser}", "${httpPassword}");\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(
`request.AddHeader("Authorization", "Bearer ${bearerToken}");\n`
)
}
// custom headers
if (headers) {
headers.forEach(({ key, value }) => {
if (key) {
requestString.push(`request.AddHeader("${key}", "${value}");\n`)
}
})
}
requestString.push(`\n`)
// set body
if (["POST", "PUT", "PATCH", "DELETE"].includes(method) && requestBody) {
requestString.push(
`request.AddParameter("${requestContentType}", @"${requestBody}", ParameterType.RequestBody);\n\n`
)
}
// process
const verb = verbs.find((v) => v.verb === method)
if (verb) {
requestString.push(`var response = client.${verb.csMethod}(request);\n\n`)
} else {
return ""
}
// analyse result
requestString.push(
`if (!response.IsSuccessful)\n{\n Console.WriteLine("An error occurred " + response.ErrorMessage);\n}\n\n`
)
requestString.push(`var result = response.Content;\n`)
return requestString.join("")
},
}

View File

@@ -1,45 +0,0 @@
export const CurlCodegen = {
id: "curl",
name: "cURL",
language: "sh",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const requestString = []
requestString.push(`curl -X ${method}`)
requestString.push(` '${url}${pathName}?${queryString}'`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
` -H 'Authorization: Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}'`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(` -H 'Authorization: Bearer ${bearerToken}'`)
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) requestString.push(` -H '${key}: ${value}'`)
})
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
requestBody = requestBody || ""
requestString.push(` -d '${requestBody}'`)
}
return requestString.join(" \\\n")
},
}

View File

@@ -1,87 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const GoNativeCodegen = {
id: "go-native",
name: "Go Native",
language: "golang",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
let genHeaders = []
// initial request setup
const requestBody = rawInput ? rawParams : rawRequestBody
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestString.push(`var reqBody = []byte(\`${requestBody}\`)\n\n`)
requestString.push(
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", bytes.NewBuffer(reqBody))\n`
)
} else if (contentType.includes("x-www-form-urlencoded")) {
requestString.push(
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", strings.NewReader("${requestBody}"))\n`
)
}
} else {
requestString.push(
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", nil)\n`
)
}
} else {
requestString.push(
`req, err := http.NewRequest("${method}", "${url}${pathName}?${queryString}", nil)\n`
)
}
// headers
// auth
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
`req.Header.Set("Authorization", "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}")\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(
`req.Header.Set("Authorization", "Bearer ${bearerToken}")\n`
)
}
// custom headers
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(`req.Header.Set("${key}", "${value}")\n`)
})
}
genHeaders = genHeaders.join("").slice(0, -1)
requestString.push(`${genHeaders}\n`)
requestString.push(
`if err != nil {\n log.Fatalf("An error occurred %v", err)\n}\n\n`
)
// request boilerplate
requestString.push(`client := &http.Client{}\n`)
requestString.push(
`resp, err := client.Do(req)\nif err != nil {\n log.Fatalf("An error occurred %v", err)\n}\n\n`
)
requestString.push(`defer resp.Body.Close()\n`)
requestString.push(
`body, err := ioutil.ReadAll(resp.Body)\nif err != nil {\n log.Fatalln(err)\n}\n`
)
return requestString.join("")
},
}

View File

@@ -1,83 +0,0 @@
export const JavaOkhttpCodegen = {
id: "java-okhttp",
name: "Java OkHttp",
language: "java",
generator: ({
auth,
httpUser,
httpPassword,
method,
url,
pathName,
queryString,
bearerToken,
headers,
rawInput,
rawParams,
rawRequestBody,
contentType,
}) => {
const requestString = []
requestString.push(
"OkHttpClient client = new OkHttpClient().newBuilder().build();"
)
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
if (contentType && contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
} else {
requestBody = requestBody ? JSON.stringify(requestBody) : null
}
if (contentType) {
requestString.push(
`MediaType mediaType = MediaType.parse("${contentType}");`
)
}
if (requestBody) {
requestString.push(
`RequestBody body = RequestBody.create(mediaType,${requestBody});`
)
} else {
requestString.push(
"RequestBody body = RequestBody.create(null, new byte[0]);"
)
}
}
requestString.push("Request request = new Request.Builder()")
requestString.push(`.url("${url}${pathName}?${queryString}")`)
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
requestString.push(`.method("${method}", body)`)
} else {
requestString.push(`.method("${method}", null)`)
}
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
`.addHeader("authorization", "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}") \n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(
`.addHeader("authorization", "Bearer ${bearerToken}" ) \n`
)
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) requestString.push(`.addHeader("${key}", "${value}")`)
})
}
requestString.push(`.build();`)
requestString.push("Response response = client.newCall(request).execute();")
return requestString.join("\n")
},
}

View File

@@ -1,73 +0,0 @@
export const JavaUnirestCodegen = {
id: "java-unirest",
name: "Java Unirest",
language: "java",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
const verbs = [
{ verb: "GET", unirestMethod: "get" },
{ verb: "POST", unirestMethod: "post" },
{ verb: "PUT", unirestMethod: "put" },
{ verb: "PATCH", unirestMethod: "patch" },
{ verb: "DELETE", unirestMethod: "delete" },
{ verb: "HEAD", unirestMethod: "head" },
{ verb: "OPTIONS", unirestMethod: "options" },
]
// create client and request
const verb = verbs.find((v) => v.verb === method)
const unirestMethod = verb.unirestMethod || "get"
requestString.push(
`HttpResponse<String> response = Unirest.${unirestMethod}("${url}${pathName}?${queryString}")\n`
)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
`.header("authorization", "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}") \n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(`.header("authorization", "Bearer ${bearerToken}") \n`)
}
// custom headers
if (headers) {
headers.forEach(({ key, value }) => {
if (key) {
requestString.push(`.header("${key}", "${value}")\n`)
}
})
}
// set body
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (contentType && requestBody) {
if (contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
} else {
requestBody = JSON.stringify(requestBody)
}
}
if (requestBody) {
requestString.push(`.body(${requestBody})\n`)
}
}
requestString.push(`.asString();\n`)
return requestString.join("")
},
}

View File

@@ -1,68 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const JavascriptFetchCodegen = {
id: "js-fetch",
name: "JavaScript Fetch",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
let genHeaders = []
requestString.push(`fetch("${url}${pathName}?${queryString}", {\n`)
requestString.push(` method: "${method}",\n`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = `JSON.stringify(${requestBody})`
} else if (contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
}
}
if (requestBody) {
requestString.push(` body: ${requestBody},\n`)
}
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}": "${value}",\n`)
})
}
genHeaders = genHeaders.join("").slice(0, -2)
if (genHeaders) {
requestString.push(` headers: {\n${genHeaders}\n },\n`)
}
requestString.push(' credentials: "same-origin"\n')
requestString.push("}).then(function(response) {\n")
requestString.push(" return response.text()\n")
requestString.push("}).catch(function(e) {\n")
requestString.push(" console.error(e)\n")
requestString.push("})")
return requestString.join("")
},
}

View File

@@ -1,66 +0,0 @@
export const JavascriptJqueryCodegen = {
id: "js-jquery",
name: "JavaScript jQuery",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
contentType,
rawRequestBody,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(
`jQuery.ajax({\n url: "${url}${pathName}?${queryString}"`
)
requestString.push(`,\n method: "${method.toUpperCase()}"`)
let requestBody = rawInput ? rawParams : rawRequestBody
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (contentType && contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
} else {
requestBody = requestBody.replaceAll("}", " }")
}
requestString.push(`,\n data: ${requestBody}`)
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}": "${value}",\n`)
})
}
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
if (genHeaders.length > 0) {
requestString.push(
`,\n headers: {\n${genHeaders.join("").slice(0, -2)}\n }`
)
}
requestString.push("\n}).then(response => {\n")
requestString.push(" console.log(response);\n")
requestString.push("})")
requestString.push(".catch(e => {\n")
requestString.push(" console.error(e);\n")
requestString.push("})\n")
return requestString.join("")
},
}

View File

@@ -1,60 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const JavascriptXhrCodegen = {
id: "js-xhr",
name: "JavaScript XHR",
language: "javascript",
generator: ({
auth,
httpUser,
httpPassword,
method,
url,
pathName,
queryString,
bearerToken,
headers,
rawInput,
rawParams,
rawRequestBody,
contentType,
}) => {
const requestString = []
requestString.push("const xhr = new XMLHttpRequest()")
requestString.push(`xhr.addEventListener("readystatechange", function() {`)
requestString.push(` if(this.readyState === 4) {`)
requestString.push(` console.log(this.responseText)\n }\n})`)
const user = auth === "Basic Auth" ? `'${httpUser}'` : null
const password = auth === "Basic Auth" ? `'${httpPassword}'` : null
requestString.push(
`xhr.open('${method}', '${url}${pathName}?${queryString}', true, ${user}, ${password})`
)
if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(
`xhr.setRequestHeader('Authorization', 'Bearer ${bearerToken}')`
)
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key)
requestString.push(`xhr.setRequestHeader('${key}', '${value}')`)
})
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = `JSON.stringify(${requestBody})`
} else if (contentType.includes("x-www-form-urlencoded")) {
requestBody = `"${requestBody}"`
}
}
requestBody = requestBody || ""
requestString.push(`xhr.send(${requestBody})`)
} else {
requestString.push("xhr.send()")
}
return requestString.join("\n")
},
}

View File

@@ -1,73 +0,0 @@
export const NodejsAxiosCodegen = {
id: "nodejs-axios",
name: "NodeJs Axios",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
let requestBody = rawInput ? rawParams : rawRequestBody
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (
contentType &&
contentType.includes("x-www-form-urlencoded") &&
requestBody
) {
requestString.push(
`var params = new URLSearchParams("${requestBody}")\n`
)
requestBody = "params"
}
}
requestString.push(
`axios.${method.toLowerCase()}('${url}${pathName}?${queryString}'`
)
if (requestBody && requestBody.length !== 0) {
requestString.push(", ")
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(`\n "${key}": "${value}",`)
})
}
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
requestString.push(`${requestBody},`)
}
if (genHeaders.length > 0) {
requestString.push(
`{ \n headers : {${genHeaders.join("").slice(0, -1)}\n }\n}`
)
}
requestString.push(").then(response => {\n")
requestString.push(" console.log(response);\n")
requestString.push("})")
requestString.push(".catch(e => {\n")
requestString.push(" console.error(e);\n")
requestString.push("})\n")
return requestString.join("")
},
}

View File

@@ -1,82 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const NodejsNativeCodegen = {
id: "nodejs-native",
name: "NodeJs Native",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`const http = require('http');\n\n`)
requestString.push(`const url = '${url}${pathName}?${queryString}';\n`)
requestString.push(`const options = {\n`)
requestString.push(` method: '${method}',\n`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
let requestBody
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
requestBody = rawInput ? rawParams : rawRequestBody
if (isJSONContentType(contentType) && requestBody) {
requestBody = `JSON.stringify(${requestBody})`
} else if (requestBody) {
requestBody = `\`${requestBody}\``
}
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}": "${value}",\n`)
})
}
if (genHeaders.length > 0 || headers.length > 0) {
requestString.push(
` headers: {\n${genHeaders.join("").slice(0, -2)}\n }\n`
)
}
requestString.push(`};\n\n`)
requestString.push(
`const request = http.request(url, options, (response) => {\n`
)
requestString.push(` console.log(response);\n`)
requestString.push(`});\n\n`)
requestString.push(`request.on('error', (e) => {\n`)
requestString.push(` console.error(e);\n`)
requestString.push(`});\n`)
if (requestBody) {
requestString.push(`\nrequest.write(${requestBody});\n`)
}
requestString.push(`request.end();`)
return requestString.join("")
},
}

View File

@@ -1,87 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const NodejsRequestCodegen = {
id: "nodejs-request",
name: "NodeJs Request",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`const request = require('request');\n`)
requestString.push(`const options = {\n`)
requestString.push(` method: '${method.toLowerCase()}',\n`)
requestString.push(` url: '${url}${pathName}?${queryString}'`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
let reqBodyType = "formData"
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = `JSON.stringify(${requestBody})`
reqBodyType = "body"
} else if (contentType.includes("x-www-form-urlencoded")) {
const formData = []
if (requestBody.includes("=")) {
requestBody.split("&").forEach((rq) => {
const [key, val] = rq.split("=")
formData.push(`"${key}": "${val}"`)
})
}
if (formData.length) {
requestBody = `{${formData.join(", ")}}`
}
reqBodyType = "form"
} else if (contentType.includes("application/xml")) {
requestBody = `\`${requestBody}\``
reqBodyType = "body"
}
}
if (requestBody) {
requestString.push(`,\n ${reqBodyType}: ${requestBody}`)
}
}
if (headers.length > 0) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}": "${value}",\n`)
})
}
if (genHeaders.length > 0 || headers.length > 0) {
requestString.push(
`,\n headers: {\n${genHeaders.join("").slice(0, -2)}\n }`
)
}
requestString.push(`\n}`)
requestString.push(`\nrequest(options, (error, response) => {\n`)
requestString.push(` if (error) throw new Error(error);\n`)
requestString.push(` console.log(response.body);\n`)
requestString.push(`});`)
return requestString.join("")
},
}

View File

@@ -1,87 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const NodejsUnirestCodegen = {
id: "nodejs-unirest",
name: "NodeJs Unirest",
language: "javascript",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`const unirest = require('unirest');\n`)
requestString.push(`const req = unirest(\n`)
requestString.push(
`'${method.toLowerCase()}', '${url}${pathName}?${queryString}')\n`
)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization": "Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization": "Bearer ${bearerToken}",\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
let reqBodyType = "formData"
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = `\`${requestBody}\``
reqBodyType = "send"
} else if (contentType.includes("x-www-form-urlencoded")) {
const formData = []
if (requestBody.includes("=")) {
requestBody.split("&").forEach((rq) => {
const [key, val] = rq.split("=")
formData.push(`"${key}": "${val}"`)
})
}
if (formData.length) {
requestBody = `{${formData.join(", ")}}`
}
reqBodyType = "send"
} else if (contentType.includes("application/xml")) {
requestBody = `\`${requestBody}\``
reqBodyType = "send"
}
}
if (requestBody) {
requestString.push(`\n.${reqBodyType}( ${requestBody})`)
}
}
if (headers.length > 0) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}": "${value}",\n`)
})
}
if (genHeaders.length > 0 || headers.length > 0) {
requestString.push(
`\n.headers({\n${genHeaders.join("").slice(0, -2)}\n })`
)
}
requestString.push(`\n.end(function (res) {\n`)
requestString.push(` if (res.error) throw new Error(res.error);\n`)
requestString.push(` console.log(res.raw_body);\n });\n`)
return requestString.join("")
},
}

View File

@@ -1,99 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
export const PhpCurlCodegen = {
id: "php-curl",
name: "PHP cURL",
language: "php",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`<?php\n`)
requestString.push(`$curl = curl_init();\n`)
requestString.push(`curl_setopt_array($curl, array(\n`)
requestString.push(` CURLOPT_URL => "${url}${pathName}?${queryString}",\n`)
requestString.push(` CURLOPT_RETURNTRANSFER => true,\n`)
requestString.push(` CURLOPT_ENCODING => "",\n`)
requestString.push(` CURLOPT_MAXREDIRS => 10,\n`)
requestString.push(` CURLOPT_TIMEOUT => 0,\n`)
requestString.push(` CURLOPT_FOLLOWLOCATION => true,\n`)
requestString.push(` CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,\n`)
requestString.push(` CURLOPT_CUSTOMREQUEST => "${method}",\n`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` "Authorization: Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}",\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` "Authorization: Bearer ${bearerToken}",\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
let requestBody = rawInput ? rawParams : rawRequestBody
if (contentType && requestBody) {
if (
!isJSONContentType(contentType) &&
rawInput &&
!contentType.includes("x-www-form-urlencoded")
) {
const toRemove = /[\n {}]/gim
const toReplace = /:/gim
const parts = requestBody
.replace(toRemove, "")
.replace(toReplace, "=>")
requestBody = `array(${parts})`
} else if (isJSONContentType(contentType)) {
requestBody = JSON.stringify(requestBody)
} else if (contentType.includes("x-www-form-urlencoded")) {
if (requestBody.includes("=")) {
requestBody = `"${requestBody}"`
} else {
const requestObject = JSON.parse(requestBody)
requestBody = `"${Object.keys(requestObject)
.map((key) => `${key}=${requestObject[key]}`)
.join("&")}"`
}
}
requestString.push(` CURLOPT_POSTFIELDS => ${requestBody},\n`)
}
}
if (headers.length > 0) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` "${key}: ${value}",\n`)
})
}
if (genHeaders.length > 0 || headers.length > 0) {
requestString.push(
` CURLOPT_HTTPHEADER => array(\n${genHeaders
.join("")
.slice(0, -2)}\n )\n`
)
}
requestString.push(`));\n`)
requestString.push(`$response = curl_exec($curl);\n`)
requestString.push(`curl_close($curl);\n`)
requestString.push(`echo $response;\n`)
return requestString.join("")
},
}

View File

@@ -1,63 +0,0 @@
export const PowershellRestmethodCodegen = {
id: "powershell-restmethod",
name: "PowerShell RestMethod",
language: "powershell",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const methodsWithBody = ["Put", "Post", "Delete"]
const formattedMethod =
method[0].toUpperCase() + method.substring(1).toLowerCase()
const includeBody = methodsWithBody.includes(formattedMethod)
const requestString = []
let genHeaders = []
let variables = ""
requestString.push(
`Invoke-RestMethod -Method '${formattedMethod}' -Uri '${url}${pathName}?${queryString}'`
)
const requestBody = rawInput ? rawParams : rawRequestBody
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (requestBody && includeBody) {
variables = variables.concat(`$body = @'\n${requestBody}\n'@\n\n`)
}
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(` '${key}' = '${value}'\n`)
})
}
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
` 'Authorization' = 'Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}'\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(` 'Authorization' = 'Bearer ${bearerToken}'\n`)
}
genHeaders = genHeaders.join("").slice(0, -1)
if (genHeaders) {
variables = variables.concat(`$headers = @{\n${genHeaders}\n}\n`)
requestString.push(` -Headers $headers`)
}
if (requestBody && includeBody) {
requestString.push(` -Body $body`)
}
return `${variables}${requestString.join("")}`
},
}

View File

@@ -1,105 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
const printHeaders = (headers) => {
if (headers.length) {
return [`headers = {\n`, ` ${headers.join(",\n ")}\n`, `}\n`]
} else {
return [`headers = {}\n`]
}
}
export const PythonHttpClientCodegen = {
id: "python-http-client",
name: "Python http.client",
language: "python",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`import http.client\n`)
requestString.push(`import mimetypes\n`)
const currentUrl = new URL(url)
const hostname = currentUrl.hostname
const port = currentUrl.port
if (!port) {
requestString.push(`conn = http.client.HTTPSConnection("${hostname}")\n`)
} else {
requestString.push(
`conn = http.client.HTTPSConnection("${hostname}", ${port})\n`
)
}
// auth headers
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
`'Authorization': 'Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}'`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(`'Authorization': 'Bearer ${bearerToken}'`)
}
// custom headers
if (headers.length) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(`'${key}': '${value}'`)
})
}
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
if (method === "GET") {
requestString.push(...printHeaders(genHeaders))
requestString.push(`payload = ''\n`)
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
requestString.push(...printHeaders(genHeaders))
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = JSON.stringify(requestBody)
requestString.push(`payload = ${requestBody}\n`)
} else if (contentType.includes("x-www-form-urlencoded")) {
const formData = []
if (requestBody.includes("=")) {
requestBody.split("&").forEach((rq) => {
const [key, val] = rq.split("=")
formData.push(`('${key}', '${val}')`)
})
}
if (formData.length) {
requestString.push(`payload = [${formData.join(",\n ")}]\n`)
}
} else {
requestString.push(`paylod = '''${requestBody}'''\n`)
}
} else {
requestString.push(`payload = ''\n`)
}
}
requestString.push(
`conn.request("${method}", "${pathName}?${queryString}", payload, headers)\n`
)
requestString.push(`res = conn.getresponse()\n`)
requestString.push(`data = res.read()\n`)
requestString.push(`print(data.decode("utf-8"))`)
return requestString.join("")
},
}

View File

@@ -1,102 +0,0 @@
import { isJSONContentType } from "~/helpers/utils/contenttypes"
const printHeaders = (headers) => {
if (headers.length) {
return [`headers = {\n`, ` ${headers.join(",\n ")}\n`, `}\n`]
}
return []
}
export const PythonRequestsCodegen = {
id: "python-requests",
name: "Python Requests",
language: "python",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
contentType,
headers,
}) => {
const requestString = []
const genHeaders = []
requestString.push(`import requests\n\n`)
requestString.push(`url = '${url}${pathName}?${queryString}'\n`)
// auth headers
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
genHeaders.push(
`'Authorization': 'Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}'`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
genHeaders.push(`'Authorization': 'Bearer ${bearerToken}'`)
}
// custom headers
if (headers.length) {
headers.forEach(({ key, value }) => {
if (key) genHeaders.push(`'${key}': '${value}'`)
})
}
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
let requestDataObj = ""
requestString.push(...printHeaders(genHeaders))
if (["POST", "PUT", "PATCH", "DELETE"].includes(method)) {
if (contentType && requestBody) {
if (isJSONContentType(contentType)) {
requestBody = JSON.stringify(requestBody)
requestDataObj = `data = ${requestBody}\n`
} else if (contentType.includes("x-www-form-urlencoded")) {
const formData = []
if (requestBody.includes("=")) {
requestBody.split("&").forEach((rq) => {
const [key, val] = rq.split("=")
formData.push(`('${key}', '${val}')`)
})
}
if (formData.length) {
requestDataObj = `data = [${formData.join(",\n ")}]\n`
}
} else {
requestDataObj = `data = '''${requestBody}'''\n`
}
}
}
if (requestDataObj) {
requestString.push(requestDataObj)
}
requestString.push(`response = requests.request(\n`)
requestString.push(` '${method}',\n`)
requestString.push(` '${url}${pathName}?${queryString}',\n`)
if (requestDataObj && requestBody) {
requestString.push(` data=data,\n`)
}
if (genHeaders.length) {
requestString.push(` headers=headers,\n`)
}
requestString.push(`)\n\n`)
requestString.push(`print(response)`)
return requestString.join("")
},
}

View File

@@ -1,80 +0,0 @@
export const RubyNetHttpCodeGen = {
id: "ruby-net-http",
name: "Ruby Net::HTTP",
language: "ruby",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const requestString = []
requestString.push(`require 'net/http'\n`)
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
if (requestBody) {
requestBody = requestBody.replaceAll(/'/g, "\\'") // escape single-quotes for single-quoted string compatibility
}
const verbs = [
{ verb: "GET", rbMethod: "Get" },
{ verb: "POST", rbMethod: "Post" },
{ verb: "PUT", rbMethod: "Put" },
{ verb: "PATCH", rbMethod: "Patch" },
{ verb: "DELETE", rbMethod: "Delete" },
]
// create URI and request
const verb = verbs.find((v) => v.verb === method)
if (!verb) return ""
requestString.push(`uri = URI.parse('${url}${pathName}?${queryString}')\n`)
requestString.push(`request = Net::HTTP::${verb.rbMethod}.new(uri)`)
// custom headers
if (headers) {
headers.forEach(({ key, value }) => {
if (key) {
requestString.push(`request['${key}'] = '${value}'`)
}
})
}
// authentication
if (auth === "Basic Auth") {
requestString.push(`request.basic_auth('${httpUser}', '${httpPassword}')`)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(`request['Authorization'] = 'Bearer ${bearerToken}'`)
}
// set body
if (["POST", "PUT", "PATCH", "DELETE"].includes(method) && requestBody) {
requestString.push(`request.body = '${requestBody}'\n`)
}
// process
requestString.push(`http = Net::HTTP.new(uri.host, uri.port)`)
requestString.push(`http.use_ssl = uri.is_a?(URI::HTTPS)`)
requestString.push(`response = http.request(request)\n`)
// analyse result
requestString.push(`unless response.is_a?(Net::HTTPSuccess) then`)
requestString.push(
` raise "An error occurred: #{response.code} #{response.message}"`
)
requestString.push(`else`)
requestString.push(` puts response.body`)
requestString.push(`end`)
return requestString.join("\n")
},
}

View File

@@ -1,80 +0,0 @@
export const SalesforceApexCodegen = {
id: "salesforce-apex",
name: "Salesforce Apex",
language: "apex",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const requestString = []
// initial request setup
let requestBody = rawInput ? rawParams : rawRequestBody
if (requestBody) {
requestBody = JSON.stringify(requestBody)
.replace(/^"|"$/g, "")
.replace(/\\"/g, '"')
.replace(/'/g, "\\'") // Apex uses single quotes for strings
}
// create request
requestString.push(`HttpRequest request = new HttpRequest();\n`)
requestString.push(`request.setMethod('${method}');\n`)
requestString.push(
`request.setEndpoint('${url}${pathName}?${queryString}');\n\n`
)
// authentification
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
`request.setHeader('Authorization', 'Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}');\n`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(
`request.setHeader('Authorization', 'Bearer ${bearerToken}');\n`
)
}
// custom headers
if (headers) {
headers.forEach(({ key, value }) => {
if (key) {
requestString.push(`request.setHeader('${key}', '${value}');\n`)
}
})
}
requestString.push(`\n`)
// set body
if (["POST", "PUT", "PATCH", "DELETE"].includes(method) && requestBody) {
requestString.push(`request.setBody('${requestBody}');\n\n`)
}
// process
requestString.push(`try {\n`)
requestString.push(` Http client = new Http();\n`)
requestString.push(` HttpResponse response = client.send(request);\n`)
requestString.push(` System.debug(response.getBody());\n`)
requestString.push(`} catch (CalloutException ex) {\n`)
requestString.push(
` System.debug('An error occurred ' + ex.getMessage());\n`
)
requestString.push(`}`)
return requestString.join("")
},
}

View File

@@ -1,58 +0,0 @@
export const ShellHttpieCodegen = {
id: "shell-httpie",
name: "Shell HTTPie",
language: "sh",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const methodsWithBody = ["POST", "PUT", "PATCH", "DELETE"]
const includeBody = methodsWithBody.includes(method)
const requestString = []
let requestBody = rawInput ? rawParams : rawRequestBody
if (requestBody && includeBody) {
requestBody = requestBody.replace(/'/g, "\\'")
// Send request body via redirected input
requestString.push(`echo -n $'${requestBody}' | `)
}
// Executable itself
requestString.push(`http`)
// basic authentication
if (auth === "Basic Auth") {
requestString.push(` -a ${httpUser}:${httpPassword}`)
}
// URL
let escapedUrl = `${url}${pathName}?${queryString}`
escapedUrl = escapedUrl.replace(/'/g, "\\'")
requestString.push(` ${method} $'${escapedUrl}'`)
if (headers) {
headers.forEach(({ key, value }) => {
requestString.push(
` $'${key.replace(/'/g, "\\'")}:${value.replace(/'/g, "\\'")}'`
)
})
}
if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(` 'Authorization:Bearer ${bearerToken}'`)
}
return requestString.join("")
},
}

View File

@@ -1,43 +0,0 @@
export const ShellWgetCodegen = {
id: "shell-wget",
name: "Shell wget",
language: "sh",
generator: ({
url,
pathName,
queryString,
auth,
httpUser,
httpPassword,
bearerToken,
method,
rawInput,
rawParams,
rawRequestBody,
headers,
}) => {
const requestString = []
const requestBody = rawInput ? rawParams : rawRequestBody
requestString.push(`wget -O - --method=${method}`)
requestString.push(` '${url}${pathName}?${queryString}'`)
if (auth === "Basic Auth") {
const basic = `${httpUser}:${httpPassword}`
requestString.push(
` --header='Authorization: Basic ${window.btoa(
unescape(encodeURIComponent(basic))
)}'`
)
} else if (auth === "Bearer Token" || auth === "OAuth 2.0") {
requestString.push(` --header='Authorization: Bearer ${bearerToken}'`)
}
if (headers) {
headers.forEach(({ key, value }) => {
if (key) requestString.push(` --header='${key}: ${value}'`)
})
}
if (["POST", "PUT", "PATCH", "DELETE"].includes(method) && requestBody) {
requestString.push(` --body-data='${requestBody}'`)
}
return requestString.join(" \\\n")
},
}

View File

@@ -281,6 +281,7 @@ export function useCodemirror(
},
})
}
cachedValue.value = newVal
})
watch(

View File

@@ -0,0 +1,102 @@
import * as Har from "har-format"
import { HoppRESTRequest } from "@hoppscotch/data"
import { FieldEquals, objectFieldIncludes } from "../typeutils"
// Hoppscotch support HAR Spec 1.2
// For more info on the spec: http://www.softwareishard.com/blog/har-12-spec/
const buildHarHeaders = (req: HoppRESTRequest): Har.Header[] => {
return req.headers
.filter((header) => header.active)
.map((header) => ({
name: header.key,
value: header.value,
}))
}
const buildHarQueryStrings = (req: HoppRESTRequest): Har.QueryString[] => {
return req.params
.filter((param) => param.active)
.map((param) => ({
name: param.key,
value: param.value,
}))
}
const buildHarPostParams = (
req: HoppRESTRequest &
FieldEquals<HoppRESTRequest, "method", ["POST", "PUT"]> & {
body: {
contentType: "application/x-www-form-urlencoded" | "multipart/form-data"
}
}
): Har.Param[] => {
// URL Encoded strings have a string style of contents
if (req.body.contentType === "application/x-www-form-urlencoded") {
return req.body.body
.split("&") // Split by separators
.map((keyValue) => {
const [key, value] = keyValue.split("=")
return {
name: key,
value,
}
})
} else {
// FormData has its own format
return req.body.body.flatMap((entry) => {
if (entry.isFile) {
// We support multiple files
return entry.value.map(
(file) =>
<Har.Param>{
name: entry.key,
fileName: entry.key, // TODO: Blob doesn't contain file info, anyway to bring file name here ?
contentType: file.type,
}
)
} else {
return {
name: entry.key,
value: entry.value,
}
}
})
}
}
const buildHarPostData = (req: HoppRESTRequest): Har.PostData | undefined => {
if (!req.body.contentType) return undefined
if (
objectFieldIncludes(req.body, "contentType", [
"application/x-www-form-urlencoded",
"multipart/form-data",
] as const)
) {
return {
mimeType: req.body.contentType, // By default assume JSON ?
params: buildHarPostParams(req as any),
}
}
return {
mimeType: req.body.contentType, // Let's assume by default content type is JSON
text: req.body.body,
}
}
export const buildHarRequest = (req: HoppRESTRequest): Har.Request => {
return {
bodySize: -1, // TODO: It would be cool if we can calculate the body size
headersSize: -1, // TODO: It would be cool if we can calculate the header size
httpVersion: "HTTP/1.1",
cookies: [], // Hoppscotch does not have formal support for Cookies as of right now
headers: buildHarHeaders(req),
method: req.method,
queryString: buildHarQueryStrings(req),
url: req.endpoint,
postData: buildHarPostData(req),
}
}

View File

@@ -0,0 +1,224 @@
import HTTPSnippet from "httpsnippet"
import { HoppRESTRequest } from "@hoppscotch/data"
import * as O from "fp-ts/Option"
import * as E from "fp-ts/Either"
import { pipe } from "fp-ts/function"
import { buildHarRequest } from "./har"
// Hoppscotch's Code Generation is Powered by HTTPSnippet (https://github.com/Kong/httpsnippet)
// If you want to add support for your favorite language/library, please contribute to the HTTPSnippet repo <3
/**
* An array defining all the code generators and their info
*/
export const CodegenDefinitions = [
{
name: "c-curl",
lang: "c",
mode: "libcurl",
caption: "C - cURL",
},
{
name: "clojure-clj_http",
lang: "clojure",
mode: "clj_http",
caption: "Clojure - clj-http",
},
{
name: "csharp-httpclient",
lang: "csharp",
mode: "httpclient",
caption: "C# - HttpClient",
},
{
name: "csharp-restsharp",
lang: "csharp",
mode: "restsharp",
caption: "C# - RestSharp",
},
{
name: "go-native",
lang: "go",
mode: "native",
caption: "Go",
},
{
name: "http-http1.1",
lang: "http",
mode: "http1.1",
caption: "HTTP - HTTP 1.1 Request String",
},
{
name: "java-asynchttp",
lang: "java",
mode: "asynchttp",
caption: "Java - AsyncHTTPClient",
},
{
name: "java-nethttp",
lang: "java",
mode: "nethttp",
caption: "Java - java.net.http",
},
{
name: "java-okhttp",
lang: "java",
mode: "okhttp",
caption: "Java - OkHttp",
},
{
name: "java-unirest",
lang: "java",
mode: "unirest",
caption: "Java - Unirest",
},
{
name: "javascript-axios",
lang: "javascript",
mode: "axios",
caption: "JavaScript - Axios",
},
{
name: "javascript-fetch",
lang: "javascript",
mode: "fetch",
caption: "JavaScript - Fetch",
},
{
name: "javascript-jquery",
lang: "javascript",
mode: "jquery",
caption: "JavaScript - jQuery",
},
{
name: "javascript-xhr",
lang: "javascript",
mode: "xhr",
caption: "JavaScript - XMLHttpRequest",
},
{
name: "kotlin-okhttp",
lang: "kotlin",
mode: "okhttp",
caption: "Kotlin - OkHttp",
},
{
name: "objc-nsurlsession",
lang: "objc",
mode: "nsurlsession",
caption: "Objective C - NSURLSession",
},
{
name: "ocaml-cohttp",
lang: "ocaml",
mode: "cohttp",
caption: "OCaml - cohttp",
},
{
name: "php-curl",
lang: "php",
mode: "curl",
caption: "PHP - cURL",
},
{
name: "powershell-restmethod",
lang: "powershell",
mode: "restmethod",
caption: "Powershell - Invoke-RestMethod",
},
{
name: "powershell-webrequest",
lang: "powershell",
mode: "webrequest",
caption: "Powershell - Invoke-WebRequest",
},
{
name: "python-python3",
lang: "python",
mode: "python3",
caption: "Python - Python 3 Native",
},
{
name: "python-requests",
lang: "python",
mode: "requests",
caption: "Python - Requests",
},
{
name: "r-httr",
lang: "r",
mode: "httr",
caption: "R - httr",
},
{
name: "ruby-native",
lang: "ruby",
mode: "native",
caption: "Ruby - Ruby Native",
},
{
name: "shell-curl",
lang: "shell",
mode: "curl",
caption: "Shell - cURL",
},
{
name: "shell-httpie",
lang: "shell",
mode: "httpie",
caption: "Shell - HTTPie",
},
{
name: "shell-wget",
lang: "shell",
mode: "wget",
caption: "Shell - Wget",
},
{
name: "swift-nsurlsession",
lang: "swift",
mode: "nsurlsession",
caption: "Swift - NSURLSession",
},
] as const
/**
* A type which defines all the valid code generators
*/
export type CodegenName = typeof CodegenDefinitions[number]["name"]
/**
* Generates Source Code for the given codgen
* @param codegen The codegen to apply
* @param req The request to generate using
* @returns An Option with the generated code snippet
*/
export const generateCode = (
codegen: CodegenName,
req: HoppRESTRequest
): O.Option<string> => {
// Since the Type contract guarantees a match in the array, we are enforcing non-null
const codegenInfo = CodegenDefinitions.find((v) => v.name === codegen)!
return pipe(
E.tryCatch(
() =>
new HTTPSnippet({
...buildHarRequest(req),
}).convert(codegenInfo.lang, codegenInfo.mode, {
indent: " ",
}),
(e) => e
),
// Only allow string output to pass through, else none
E.chainW(
E.fromPredicate(
(val): val is string => typeof val === "string",
() => "code generator failed" as const
)
),
O.fromEither
)
}

View File

@@ -0,0 +1,17 @@
export type FieldEquals<T, K extends keyof T, Vals extends T[K][]> = {
// eslint-disable-next-line
[_x in K]: Vals[number]
}
export const objectFieldIncludes = <
T,
K extends keyof T,
V extends readonly T[K][]
>(
obj: T,
field: K,
values: V
// eslint-disable-next-line
): obj is T & { [_x in K]: V[number] } => {
return values.includes(obj[field])
}

View File

@@ -1,6 +1,10 @@
import { combineLatest, Observable } from "rxjs"
import { map } from "rxjs/operators"
import { FormDataKeyValue, HoppRESTRequest } from "@hoppscotch/data"
import {
FormDataKeyValue,
HoppRESTReqBody,
HoppRESTRequest,
} from "@hoppscotch/data"
import { parseTemplateString, parseBodyEnvVariables } from "../templating"
import { Environment, getGlobalVariables } from "~/newstore/environments"
@@ -16,6 +20,36 @@ export interface EffectiveHoppRESTRequest extends HoppRESTRequest {
effectiveFinalBody: FormData | string | null
}
// Resolves environment variables in the body
export const resolvesEnvsInBody = (
body: HoppRESTReqBody,
env: Environment
): HoppRESTReqBody => {
if (!body.contentType) return body
if (body.contentType === "multipart/form-data") {
return {
contentType: "multipart/form-data",
body: body.body.map(
(entry) =>
<FormDataKeyValue>{
active: entry.active,
isFile: entry.isFile,
key: parseTemplateString(entry.key, env.variables),
value: entry.isFile
? entry.value
: parseTemplateString(entry.value, env.variables),
}
),
}
} else {
return {
contentType: body.contentType,
body: parseTemplateString(body.body, env.variables),
}
}
}
function getFinalBodyFromRequest(
request: HoppRESTRequest,
env: Environment

View File

@@ -11,6 +11,13 @@ module.exports = {
"^.+\\.js$": "babel-jest",
".*\\.(vue)$": "vue-jest",
},
globals: {
"vue-jest": {
templateCompiler: {
compiler: require("vue-template-babel-compiler"),
},
},
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
snapshotSerializers: ["jest-serializer-vue"],
collectCoverage: true,

View File

@@ -23,12 +23,12 @@ type RESTSession = {
saveContext: HoppRequestSaveContext | null
}
export const defaultRESTRequest: HoppRESTRequest = {
export const getDefaultRESTRequest = (): HoppRESTRequest => ({
v: RESTReqSchemaVersion,
endpoint: "https://echo.hoppscotch.io",
name: "Untitled request",
params: [{ key: "", value: "", active: true }],
headers: [{ key: "", value: "", active: true }],
params: [],
headers: [],
method: "GET",
auth: {
authType: "none",
@@ -40,10 +40,10 @@ export const defaultRESTRequest: HoppRESTRequest = {
contentType: null,
body: null,
},
}
})
const defaultRESTSession: RESTSession = {
request: defaultRESTRequest,
request: getDefaultRESTRequest(),
response: null,
testResults: null,
saveContext: null,
@@ -387,7 +387,7 @@ export function getRESTSaveContext() {
}
export function resetRESTRequest() {
setRESTRequest(defaultRESTRequest)
setRESTRequest(getDefaultRESTRequest())
}
export function setRESTEndpoint(newEndpoint: string) {

View File

@@ -6,6 +6,7 @@ import isEmpty from "lodash/isEmpty"
import * as O from "fp-ts/Option"
import { pipe } from "fp-ts/function"
import { translateToNewRequest } from "@hoppscotch/data"
import { cloneDeep } from "lodash"
import {
settingsStore,
bulkApplySettings,
@@ -284,7 +285,19 @@ function setupRequestPersistence() {
}
restRequest$.subscribe((req) => {
window.localStorage.setItem("restRequest", JSON.stringify(req))
const reqClone = cloneDeep(req)
if (reqClone.body.contentType === "multipart/form-data") {
reqClone.body.body = reqClone.body.body.map((x) => {
if (x.isFile)
return {
...x,
isFile: false,
value: "",
}
else return x
})
}
window.localStorage.setItem("restRequest", JSON.stringify(reqClone))
})
}

View File

@@ -251,6 +251,11 @@ export default {
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {
loaders: {
vue: {
compiler: require("vue-template-babel-compiler"),
},
},
// You can extend webpack config here
extend(config, { isDev, isClient }) {
// Sets webpack's mode to development if `isDev` is true.

View File

@@ -81,6 +81,7 @@
"graphql-language-service-interface": "^2.9.1",
"graphql-language-service-parser": "^1.10.4",
"graphql-tag": "^2.12.6",
"httpsnippet": "^2.0.0",
"io-ts": "^2.2.16",
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
@@ -135,6 +136,8 @@
"@types/codemirror": "^5.60.5",
"@types/cookie": "^0.4.1",
"@types/esprima": "^4.0.3",
"@types/har-format": "^1.2.8",
"@types/httpsnippet": "^1.23.1",
"@types/lodash": "^4.14.178",
"@types/splitpanes": "^2.2.1",
"@types/uuid": "^8.3.3",
@@ -164,6 +167,7 @@
"ts-jest": "^27.1.2",
"typescript": "^4.5.4",
"vue-jest": "^3.0.7",
"vue-template-babel-compiler": "^1.0.8",
"worker-loader": "^3.0.8"
}
}