feat: save api responses (#4382)
Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com>
This commit is contained in:
@@ -29,7 +29,7 @@ import {
|
||||
getCombinedEnvVariables,
|
||||
getFinalEnvsFromPreRequest,
|
||||
} from "./preRequest"
|
||||
import { HoppRESTDocument } from "./rest/document"
|
||||
import { HoppRequestDocument } from "./rest/document"
|
||||
import { HoppRESTResponse } from "./types/HoppRESTResponse"
|
||||
import { HoppTestData, HoppTestResult } from "./types/HoppTestResult"
|
||||
import { getEffectiveRESTRequest } from "./utils/EffectiveURL"
|
||||
@@ -166,7 +166,7 @@ const filterNonEmptyEnvironmentVariables = (
|
||||
}
|
||||
|
||||
export function runRESTRequest$(
|
||||
tab: Ref<HoppTab<HoppRESTDocument>>
|
||||
tab: Ref<HoppTab<HoppRequestDocument>>
|
||||
): [
|
||||
() => void,
|
||||
Promise<
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import { Ref, onBeforeUnmount, onMounted, reactive, watch } from "vue"
|
||||
import { BehaviorSubject } from "rxjs"
|
||||
import { HoppRESTDocument } from "./rest/document"
|
||||
import { HoppRequestDocument } from "./rest/document"
|
||||
import { Environment, HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||
import { HoppGQLSaveContext } from "./graphql/document"
|
||||
@@ -16,7 +16,7 @@ export type HoppAction =
|
||||
| "request.send-cancel" // Send/Cancel a Hoppscotch Request
|
||||
| "request.reset" // Clear request data
|
||||
| "request.share-request" // Share Request
|
||||
| "request.save" // Save to Collections
|
||||
| "request-response.save" // Save Request or Response
|
||||
| "request.save-as" // Save As
|
||||
| "request.rename" // Rename request on REST or GraphQL
|
||||
| "request.method.next" // Select Next Method
|
||||
@@ -64,6 +64,8 @@ export type HoppAction =
|
||||
| "response.schema.toggle" // Toggle response data schema
|
||||
| "response.file.download" // Download response as file
|
||||
| "response.copy" // Copy response to clipboard
|
||||
| "response.save" // Save response
|
||||
| "response.save-as-example" // Save response as example
|
||||
| "modals.login.toggle" // Login to Hoppscotch
|
||||
| "history.clear" // Clear REST History
|
||||
| "user.login" // Login to Hoppscotch
|
||||
@@ -117,12 +119,12 @@ type HoppActionArgsMap = {
|
||||
teamId: string
|
||||
}
|
||||
"rest.request.open": {
|
||||
doc: HoppRESTDocument
|
||||
doc: HoppRequestDocument
|
||||
}
|
||||
"request.save-as":
|
||||
| {
|
||||
requestType: "rest"
|
||||
request: HoppRESTRequest
|
||||
request: HoppRESTRequest | null
|
||||
}
|
||||
| {
|
||||
requestType: "gql"
|
||||
|
||||
@@ -79,7 +79,7 @@ export function resolveSaveContextOnCollectionReorder(
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve save context for affected requests on drop folder from one to another
|
||||
* Resolve save context for affected requests on drop folder from one to another
|
||||
* @param oldFolderPath
|
||||
* @param newFolderPath
|
||||
* @returns
|
||||
@@ -186,6 +186,8 @@ export function updateInheritedPropertiesForAffectedRequests(
|
||||
})
|
||||
|
||||
effectedTabs.map((tab) => {
|
||||
if (!("inheritedProperties" in tab.value.document)) return
|
||||
|
||||
const inheritedParentID =
|
||||
tab.value.document.inheritedProperties?.auth.parentID
|
||||
|
||||
@@ -239,6 +241,13 @@ function resetSaveContextForAffectedRequests(folderPath: string) {
|
||||
for (const tab of tabs) {
|
||||
tab.value.document.saveContext = null
|
||||
tab.value.document.isDirty = true
|
||||
|
||||
if (tab.value.document.type === "request") {
|
||||
// since the request is deleted, we need to remove the saved responses as well
|
||||
tab.value.document.request.responses = {}
|
||||
}
|
||||
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,6 +274,11 @@ export async function resetTeamRequestsContext() {
|
||||
if (E.isRight(data) && data.right.request === null) {
|
||||
tab.value.document.saveContext = null
|
||||
tab.value.document.isDirty = true
|
||||
|
||||
if (tab.value.document.type === "request") {
|
||||
// since the request is deleted, we need to remove the saved responses as well
|
||||
tab.value.document.request.responses = {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
HoppCollection,
|
||||
HoppGQLRequest,
|
||||
HoppRESTRequest,
|
||||
RESTReqSchemaVersion,
|
||||
} from "@hoppscotch/data"
|
||||
import { getAffectedIndexes } from "./affectedIndex"
|
||||
import { RESTTabService } from "~/services/tab/rest"
|
||||
@@ -67,7 +68,7 @@ export function getRequestsByPath(
|
||||
|
||||
if (pathArray.length === 1) {
|
||||
const latestVersionedRequests = currentCollection.requests.filter(
|
||||
(req): req is HoppRESTRequest => req.v === "3"
|
||||
(req): req is HoppRESTRequest => req.v === RESTReqSchemaVersion
|
||||
)
|
||||
|
||||
return latestVersionedRequests
|
||||
@@ -78,7 +79,7 @@ export function getRequestsByPath(
|
||||
}
|
||||
|
||||
const latestVersionedRequests = currentCollection.requests.filter(
|
||||
(req): req is HoppRESTRequest => req.v === "3"
|
||||
(req): req is HoppRESTRequest => req.v === RESTReqSchemaVersion
|
||||
)
|
||||
|
||||
return latestVersionedRequests
|
||||
|
||||
@@ -39,6 +39,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -149,6 +150,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -167,6 +169,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -192,6 +195,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -223,6 +227,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -254,6 +259,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -285,6 +291,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -309,6 +316,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -331,6 +339,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -355,6 +364,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -389,6 +399,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -459,6 +470,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -488,6 +500,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -552,6 +565,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -600,6 +614,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -650,6 +665,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -665,6 +681,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -690,6 +707,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -708,6 +726,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -733,6 +752,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -751,6 +771,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -776,6 +797,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -799,6 +821,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -820,6 +843,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -842,6 +866,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -863,6 +888,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -895,6 +921,7 @@ const samples = [
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
{
|
||||
@@ -928,6 +955,7 @@ data2: {"type":"test2","typeId":"123"}`,
|
||||
preRequestScript: "",
|
||||
testScript: "",
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
}),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -202,5 +202,6 @@ export const parseCurlCommand = (curlCommand: string) => {
|
||||
auth,
|
||||
body: finalBody,
|
||||
requestVariables: defaultRESTReq.requestVariables,
|
||||
responses: {},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -265,8 +265,13 @@ export class HoppEnvironmentPlugin {
|
||||
const aggregateEnvs = getAggregateEnvs()
|
||||
const currentTab = restTabs.currentActiveTab.value
|
||||
|
||||
const currentTabRequest =
|
||||
currentTab.document.type === "request"
|
||||
? currentTab.document.request
|
||||
: currentTab.document.response.originalRequest
|
||||
|
||||
watch(
|
||||
currentTab.document.request,
|
||||
currentTabRequest,
|
||||
(reqVariables) => {
|
||||
this.envs = [
|
||||
...reqVariables.requestVariables.map(({ key, value }) => ({
|
||||
@@ -290,14 +295,12 @@ export class HoppEnvironmentPlugin {
|
||||
|
||||
subscribeToStream(aggregateEnvsWithSecrets$, (envs) => {
|
||||
this.envs = [
|
||||
...currentTab.document.request.requestVariables.map(
|
||||
({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})
|
||||
),
|
||||
...currentTabRequest.requestVariables.map(({ key, value }) => ({
|
||||
key,
|
||||
value,
|
||||
sourceEnv: "RequestVariable",
|
||||
secret: false,
|
||||
})),
|
||||
...envs,
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function (responseStatus) {
|
||||
export default function (responseStatus: number) {
|
||||
if (responseStatus >= 100 && responseStatus < 200)
|
||||
return {
|
||||
name: "informational",
|
||||
|
||||
@@ -230,6 +230,9 @@ const getHoppRequest = (req: InsomniaRequestResource): HoppRESTRequest =>
|
||||
testScript: "",
|
||||
|
||||
requestVariables: getHoppReqVariables(req),
|
||||
|
||||
//insomnia doesn't have saved response
|
||||
responses: {},
|
||||
})
|
||||
|
||||
const getHoppFolder = (
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
makeCollection,
|
||||
HoppRESTRequestVariable,
|
||||
HoppRESTRequest,
|
||||
HoppRESTRequestResponses,
|
||||
HoppRESTResponseOriginalRequest,
|
||||
} from "@hoppscotch/data"
|
||||
import { pipe, flow } from "fp-ts/function"
|
||||
import * as A from "fp-ts/Array"
|
||||
@@ -26,7 +28,8 @@ import * as O from "fp-ts/Option"
|
||||
import * as TE from "fp-ts/TaskEither"
|
||||
import * as RA from "fp-ts/ReadonlyArray"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { cloneDeep } from "lodash-es"
|
||||
import { cloneDeep, isNumber } from "lodash-es"
|
||||
import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes"
|
||||
|
||||
export const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const
|
||||
|
||||
@@ -107,6 +110,105 @@ const parseOpenAPIVariables = (
|
||||
)
|
||||
)
|
||||
|
||||
const parseOpenAPIV3Responses = (
|
||||
op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject,
|
||||
originalRequest: HoppRESTResponseOriginalRequest
|
||||
): HoppRESTRequestResponses => {
|
||||
const responses = op.responses
|
||||
if (!responses) return {}
|
||||
|
||||
const res: HoppRESTRequestResponses = {}
|
||||
|
||||
for (const [key, value] of Object.entries(responses)) {
|
||||
const response = value as
|
||||
| OpenAPIV3.ResponseObject
|
||||
| OpenAPIV31.ResponseObject
|
||||
|
||||
// add support for schema key as well
|
||||
const contentType = Object.keys(response.content ?? {})[0]
|
||||
const body = response.content?.[contentType]
|
||||
|
||||
const name = response.description ?? key
|
||||
|
||||
const code = isNumber(key) ? Number(key) : 200
|
||||
|
||||
const status = getStatusCodeReasonPhrase(code)
|
||||
|
||||
const headers: HoppRESTHeader[] = [
|
||||
{
|
||||
key: "content-type",
|
||||
value: contentType ?? "application/json",
|
||||
description: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
res[name] = {
|
||||
name,
|
||||
status,
|
||||
code,
|
||||
headers,
|
||||
body: JSON.stringify(body ?? ""),
|
||||
originalRequest,
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const parseOpenAPIV2Responses = (
|
||||
op: OpenAPIV2.OperationObject,
|
||||
originalRequest: HoppRESTResponseOriginalRequest
|
||||
): HoppRESTRequestResponses => {
|
||||
const responses = op.responses
|
||||
|
||||
if (!responses) return {}
|
||||
|
||||
const res: HoppRESTRequestResponses = {}
|
||||
|
||||
for (const [key, value] of Object.entries(responses)) {
|
||||
const response = value as OpenAPIV2.ResponseObject
|
||||
|
||||
// add support for schema key as well
|
||||
const contentType = Object.keys(response.examples ?? {})[0]
|
||||
const body = response.examples?.[contentType]
|
||||
|
||||
const name = response.description ?? key
|
||||
|
||||
const code = isNumber(Number(key)) ? Number(key) : 200
|
||||
const status = getStatusCodeReasonPhrase(code)
|
||||
|
||||
const headers: HoppRESTHeader[] = [
|
||||
{
|
||||
key: "content-type",
|
||||
value: contentType ?? "application/json",
|
||||
description: "",
|
||||
active: true,
|
||||
},
|
||||
]
|
||||
|
||||
res[name] = {
|
||||
name,
|
||||
status,
|
||||
code,
|
||||
headers,
|
||||
body: body ?? "",
|
||||
originalRequest,
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
const parseOpenAPIResponses = (
|
||||
doc: OpenAPI.Document,
|
||||
op: OpenAPIOperationType,
|
||||
originalRequest: HoppRESTResponseOriginalRequest
|
||||
): HoppRESTRequestResponses =>
|
||||
isOpenAPIV3Operation(doc, op)
|
||||
? parseOpenAPIV3Responses(op, originalRequest)
|
||||
: parseOpenAPIV2Responses(op, originalRequest)
|
||||
|
||||
const parseOpenAPIHeaders = (params: OpenAPIParamsType[]): HoppRESTHeader[] =>
|
||||
pipe(
|
||||
params,
|
||||
@@ -657,6 +759,25 @@ const convertPathToHoppReqs = (
|
||||
requestVariables: parseOpenAPIVariables(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
|
||||
responses: parseOpenAPIResponses(doc, info, {
|
||||
name: info.operationId ?? info.summary ?? "Untitled Request",
|
||||
auth: parseOpenAPIAuth(doc, info),
|
||||
body: parseOpenAPIBody(doc, info),
|
||||
endpoint,
|
||||
// We don't need to worry about reference types as the Dereferencing pass should remove them
|
||||
params: parseOpenAPIParams(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
headers: parseOpenAPIHeaders(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
method: method.toUpperCase(),
|
||||
requestVariables: parseOpenAPIVariables(
|
||||
(info.parameters as OpenAPIParamsType[] | undefined) ?? []
|
||||
),
|
||||
v: "1",
|
||||
}),
|
||||
}),
|
||||
metadata: {
|
||||
tags: info.tags ?? [],
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
import { stringArrayJoin } from "~/helpers/functional/array"
|
||||
import { PMRawLanguage } from "~/types/pm-coll-exts"
|
||||
import { IMPORTER_INVALID_FILE_FORMAT } from "."
|
||||
import { HoppRESTRequestResponses } from "@hoppscotch/data/dist/rest/v/8"
|
||||
|
||||
const safeParseJSON = (jsonStr: string) => O.tryCatch(() => JSON.parse(jsonStr))
|
||||
|
||||
@@ -77,9 +78,12 @@ const parseDescription = (descField?: string | DescriptionDefinition) => {
|
||||
return descField.content
|
||||
}
|
||||
|
||||
const getHoppReqHeaders = (item: Item): HoppRESTHeader[] =>
|
||||
pipe(
|
||||
item.request.headers.all(),
|
||||
const getHoppReqHeaders = (
|
||||
headers: Item["request"]["headers"] | null
|
||||
): HoppRESTHeader[] => {
|
||||
if (!headers) return []
|
||||
return pipe(
|
||||
headers.all(),
|
||||
A.map((header) => {
|
||||
const description = parseDescription(header.description)
|
||||
|
||||
@@ -91,43 +95,94 @@ const getHoppReqHeaders = (item: Item): HoppRESTHeader[] =>
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const getHoppReqParams = (item: Item): HoppRESTParam[] => {
|
||||
return pipe(
|
||||
item.request.url.query.all(),
|
||||
A.filter(
|
||||
(param): param is QueryParam & { key: string } =>
|
||||
param.key !== undefined && param.key !== null && param.key.length > 0
|
||||
),
|
||||
A.map((param) => {
|
||||
const description = parseDescription(param.description)
|
||||
|
||||
return <HoppRESTHeader>{
|
||||
key: replacePMVarTemplating(param.key),
|
||||
value: replacePMVarTemplating(param.value ?? ""),
|
||||
active: !param.disabled,
|
||||
description,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
const getHoppReqVariables = (item: Item) => {
|
||||
return pipe(
|
||||
item.request.url.variables.all(),
|
||||
A.filter(
|
||||
(variable): variable is Variable =>
|
||||
variable.key !== undefined &&
|
||||
variable.key !== null &&
|
||||
variable.key.length > 0
|
||||
),
|
||||
A.map((variable) => {
|
||||
return <HoppRESTRequestVariable>{
|
||||
key: replacePMVarTemplating(variable.key ?? ""),
|
||||
value: replacePMVarTemplating(variable.value ?? ""),
|
||||
active: !variable.disabled,
|
||||
}
|
||||
})
|
||||
const getHoppReqParams = (
|
||||
query: Item["request"]["url"]["query"] | null
|
||||
): HoppRESTParam[] => {
|
||||
{
|
||||
if (!query) return []
|
||||
return pipe(
|
||||
query.all(),
|
||||
A.filter(
|
||||
(param): param is QueryParam & { key: string } =>
|
||||
param.key !== undefined && param.key !== null && param.key.length > 0
|
||||
),
|
||||
A.map((param) => {
|
||||
const description = parseDescription(param.description)
|
||||
|
||||
return <HoppRESTHeader>{
|
||||
key: replacePMVarTemplating(param.key),
|
||||
value: replacePMVarTemplating(param.value ?? ""),
|
||||
active: !param.disabled,
|
||||
description,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getHoppReqVariables = (
|
||||
variables: Item["request"]["url"]["variables"] | null
|
||||
): HoppRESTRequestVariable[] => {
|
||||
{
|
||||
if (!variables) return []
|
||||
return pipe(
|
||||
variables.all(),
|
||||
A.filter(
|
||||
(variable): variable is Variable =>
|
||||
variable.key !== undefined &&
|
||||
variable.key !== null &&
|
||||
variable.key.length > 0
|
||||
),
|
||||
A.map((variable) => {
|
||||
return <HoppRESTRequestVariable>{
|
||||
key: replacePMVarTemplating(variable.key ?? ""),
|
||||
value: replacePMVarTemplating(variable.value ?? ""),
|
||||
active: !variable.disabled,
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getHoppResponses = (
|
||||
responses: Item["responses"]
|
||||
): HoppRESTRequestResponses => {
|
||||
return Object.fromEntries(
|
||||
pipe(
|
||||
responses.all(),
|
||||
A.map((response) => {
|
||||
const res = {
|
||||
name: response.name,
|
||||
status: response.status,
|
||||
body: response.body ?? "",
|
||||
headers: getHoppReqHeaders(response.headers),
|
||||
code: response.code,
|
||||
originalRequest: {
|
||||
auth: getHoppReqAuth(response.originalRequest?.auth),
|
||||
body: getHoppReqBody({
|
||||
body: response.originalRequest?.body,
|
||||
headers: response.originalRequest?.headers ?? null,
|
||||
}) ?? { contentType: null, body: null },
|
||||
endpoint: getHoppReqURL(response.originalRequest?.url ?? null),
|
||||
headers: getHoppReqHeaders(
|
||||
response.originalRequest?.headers ?? null
|
||||
),
|
||||
method: response.originalRequest?.method ?? "",
|
||||
name: response.originalRequest?.name ?? response.name,
|
||||
params: getHoppReqParams(
|
||||
response.originalRequest?.url.query ?? null
|
||||
),
|
||||
requestVariables: getHoppReqVariables(
|
||||
response.originalRequest?.url.variables ?? null
|
||||
),
|
||||
v: "1" as const,
|
||||
},
|
||||
}
|
||||
return [response.name, res]
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,11 +197,11 @@ type PMRequestAuthDef<
|
||||
const getVariableValue = (defs: VariableDefinition[], key: string) =>
|
||||
defs.find((param) => param.key === key)?.value as string | undefined
|
||||
|
||||
const getHoppReqAuth = (item: Item): HoppRESTAuth => {
|
||||
if (!item.request.auth) return { authType: "none", authActive: true }
|
||||
const getHoppReqAuth = (hoppAuth: Item["request"]["auth"]): HoppRESTAuth => {
|
||||
if (!hoppAuth) return { authType: "none", authActive: true }
|
||||
|
||||
// Cast to the type for more stricter checking down the line
|
||||
const auth = item.request.auth as unknown as PMRequestAuthDef
|
||||
const auth = hoppAuth as unknown as PMRequestAuthDef
|
||||
|
||||
if (auth.type === "basic") {
|
||||
return {
|
||||
@@ -217,10 +272,14 @@ const getHoppReqAuth = (item: Item): HoppRESTAuth => {
|
||||
return { authType: "none", authActive: true }
|
||||
}
|
||||
|
||||
const getHoppReqBody = (item: Item): HoppRESTReqBody => {
|
||||
if (!item.request.body) return { contentType: null, body: null }
|
||||
|
||||
const body = item.request.body
|
||||
const getHoppReqBody = ({
|
||||
body,
|
||||
headers,
|
||||
}: {
|
||||
body: Item["request"]["body"] | null
|
||||
headers: Item["request"]["headers"] | null
|
||||
}): HoppRESTReqBody => {
|
||||
if (!body) return { contentType: null, body: null }
|
||||
|
||||
if (body.mode === "formdata") {
|
||||
return {
|
||||
@@ -262,7 +321,7 @@ const getHoppReqBody = (item: Item): HoppRESTReqBody => {
|
||||
O.bind("contentType", () =>
|
||||
pipe(
|
||||
// Get the info from the content-type header
|
||||
getHoppReqHeaders(item),
|
||||
getHoppReqHeaders(headers),
|
||||
A.findFirst(({ key }) => key.toLowerCase() === "content-type"),
|
||||
O.map((x) => x.value),
|
||||
|
||||
@@ -315,23 +374,29 @@ const getHoppReqBody = (item: Item): HoppRESTReqBody => {
|
||||
return { contentType: null, body: null }
|
||||
}
|
||||
|
||||
const getHoppReqURL = (item: Item): string =>
|
||||
pipe(
|
||||
item.request.url.toString(false),
|
||||
const getHoppReqURL = (url: Item["request"]["url"] | null): string => {
|
||||
if (!url) return ""
|
||||
return pipe(
|
||||
url.toString(false),
|
||||
S.replace(/\?.+/g, ""),
|
||||
replacePMVarTemplating
|
||||
)
|
||||
}
|
||||
|
||||
const getHoppRequest = (item: Item): HoppRESTRequest => {
|
||||
return makeRESTRequest({
|
||||
name: item.name,
|
||||
endpoint: getHoppReqURL(item),
|
||||
endpoint: getHoppReqURL(item.request.url),
|
||||
method: item.request.method.toUpperCase(),
|
||||
headers: getHoppReqHeaders(item),
|
||||
params: getHoppReqParams(item),
|
||||
auth: getHoppReqAuth(item),
|
||||
body: getHoppReqBody(item),
|
||||
requestVariables: getHoppReqVariables(item),
|
||||
headers: getHoppReqHeaders(item.request.headers),
|
||||
params: getHoppReqParams(item.request.url.query),
|
||||
auth: getHoppReqAuth(item.request.auth),
|
||||
body: getHoppReqBody({
|
||||
body: item.request.body,
|
||||
headers: item.request.headers,
|
||||
}),
|
||||
requestVariables: getHoppReqVariables(item.request.url.variables),
|
||||
responses: getHoppResponses(item.responses),
|
||||
|
||||
// TODO: Decide about this
|
||||
preRequestScript: "",
|
||||
|
||||
@@ -45,7 +45,7 @@ export const bindings: {
|
||||
"ctrl-enter": "request.send-cancel",
|
||||
"ctrl-i": "request.reset",
|
||||
"ctrl-u": "request.share-request",
|
||||
"ctrl-s": "request.save",
|
||||
"ctrl-s": "request-response.save",
|
||||
"ctrl-shift-s": "request.save-as",
|
||||
"alt-up": "request.method.next",
|
||||
"alt-down": "request.method.prev",
|
||||
@@ -67,6 +67,7 @@ export const bindings: {
|
||||
"ctrl-shift-p": "response.preview.toggle",
|
||||
"ctrl-j": "response.file.download",
|
||||
"ctrl-.": "response.copy",
|
||||
"ctrl-e": "response.save-as-example",
|
||||
"ctrl-shift-l": "editor.format",
|
||||
}
|
||||
|
||||
|
||||
@@ -18,4 +18,5 @@ export const getDefaultRESTRequest = (): HoppRESTRequest => ({
|
||||
body: null,
|
||||
},
|
||||
requestVariables: [],
|
||||
responses: {},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { HoppRESTRequest, HoppRESTRequestResponse } from "@hoppscotch/data"
|
||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||
import { HoppTestResult } from "../types/HoppTestResult"
|
||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||
@@ -18,6 +18,10 @@ export type HoppRESTSaveContext =
|
||||
* Index to the request
|
||||
*/
|
||||
requestIndex: number
|
||||
/**
|
||||
* ID of the example response
|
||||
*/
|
||||
exampleID?: string
|
||||
}
|
||||
| {
|
||||
/**
|
||||
@@ -36,13 +40,22 @@ export type HoppRESTSaveContext =
|
||||
* ID of the collection loaded
|
||||
*/
|
||||
collectionID?: string
|
||||
/**
|
||||
* ID of the example response
|
||||
*/
|
||||
exampleID?: string
|
||||
}
|
||||
| null
|
||||
|
||||
/**
|
||||
* Defines a live 'document' (something that is open and being edited) in the app
|
||||
*/
|
||||
export type HoppRESTDocument = {
|
||||
export type HoppRequestDocument = {
|
||||
/**
|
||||
* The type of the document
|
||||
*/
|
||||
type: "request"
|
||||
|
||||
/**
|
||||
* The request as it is in the document
|
||||
*/
|
||||
@@ -93,3 +106,32 @@ export type HoppRESTDocument = {
|
||||
*/
|
||||
cancelFunction?: () => void
|
||||
}
|
||||
|
||||
export type HoppSavedExampleDocument = {
|
||||
/**
|
||||
* The type of the document
|
||||
*/
|
||||
type: "example-response"
|
||||
|
||||
/**
|
||||
* The response as it is in the document
|
||||
*/
|
||||
response: HoppRESTRequestResponse
|
||||
|
||||
/**
|
||||
* Info about where this response should be saved.
|
||||
* This contains where the response is originated from basically.
|
||||
*/
|
||||
saveContext?: HoppRESTSaveContext
|
||||
|
||||
/**
|
||||
* Whether the response has any unsaved changes
|
||||
* (atleast as far as we can say)
|
||||
*/
|
||||
isDirty: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a live 'document' (something that is open and being edited) in the app
|
||||
*/
|
||||
export type HoppTabDocument = HoppSavedExampleDocument | HoppRequestDocument
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { pipe } from "fp-ts/function"
|
||||
import * as O from "fp-ts/Option"
|
||||
import * as RR from "fp-ts/ReadonlyRecord"
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
|
||||
export const REQUEST_METHOD_LABEL_COLORS = {
|
||||
get: "var(--method-get-color)",
|
||||
@@ -16,13 +15,13 @@ export const REQUEST_METHOD_LABEL_COLORS = {
|
||||
|
||||
/**
|
||||
* Returns the label color tailwind class for a request
|
||||
* @param request The HoppRESTRequest object to get the value for
|
||||
* @param method The HTTP VERB of the request
|
||||
* @returns The class value for the given HTTP VERB, if not, a generic verb class
|
||||
*/
|
||||
export function getMethodLabelColorClassOf(request: HoppRESTRequest) {
|
||||
export function getMethodLabelColorClassOf(method: string) {
|
||||
return pipe(
|
||||
REQUEST_METHOD_LABEL_COLORS,
|
||||
RR.lookup(request.method.toLowerCase()),
|
||||
RR.lookup(method.toLowerCase()),
|
||||
O.getOrElseW(() => REQUEST_METHOD_LABEL_COLORS.default)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||
import { runGQLQuery } from "../backend/GQLClient"
|
||||
import {
|
||||
GetCollectionChildrenDocument,
|
||||
GetCollectionRequestsDocument,
|
||||
GetSingleRequestDocument,
|
||||
} from "../backend/graphql"
|
||||
@@ -30,3 +31,11 @@ export const getSingleRequest = (requestID: string) =>
|
||||
requestID,
|
||||
},
|
||||
})
|
||||
|
||||
export const getCollectionChildCollections = (collectionID: string) =>
|
||||
runGQLQuery({
|
||||
query: GetCollectionChildrenDocument,
|
||||
variables: {
|
||||
collectionID,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
getSingleRequest,
|
||||
getCollectionChildRequests,
|
||||
TeamRequest,
|
||||
getCollectionChildCollections,
|
||||
} from "./TeamRequest"
|
||||
|
||||
type CollectionSearchMeta = {
|
||||
|
||||
@@ -99,3 +99,38 @@ export function getStatusCodeReasonPhrase(
|
||||
|
||||
return statusCodes[code] ?? "Unknown"
|
||||
}
|
||||
|
||||
// return the status code like
|
||||
// code • status
|
||||
export const getFullStatusCodePhrase = () => {
|
||||
return Object.keys(statusCodes).map((code) => {
|
||||
return `${code} • ${statusCodes[code]}`
|
||||
})
|
||||
}
|
||||
|
||||
// return all status codes and their phrases
|
||||
// like code • phrase
|
||||
export const getStatusCodePhrase = (
|
||||
code: number | undefined,
|
||||
statusText: string
|
||||
) => {
|
||||
if (!code) return statusText
|
||||
return `${code} • ${getStatusCodeReasonPhrase(code, statusText)}`
|
||||
}
|
||||
|
||||
// return the status code and status
|
||||
// like { code, status }
|
||||
export const getStatusAndCode = (status: string) => {
|
||||
const statusAndCode = status.split(" • ")
|
||||
return {
|
||||
code: Number(statusAndCode[0]),
|
||||
status: statusAndCode[1],
|
||||
}
|
||||
}
|
||||
|
||||
// check if the status code is valid
|
||||
export const isValidStatusCode = (status: string) => {
|
||||
const allPhrases = getFullStatusCodePhrase()
|
||||
|
||||
return allPhrases.includes(status)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user