From 2bd3f36bfaf15f6833ca62962804a9cd828efc2b Mon Sep 17 00:00:00 2001 From: Nivedin <53208152+nivedin@users.noreply.github.com> Date: Fri, 13 Jun 2025 09:55:05 +0530 Subject: [PATCH] fix: schema error and test updation flow (#5124) Co-authored-by: jamesgeorge007 <25279263+jamesgeorge007@users.noreply.github.com> --- .../src/components/http/Response.vue | 26 +-- .../src/components/http/TestResultEnv.vue | 4 +- .../src/helpers/RequestRunner.ts | 39 ++-- .../helpers/fixBrokenEnvironmentVersion.ts | 27 +++ .../src/helpers/fixBrokenRequestVersion.ts | 54 ++++++ .../helpers/import-export/import/openapi.ts | 41 +++-- .../helpers/import-export/import/postman.ts | 9 +- .../helpers/teams/TeamEnvironmentAdapter.ts | 17 +- .../src/helpers/utils/statusCodes.ts | 2 +- .../hoppscotch-common/src/kernel/store.ts | 9 +- .../std/kernel-interceptors/agent/store.ts | 17 +- .../kernel-interceptors/extension/store.ts | 13 +- .../std/kernel-interceptors/native/store.ts | 13 +- .../std/kernel-interceptors/proxy/store.ts | 13 +- .../persistence/__tests__/index.spec.ts | 2 +- .../src/services/persistence/index.ts | 17 +- .../persistence/validation-schemas/index.ts | 20 +-- .../hoppscotch-data/src/collection/v/4.ts | 2 +- .../hoppscotch-data/src/collection/v/6.ts | 2 +- .../hoppscotch-data/src/collection/v/7.ts | 2 +- .../hoppscotch-data/src/collection/v/8.ts | 2 +- .../hoppscotch-data/src/environment/index.ts | 2 +- .../src/global-environment/v/2.ts | 5 +- packages/hoppscotch-data/src/graphql/v/8.ts | 4 +- packages/hoppscotch-data/src/index.ts | 1 + .../src/rest-request-response/index.ts | 50 ++++++ .../original-request/index.ts | 39 ++++ .../original-request/v/1.ts | 24 +++ .../original-request/v/2.ts | 20 +++ .../original-request/v/3.ts | 38 ++++ .../original-request/v/4.ts | 20 +++ .../original-request/v/5.ts | 20 +++ .../src/rest-request-response/v/0.ts | 35 ++++ packages/hoppscotch-data/src/rest/index.ts | 43 +++-- .../src/rest/v/{10.ts => 10/body.ts} | 19 +- .../hoppscotch-data/src/rest/v/10/index.ts | 20 +++ packages/hoppscotch-data/src/rest/v/11.ts | 146 --------------- .../hoppscotch-data/src/rest/v/11/auth.ts | 52 ++++++ .../hoppscotch-data/src/rest/v/11/index.ts | 37 ++++ packages/hoppscotch-data/src/rest/v/12.ts | 128 -------------- .../hoppscotch-data/src/rest/v/12/auth.ts | 67 +++++++ .../hoppscotch-data/src/rest/v/12/index.ts | 21 +++ packages/hoppscotch-data/src/rest/v/13.ts | 126 ------------- .../hoppscotch-data/src/rest/v/13/auth.ts | 64 +++++++ .../hoppscotch-data/src/rest/v/13/index.ts | 22 +++ packages/hoppscotch-data/src/rest/v/14.ts | 19 ++ packages/hoppscotch-data/src/rest/v/8.ts | 166 ------------------ packages/hoppscotch-data/src/rest/v/8/auth.ts | 75 ++++++++ .../hoppscotch-data/src/rest/v/8/index.ts | 27 +++ packages/hoppscotch-data/src/rest/v/9.ts | 154 ---------------- packages/hoppscotch-data/src/rest/v/9/body.ts | 68 +++++++ .../hoppscotch-data/src/rest/v/9/index.ts | 22 +++ .../environments/environments.platform.ts | 29 ++- .../platform/environments/desktop/index.ts | 7 +- .../src/platform/environments/web/index.ts | 30 ++-- 55 files changed, 1013 insertions(+), 918 deletions(-) create mode 100644 packages/hoppscotch-common/src/helpers/fixBrokenEnvironmentVersion.ts create mode 100644 packages/hoppscotch-common/src/helpers/fixBrokenRequestVersion.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/index.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/index.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/v/1.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/v/2.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/v/3.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/v/4.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/original-request/v/5.ts create mode 100644 packages/hoppscotch-data/src/rest-request-response/v/0.ts rename packages/hoppscotch-data/src/rest/v/{10.ts => 10/body.ts} (76%) create mode 100644 packages/hoppscotch-data/src/rest/v/10/index.ts delete mode 100644 packages/hoppscotch-data/src/rest/v/11.ts create mode 100644 packages/hoppscotch-data/src/rest/v/11/auth.ts create mode 100644 packages/hoppscotch-data/src/rest/v/11/index.ts delete mode 100644 packages/hoppscotch-data/src/rest/v/12.ts create mode 100644 packages/hoppscotch-data/src/rest/v/12/auth.ts create mode 100644 packages/hoppscotch-data/src/rest/v/12/index.ts delete mode 100644 packages/hoppscotch-data/src/rest/v/13.ts create mode 100644 packages/hoppscotch-data/src/rest/v/13/auth.ts create mode 100644 packages/hoppscotch-data/src/rest/v/13/index.ts create mode 100644 packages/hoppscotch-data/src/rest/v/14.ts delete mode 100644 packages/hoppscotch-data/src/rest/v/8.ts create mode 100644 packages/hoppscotch-data/src/rest/v/8/auth.ts create mode 100644 packages/hoppscotch-data/src/rest/v/8/index.ts delete mode 100644 packages/hoppscotch-data/src/rest/v/9.ts create mode 100644 packages/hoppscotch-data/src/rest/v/9/body.ts create mode 100644 packages/hoppscotch-data/src/rest/v/9/index.ts diff --git a/packages/hoppscotch-common/src/components/http/Response.vue b/packages/hoppscotch-common/src/components/http/Response.vue index c3218c2f..95289a1b 100644 --- a/packages/hoppscotch-common/src/components/http/Response.vue +++ b/packages/hoppscotch-common/src/components/http/Response.vue @@ -24,9 +24,9 @@ import { HoppRequestDocument } from "~/helpers/rest/document" import { useResponseBody } from "@composables/lens-actions" import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes" import { - HoppRESTResponseOriginalRequest, HoppRESTRequestResponse, - RESTResOriginalReqSchemaVersion, + HoppRESTResponseOriginalRequest, + makeHoppRESTResponseOriginalRequest, } from "@hoppscotch/data" import { editRESTRequest } from "~/newstore/collections" import { useToast } from "@composables/toast" @@ -94,17 +94,17 @@ const onSaveAsExample = () => { requestVariables, } = response.req - const originalRequest: HoppRESTResponseOriginalRequest = { - v: RESTResOriginalReqSchemaVersion, - method, - endpoint, - headers, - body, - auth, - params, - name, - requestVariables, - } + const originalRequest: HoppRESTResponseOriginalRequest = + makeHoppRESTResponseOriginalRequest({ + method, + endpoint, + headers, + body, + auth, + params, + name, + requestVariables, + }) const resName = responseName.value.trim() diff --git a/packages/hoppscotch-common/src/components/http/TestResultEnv.vue b/packages/hoppscotch-common/src/components/http/TestResultEnv.vue index e34de824..84d529e7 100644 --- a/packages/hoppscotch-common/src/components/http/TestResultEnv.vue +++ b/packages/hoppscotch-common/src/components/http/TestResultEnv.vue @@ -14,7 +14,7 @@ - {{ env.value }} + {{ env.currentValue }} - const updatedRunResult = updateEnvsAfterTestScript(combinedResult) - - tab.value.document.testResults = - translateToSandboxTestResults(updatedRunResult) + tab.value.document.testResults = translateToSandboxTestResults( + combinedResult.right + ) + updateEnvsAfterTestScript(combinedResult) } else { tab.value.document.testResults = { description: "", @@ -492,26 +492,6 @@ export function runRESTRequest$( } function updateEnvsAfterTestScript(runResult: E.Right) { - const updatedGlobalEnvVariables = updateEnvironments( - // @ts-expect-error Typescript can't figure out this inference for some reason - cloneDeep(runResult.right.envs.global), - "global" - ) - - const updatedSelectedEnvVariables = updateEnvironments( - // @ts-expect-error Typescript can't figure out this inference for some reason - cloneDeep(runResult.right.envs.selected), - "selected" - ) - - const updatedRunResult = { - ...runResult.right, - envs: { - global: updatedGlobalEnvVariables, - selected: updatedSelectedEnvVariables, - }, - } - const globalEnvVariables = updateEnvironments( // @ts-expect-error Typescript can't figure out this inference for some reason runResult.right.envs.global, @@ -522,6 +502,11 @@ function updateEnvsAfterTestScript(runResult: E.Right) { v: 2, variables: globalEnvVariables, }) + updateEnvironments( + // @ts-expect-error Typescript can't figure out this inference for some reason + cloneDeep(runResult.right.envs.selected), + "selected" + ) if (environmentsStore.value.selectedEnvironmentIndex.type === "MY_ENV") { const env = getEnvironment({ type: "MY_ENV", @@ -531,7 +516,7 @@ function updateEnvsAfterTestScript(runResult: E.Right) { name: env.name, v: 2, id: "id" in env ? env.id : "", - variables: updatedRunResult.envs.selected, + variables: runResult.right.envs.selected, }) } else if ( environmentsStore.value.selectedEnvironmentIndex.type === "TEAM_ENV" @@ -541,14 +526,12 @@ function updateEnvsAfterTestScript(runResult: E.Right) { }) pipe( updateTeamEnvironment( - JSON.stringify(updatedRunResult.envs.selected), + JSON.stringify(runResult.right.envs.selected), environmentsStore.value.selectedEnvironmentIndex.teamEnvID, env.name ) )() } - - return updatedRunResult } /** diff --git a/packages/hoppscotch-common/src/helpers/fixBrokenEnvironmentVersion.ts b/packages/hoppscotch-common/src/helpers/fixBrokenEnvironmentVersion.ts new file mode 100644 index 00000000..76090d92 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/fixBrokenEnvironmentVersion.ts @@ -0,0 +1,27 @@ +import { + Environment, + translateToNewEnvironmentVariables, +} from "@hoppscotch/data" + +/** + * Fixes broken environment versions in the given environments. + * This function ensures that all environment variables are translated + * to the new format, which is necessary for compatibility with the latest + * version of the application. + * + * Some environments may have been created with an unsupported + * variable format, which can lead to issues when trying to access or manipulate those environments. + * + * + * @param envs - The array of environments to fix. + * @returns The fixed array of environments with updated variable formats. + */ +export const fixBrokenEnvironmentVersion = (envs: Environment[]) => { + if (!Array.isArray(envs)) { + return envs + } + return envs.map((env) => ({ + ...env, + variables: (env.variables ?? []).map(translateToNewEnvironmentVariables), + })) +} diff --git a/packages/hoppscotch-common/src/helpers/fixBrokenRequestVersion.ts b/packages/hoppscotch-common/src/helpers/fixBrokenRequestVersion.ts new file mode 100644 index 00000000..c9dee8f2 --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/fixBrokenRequestVersion.ts @@ -0,0 +1,54 @@ +import { + getDefaultRESTRequest, + safelyExtractRESTRequest, +} from "@hoppscotch/data" +import { z } from "zod" +import { REST_TAB_STATE_SCHEMA } from "~/services/persistence/validation-schemas" + +type HoppRESTab = z.infer + +/** + * Fixes broken request versions in the given REST tab documents. + * This function ensures that all requests and test runners have valid + * request data, defaulting to the default REST request structure if necessary. + * + * There were requests in the REST tab that had an invalid version + * structure, with response and parent request which could lead to issues when trying to access or + * manipulate those requests. This function iterates through the + * ordered documents of the REST tab and checks each request. + * + * @param docs - The ordered documents of the REST tab to fix. + * @returns The fixed ordered documents with valid request structures. + */ +export const fixBrokenRequestVersion = ( + docs: HoppRESTab["orderedDocs"] +): HoppRESTab["orderedDocs"] => { + return docs.map((x: HoppRESTab["orderedDocs"][number]) => { + if (x.doc.type === "request") { + const req = safelyExtractRESTRequest( + x.doc.request, + getDefaultRESTRequest() + ) + if (req) { + x.doc.request = req + } + } + + if (x.doc.type === "test-runner") { + x.doc.request = safelyExtractRESTRequest( + x.doc.request, + getDefaultRESTRequest() + ) + + if (x.doc.resultCollection) { + x.doc.resultCollection.requests = x.doc.resultCollection?.requests.map( + (req) => { + return safelyExtractRESTRequest(req, getDefaultRESTRequest()) + } + ) + } + } + + return x + }) +} diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts b/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts index 6fead267..9d2a4576 100644 --- a/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts +++ b/packages/hoppscotch-common/src/helpers/import-export/import/openapi.ts @@ -20,7 +20,7 @@ import { HoppRESTRequest, HoppRESTRequestResponses, HoppRESTResponseOriginalRequest, - RESTResOriginalReqSchemaVersion, + makeHoppRESTResponseOriginalRequest, } from "@hoppscotch/data" import { pipe, flow } from "fp-ts/function" import * as A from "fp-ts/Array" @@ -828,24 +828,27 @@ const convertPathToHoppReqs = ( (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: RESTResOriginalReqSchemaVersion, - }), + responses: parseOpenAPIResponses( + doc, + info, + makeHoppRESTResponseOriginalRequest({ + 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) ?? [] + ), + }) + ), }), metadata: { tags: info.tags ?? [], diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts b/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts index a41df9af..f396c4b6 100644 --- a/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts +++ b/packages/hoppscotch-common/src/helpers/import-export/import/postman.ts @@ -10,8 +10,9 @@ import { knownContentTypes, makeCollection, makeRESTRequest, - RESTResOriginalReqSchemaVersion, ValidContentTypes, + HoppRESTRequestResponses, + makeHoppRESTResponseOriginalRequest, } from "@hoppscotch/data" import * as A from "fp-ts/Array" import { flow, pipe } from "fp-ts/function" @@ -31,7 +32,6 @@ 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" const safeParseJSON = (jsonStr: string) => O.tryCatch(() => JSON.parse(jsonStr)) @@ -160,7 +160,7 @@ const getHoppResponses = ( body: response.body ?? "", headers: getHoppReqHeaders(response.headers), code: response.code, - originalRequest: { + originalRequest: makeHoppRESTResponseOriginalRequest({ auth: getHoppReqAuth(response.originalRequest?.auth), body: getHoppReqBody({ body: response.originalRequest?.body, @@ -178,8 +178,7 @@ const getHoppResponses = ( requestVariables: getHoppReqVariables( response.originalRequest?.url.variables ?? null ), - v: RESTResOriginalReqSchemaVersion, - }, + }), } return [response.name, res] }) diff --git a/packages/hoppscotch-common/src/helpers/teams/TeamEnvironmentAdapter.ts b/packages/hoppscotch-common/src/helpers/teams/TeamEnvironmentAdapter.ts index c43d521b..6f1143c7 100644 --- a/packages/hoppscotch-common/src/helpers/teams/TeamEnvironmentAdapter.ts +++ b/packages/hoppscotch-common/src/helpers/teams/TeamEnvironmentAdapter.ts @@ -11,7 +11,6 @@ import { } from "../backend/graphql" import { TeamEnvironment } from "./TeamEnvironment" import { Environment, EnvironmentSchemaVersion } from "@hoppscotch/data" -import { entityReference } from "verzod" type EntityType = "environment" type EntityID = `${EntityType}-${string}` @@ -121,18 +120,18 @@ export default class TeamEnvironmentAdapter { variables: JSON.parse(x.variables), } - const parsedEnvironment = - entityReference(Environment).safeParse(environment) + const parsedEnvironment = Environment.safeParse(environment) return { id: x.id, teamID: x.teamID, - environment: parsedEnvironment.success - ? parsedEnvironment.data - : { - ...environment, - v: EnvironmentSchemaVersion, - }, + environment: + parsedEnvironment.type === "ok" + ? parsedEnvironment.value + : { + ...environment, + v: EnvironmentSchemaVersion, + }, } }) ) diff --git a/packages/hoppscotch-common/src/helpers/utils/statusCodes.ts b/packages/hoppscotch-common/src/helpers/utils/statusCodes.ts index 44d7f8a8..6f2cf56d 100644 --- a/packages/hoppscotch-common/src/helpers/utils/statusCodes.ts +++ b/packages/hoppscotch-common/src/helpers/utils/statusCodes.ts @@ -111,7 +111,7 @@ export const getFullStatusCodePhrase = () => { // return all status codes and their phrases // like code • phrase export const getStatusCodePhrase = ( - code: number | undefined, + code: number | undefined | null, statusText: string ) => { if (!code) return statusText diff --git a/packages/hoppscotch-common/src/kernel/store.ts b/packages/hoppscotch-common/src/kernel/store.ts index 0cdbdbe8..25971db2 100644 --- a/packages/hoppscotch-common/src/kernel/store.ts +++ b/packages/hoppscotch-common/src/kernel/store.ts @@ -57,11 +57,16 @@ export const Store = (() => { return module().listNamespaces(STORE_PATH) }, - listKeys: async (namespace: string): Promise> => { + listKeys: async ( + namespace: string + ): Promise> => { return module().listKeys(STORE_PATH, namespace) }, - watch: async (namespace: string, key: string): Promise> => { + watch: async ( + namespace: string, + key: string + ): Promise> => { return module().watch(STORE_PATH, namespace, key) }, } as const diff --git a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts index 416e7481..48e4024e 100644 --- a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts +++ b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/agent/store.ts @@ -92,17 +92,14 @@ export class KernelInterceptorAgentStore extends Service { private async setupWatchers() { const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.SETTINGS) - watcher.on( - "change", - async ({ value }) => { - if (value) { - const store = value as StoredData - this.domainSettings = new Map(Object.entries(store.domains)) - this.authKey.value = store.auth.key - this.sharedSecretB16.value = store.auth.sharedSecret - } + watcher.on("change", async ({ value }) => { + if (value) { + const store = value as StoredData + this.domainSettings = new Map(Object.entries(store.domains)) + this.authKey.value = store.auth.key + this.sharedSecretB16.value = store.auth.sharedSecret } - ) + }) } private async persistStore(): Promise { diff --git a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/extension/store.ts b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/extension/store.ts index ad386d73..4adc757e 100644 --- a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/extension/store.ts +++ b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/extension/store.ts @@ -89,15 +89,12 @@ export class KernelInterceptorExtensionStore extends Service { } const watcher = await Store.watch(STORE_NAMESPACE, SETTINGS_KEY) - watcher.on( - "change", - async ({ value }) => { - if (value) { - const storedData = value as StoredData - this.settings = storedData.settings - } + watcher.on("change", async ({ value }) => { + if (value) { + const storedData = value as StoredData + this.settings = storedData.settings } - ) + }) } public getExtensionVersion(): O.Option { diff --git a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts index 64995890..c8294386 100644 --- a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts +++ b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/native/store.ts @@ -74,15 +74,12 @@ export class KernelInterceptorNativeStore extends Service { private async setupWatchers() { const watcher = await Store.watch(STORE_NAMESPACE, STORE_KEYS.SETTINGS) - watcher.on( - "change", - async ({ value }) => { - if (value) { - const store = value as StoredData - this.domainSettings = new Map(Object.entries(store.domains)) - } + watcher.on("change", async ({ value }) => { + if (value) { + const store = value as StoredData + this.domainSettings = new Map(Object.entries(store.domains)) } - ) + }) } private async persistStore(): Promise { diff --git a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts index 271d448b..f70308a2 100644 --- a/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts +++ b/packages/hoppscotch-common/src/platform/std/kernel-interceptors/proxy/store.ts @@ -39,15 +39,12 @@ export class KernelInterceptorProxyStore extends Service { await this.loadSettings() const watcher = await Store.watch(STORE_NAMESPACE, SETTINGS_KEY) - watcher.on( - "change", - async ({ value }) => { - if (value) { - const storedData = value as StoredData - this.settings = storedData.settings - } + watcher.on("change", async ({ value }) => { + if (value) { + const storedData = value as StoredData + this.settings = storedData.settings } - ) + }) } private async loadSettings(): Promise { diff --git a/packages/hoppscotch-common/src/services/persistence/__tests__/index.spec.ts b/packages/hoppscotch-common/src/services/persistence/__tests__/index.spec.ts index fb3d2d31..c338266e 100644 --- a/packages/hoppscotch-common/src/services/persistence/__tests__/index.spec.ts +++ b/packages/hoppscotch-common/src/services/persistence/__tests__/index.spec.ts @@ -954,10 +954,10 @@ describe("PersistenceService", () => { // Invalid shape for `environments` const environments = [ // `entries` -> `variables` + // no name for the environment { v: 1, id: "ENV_1", - name: "Test", entries: [{ key: "test-key", value: "test-value", secret: false }], }, ] diff --git a/packages/hoppscotch-common/src/services/persistence/index.ts b/packages/hoppscotch-common/src/services/persistence/index.ts index 986dd82b..a68ad0c0 100644 --- a/packages/hoppscotch-common/src/services/persistence/index.ts +++ b/packages/hoppscotch-common/src/services/persistence/index.ts @@ -97,6 +97,9 @@ import { CurrentValueService, Variable, } from "../current-environment-value.service" +import { cloneDeep } from "lodash-es" +import { fixBrokenRequestVersion } from "~/helpers/fixBrokenRequestVersion" +import { fixBrokenEnvironmentVersion } from "~/helpers/fixBrokenEnvironmentVersion" export const STORE_NAMESPACE = "persistence.v1" @@ -528,7 +531,9 @@ export class PersistenceService extends Service { try { if (E.isRight(loadResult)) { const data = loadResult.right ?? [] - const result = ENVIRONMENTS_SCHEMA.safeParse(data) + const environments = fixBrokenEnvironmentVersion(data) + + const result = ENVIRONMENTS_SCHEMA.safeParse(environments) if (result.success) { // Check for and handle globals @@ -866,8 +871,16 @@ export class PersistenceService extends Service { try { if (E.isRight(loadResult) && loadResult.right) { - const result = REST_TAB_STATE_SCHEMA.safeParse(loadResult.right) + // Correcting the request schema for broken data + const orderedDocs = fixBrokenRequestVersion( + cloneDeep(loadResult.right.orderedDocs) ?? [] + ) + const transformedTabs = { + ...loadResult.right, + orderedDocs, + } + const result = REST_TAB_STATE_SCHEMA.safeParse(transformedTabs) if (result.success) { // SAFETY: We know the schema matches this.restTabService.loadTabsFromPersistedState( diff --git a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts index 4e9d8460..65ad931c 100644 --- a/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts +++ b/packages/hoppscotch-common/src/services/persistence/validation-schemas/index.ts @@ -405,12 +405,9 @@ const HoppTestResultSchema = z .object({ additions: z.array(EnvironmentVariablesSchema), updations: z.array( - EnvironmentVariablesSchema.refine( - (x) => "secret" in x && !x.secret - ).and( - z.object({ - previousValue: z.optional(z.string()), - }) + z.intersection( + EnvironmentVariablesSchema, + z.object({ previousValue: z.optional(z.string()) }) ) ), deletions: z.array(EnvironmentVariablesSchema), @@ -420,12 +417,9 @@ const HoppTestResultSchema = z .object({ additions: z.array(EnvironmentVariablesSchema), updations: z.array( - EnvironmentVariablesSchema.refine( - (x) => "secret" in x && !x.secret - ).and( - z.object({ - previousValue: z.optional(z.string()), - }) + z.intersection( + EnvironmentVariablesSchema, + z.object({ previousValue: z.optional(z.string()) }) ) ), deletions: z.array(EnvironmentVariablesSchema), @@ -582,7 +576,7 @@ export const REST_TAB_STATE_SCHEMA = z }), z.object({ type: z.literal("example-response").catch("example-response"), - response: HoppRESTRequestResponse, + response: entityReference(HoppRESTRequestResponse), saveContext: z.optional(HoppRESTSaveContextSchema), isDirty: z.boolean(), }), diff --git a/packages/hoppscotch-data/src/collection/v/4.ts b/packages/hoppscotch-data/src/collection/v/4.ts index 7af4ab38..7fa2bb09 100644 --- a/packages/hoppscotch-data/src/collection/v/4.ts +++ b/packages/hoppscotch-data/src/collection/v/4.ts @@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod" import { z } from "zod" import { HoppGQLAuth } from "../../graphql/v/7" -import { HoppRESTAuth } from "../../rest/v/8" +import { HoppRESTAuth } from "../../rest/v/8/auth" import { V3_SCHEMA, v3_baseCollectionSchema } from "./3" import { HoppCollection } from ".." diff --git a/packages/hoppscotch-data/src/collection/v/6.ts b/packages/hoppscotch-data/src/collection/v/6.ts index ca65d7a5..437091d5 100644 --- a/packages/hoppscotch-data/src/collection/v/6.ts +++ b/packages/hoppscotch-data/src/collection/v/6.ts @@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod" import { z } from "zod" import { HoppGQLAuth } from "../../graphql/v/8" -import { HoppRESTAuth } from "../../rest/v/11" +import { HoppRESTAuth } from "../../rest/v/11/auth" import { V5_SCHEMA, v5_baseCollectionSchema } from "./5" import { HoppCollection } from ".." diff --git a/packages/hoppscotch-data/src/collection/v/7.ts b/packages/hoppscotch-data/src/collection/v/7.ts index ce6fb29f..8b069824 100644 --- a/packages/hoppscotch-data/src/collection/v/7.ts +++ b/packages/hoppscotch-data/src/collection/v/7.ts @@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod" import { z } from "zod" import { HoppGQLAuth } from "../../graphql/v/8" -import { HoppRESTAuth } from "../../rest/v/12" +import { HoppRESTAuth } from "../../rest/v/12/auth" import { V6_SCHEMA, v6_baseCollectionSchema } from "./6" import { HoppCollection } from ".." diff --git a/packages/hoppscotch-data/src/collection/v/8.ts b/packages/hoppscotch-data/src/collection/v/8.ts index d11ed776..1426a625 100644 --- a/packages/hoppscotch-data/src/collection/v/8.ts +++ b/packages/hoppscotch-data/src/collection/v/8.ts @@ -2,7 +2,7 @@ import { defineVersion, entityRefUptoVersion } from "verzod" import { z } from "zod" import { HoppGQLAuth } from "../../graphql/v/8" -import { HoppRESTAuth } from "../../rest/v/13" +import { HoppRESTAuth } from "../../rest/v/13/auth" import { HoppCollection } from ".." import { v7_baseCollectionSchema, V7_SCHEMA } from "./7" diff --git a/packages/hoppscotch-data/src/environment/index.ts b/packages/hoppscotch-data/src/environment/index.ts index e2438a29..66118a41 100644 --- a/packages/hoppscotch-data/src/environment/index.ts +++ b/packages/hoppscotch-data/src/environment/index.ts @@ -205,7 +205,7 @@ export const translateToNewEnvironmentVariables = ( key: x.key, initialValue: x.initialValue ?? x.value ?? "", currentValue: x.currentValue ?? x.value ?? "", - secret: false, + secret: x.secret ?? false, } } diff --git a/packages/hoppscotch-data/src/global-environment/v/2.ts b/packages/hoppscotch-data/src/global-environment/v/2.ts index 6058ad58..e205d510 100644 --- a/packages/hoppscotch-data/src/global-environment/v/2.ts +++ b/packages/hoppscotch-data/src/global-environment/v/2.ts @@ -22,14 +22,15 @@ export default defineVersion({ ...old, v: 2, variables: old.variables.map((variable) => { + const { key, secret } = variable // if the variable is secret, set initialValue and currentValue to empty string // else set initialValue and currentValue to value // and delete value return { - ...variable, + key, + secret: secret ?? false, initialValue: variable.secret ? "" : variable.value, currentValue: variable.secret ? "" : variable.value, - value: undefined, } }), } diff --git a/packages/hoppscotch-data/src/graphql/v/8.ts b/packages/hoppscotch-data/src/graphql/v/8.ts index b34e5bfa..d7e8f8e3 100644 --- a/packages/hoppscotch-data/src/graphql/v/8.ts +++ b/packages/hoppscotch-data/src/graphql/v/8.ts @@ -9,10 +9,10 @@ import { } from "./2" import { HoppGQLAuthAPIKey } from "./4" import { HoppGQLAuthAWSSignature } from "./6" -import { HoppRESTAuthOAuth2 } from "./../../rest/v/11" +import { HoppRESTAuthOAuth2 } from "../../rest/v/11/auth" import { V7_SCHEMA } from "./7" -export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/11" +export { HoppRESTAuthOAuth2 as HoppGQLAuthOAuth2 } from "../../rest/v/11/auth" export const HoppGQLAuth = z .discriminatedUnion("authType", [ diff --git a/packages/hoppscotch-data/src/index.ts b/packages/hoppscotch-data/src/index.ts index b3bb743a..37e6ae94 100644 --- a/packages/hoppscotch-data/src/index.ts +++ b/packages/hoppscotch-data/src/index.ts @@ -9,3 +9,4 @@ export * from "./utils/collection" export * from "./utils/hawk" export * from "./utils/akamai-eg" export * from "./utils/jwt" +export * from "./rest-request-response" diff --git a/packages/hoppscotch-data/src/rest-request-response/index.ts b/packages/hoppscotch-data/src/rest-request-response/index.ts new file mode 100644 index 00000000..5eb26564 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/index.ts @@ -0,0 +1,50 @@ +import { InferredEntity, createVersionedEntity, entityReference } from "verzod" +import { z } from "zod" +import V0_VERSION from "./v/0" +import { + HoppRESTResOriginalReqSchemaVersion, + HoppRESTResponseOriginalRequest, +} from "./original-request" + +export { HoppRESTResponseOriginalRequest } from "./original-request" + +const versionedObject = z.object({ + v: z.number(), +}) + +export const HoppRESTRequestResponse = createVersionedEntity({ + latestVersion: 0, + versionMap: { + 0: V0_VERSION, + }, + getVersion(data) { + const versionCheck = versionedObject.safeParse(data) + + if (versionCheck.success) return versionCheck.data.v + + // Schema starts from version 0, so if the version is not present, + // we assume it's version 0 + const result = V0_VERSION.schema.safeParse(data) + return result.success ? 0 : null + }, +}) + +export type HoppRESTRequestResponse = InferredEntity< + typeof HoppRESTRequestResponse +> + +export const HoppRESTRequestResponses = z.record( + z.string(), + entityReference(HoppRESTRequestResponse) +) + +export type HoppRESTRequestResponses = z.infer + +export function makeHoppRESTResponseOriginalRequest( + x: Omit +): HoppRESTResponseOriginalRequest { + return { + v: HoppRESTResOriginalReqSchemaVersion, + ...x, + } +} diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/index.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/index.ts new file mode 100644 index 00000000..53f4ef09 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/index.ts @@ -0,0 +1,39 @@ +import { InferredEntity, createVersionedEntity } from "verzod" +import { z } from "zod" +import V1_VERSION from "./v/1" +import V2_VERSION from "./v/2" +import V3_VERSION from "./v/3" +import V4_VERSION from "./v/4" +import V5_VERSION from "./v/5" + +const versionedObject = z.object({ + // v is a stringified number + v: z.string().regex(/^\d+$/).transform(Number), +}) + +export const HoppRESTResponseOriginalRequest = createVersionedEntity({ + latestVersion: 5, + versionMap: { + 1: V1_VERSION, + 2: V2_VERSION, + 3: V3_VERSION, + 4: V4_VERSION, + 5: V5_VERSION, + }, + getVersion(data) { + const versionCheck = versionedObject.safeParse(data) + + if (versionCheck.success) return versionCheck.data.v + + // For V1 we have to check the schema + const result = V1_VERSION.schema.safeParse(data) + + return result.success ? 1 : null + }, +}) + +export const HoppRESTResOriginalReqSchemaVersion = "5" + +export type HoppRESTResponseOriginalRequest = InferredEntity< + typeof HoppRESTResponseOriginalRequest +> diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/v/1.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/v/1.ts new file mode 100644 index 00000000..a4307bd7 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/v/1.ts @@ -0,0 +1,24 @@ +import { defineVersion } from "verzod" +import { z } from "zod" +import { HoppRESTHeaders, HoppRESTParams } from "../../../rest/v/7" +import { HoppRESTReqBody } from "../../../rest/v/6" +import { HoppRESTRequestVariables } from "../../../rest/v/2" + +import { HoppRESTAuth } from "../../../rest/v/8/auth" + +export const V1_SCHEMA = z.object({ + v: z.literal("1"), + name: z.string(), + method: z.string(), + endpoint: z.string(), + headers: HoppRESTHeaders, + params: HoppRESTParams, + body: HoppRESTReqBody, + auth: HoppRESTAuth, + requestVariables: HoppRESTRequestVariables, +}) + +export default defineVersion({ + initial: true, + schema: V1_SCHEMA, +}) diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/v/2.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/v/2.ts new file mode 100644 index 00000000..cf6231a5 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/v/2.ts @@ -0,0 +1,20 @@ +import { defineVersion } from "verzod" +import { z } from "zod" +import { V1_SCHEMA } from "./1" +import { HoppRESTReqBody } from "../../../rest/v/9/body" + +export const V2_SCHEMA = V1_SCHEMA.extend({ + v: z.literal("2"), + body: HoppRESTReqBody, +}) + +export default defineVersion({ + initial: false, + schema: V2_SCHEMA, + up(old: z.infer) { + return { + ...old, + v: "2" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/v/3.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/v/3.ts new file mode 100644 index 00000000..57a03f2d --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/v/3.ts @@ -0,0 +1,38 @@ +import { defineVersion } from "verzod" +import { z } from "zod" + +import { V2_SCHEMA } from "./2" +import { HoppRESTAuth } from "../../../rest/v/11/auth" +import { HoppRESTReqBody } from "../../../rest/v/10/body" + +export const V3_SCHEMA = V2_SCHEMA.extend({ + v: z.literal("3"), + auth: HoppRESTAuth, + body: HoppRESTReqBody, +}) + +export default defineVersion({ + initial: false, + schema: V3_SCHEMA, + up(old: z.infer) { + const auth = old.auth + + return { + ...old, + v: "3" as const, + auth: + auth.authType === "oauth-2" + ? { + ...auth, + grantTypeInfo: + auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS" + ? { + ...auth.grantTypeInfo, + clientAuthentication: "IN_BODY" as const, + } + : auth.grantTypeInfo, + } + : auth, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/v/4.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/v/4.ts new file mode 100644 index 00000000..a97b954f --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/v/4.ts @@ -0,0 +1,20 @@ +import { defineVersion } from "verzod" +import { z } from "zod" +import { V3_SCHEMA } from "./3" +import { HoppRESTAuth } from "../../../rest/v/12/auth" + +export const V4_SCHEMA = V3_SCHEMA.extend({ + v: z.literal("4"), + auth: HoppRESTAuth, +}) + +export default defineVersion({ + initial: false, + schema: V4_SCHEMA, + up(old: z.infer) { + return { + ...old, + v: "4" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest-request-response/original-request/v/5.ts b/packages/hoppscotch-data/src/rest-request-response/original-request/v/5.ts new file mode 100644 index 00000000..2fa26a76 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/original-request/v/5.ts @@ -0,0 +1,20 @@ +import { defineVersion } from "verzod" +import { z } from "zod" +import { V4_SCHEMA } from "./4" +import { HoppRESTAuth } from "../../../rest/v/13/auth" + +export const V5_SCHEMA = V4_SCHEMA.extend({ + v: z.literal("5"), + auth: HoppRESTAuth, +}) + +export default defineVersion({ + initial: false, + schema: V5_SCHEMA, + up(old: z.infer) { + return { + ...old, + v: "5" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest-request-response/v/0.ts b/packages/hoppscotch-data/src/rest-request-response/v/0.ts new file mode 100644 index 00000000..9e4bb9e5 --- /dev/null +++ b/packages/hoppscotch-data/src/rest-request-response/v/0.ts @@ -0,0 +1,35 @@ +import { defineVersion, entityReference } from "verzod" +import { z } from "zod" +import { HoppRESTResponseOriginalRequest } from "../original-request" +import { StatusCodes } from "../../utils/statusCodes" + +export const ValidCodes = z.union( + Object.keys(StatusCodes).map((code) => z.literal(parseInt(code))) as [ + z.ZodLiteral, + z.ZodLiteral, + ...z.ZodLiteral[] + ] +) + +export const HoppRESTResponseHeaders = z.array( + z.object({ + key: z.string(), + value: z.string(), + }) +) + +export type HoppRESTResponseHeader = z.infer + +export const V0_SCHEMA = z.object({ + name: z.string(), + originalRequest: entityReference(HoppRESTResponseOriginalRequest), + status: z.string(), + code: z.optional(ValidCodes).nullable().catch(null), + headers: HoppRESTResponseHeaders, + body: z.string(), +}) + +export default defineVersion({ + initial: true, + schema: V0_SCHEMA, +}) diff --git a/packages/hoppscotch-data/src/rest/index.ts b/packages/hoppscotch-data/src/rest/index.ts index d3f1ca14..e03967c2 100644 --- a/packages/hoppscotch-data/src/rest/index.ts +++ b/packages/hoppscotch-data/src/rest/index.ts @@ -16,10 +16,14 @@ import V6_VERSION from "./v/6" import V7_VERSION, { HoppRESTHeaders, HoppRESTParams } from "./v/7" import V8_VERSION from "./v/8" import V9_VERSION from "./v/9" -import V10_VERSION, { HoppRESTReqBody } from "./v/10" +import V10_VERSION from "./v/10" +import { HoppRESTReqBody } from "./v/10/body" import V11_VERSION from "./v/11" import V12_VERSION from "./v/12" -import V13_VERSION, { HoppRESTAuth, HoppRESTRequestResponses } from "./v/13" +import V13_VERSION from "./v/13" +import { HoppRESTAuth } from "./v/13/auth" +import V14_VERSION from "./v/14" +import { HoppRESTRequestResponses } from "../rest-request-response" export * from "./content-types" @@ -44,23 +48,25 @@ export { HoppRESTParams, } from "./v/7" -export { HoppRESTAuthDigest, PasswordGrantTypeParams } from "./v/8" +export { HoppRESTAuthDigest, PasswordGrantTypeParams } from "./v/8/auth" -export { FormDataKeyValue } from "./v/9" - -export { HoppRESTAuthOAuth2, ClientCredentialsGrantTypeParams } from "./v/11" - -export { HoppRESTReqBody } from "./v/10" - -export { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid } from "./v/12" +export { FormDataKeyValue } from "./v/9/body" + +export { + HoppRESTAuthOAuth2, + ClientCredentialsGrantTypeParams, +} from "./v/11/auth" + +export { HoppRESTReqBody } from "./v/10/body" + +export { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid } from "./v/12/auth" + +export { HoppRESTAuth, HoppRESTAuthJWT } from "./v/13/auth" export { - HoppRESTAuth, - HoppRESTAuthJWT, - HoppRESTRequestResponses, - HoppRESTResponseOriginalRequest, HoppRESTRequestResponse, -} from "./v/13" + HoppRESTRequestResponses, +} from "../rest-request-response" const versionedObject = z.object({ // v is a stringified number @@ -68,7 +74,7 @@ const versionedObject = z.object({ }) export const HoppRESTRequest = createVersionedEntity({ - latestVersion: 13, + latestVersion: 14, versionMap: { 0: V0_VERSION, 1: V1_VERSION, @@ -84,6 +90,7 @@ export const HoppRESTRequest = createVersionedEntity({ 11: V11_VERSION, 12: V12_VERSION, 13: V13_VERSION, + 14: V14_VERSION, }, getVersion(data) { // For V1 onwards we have the v string storing the number @@ -126,8 +133,7 @@ const HoppRESTRequestEq = Eq.struct({ responses: lodashIsEqualEq, }) -export const RESTReqSchemaVersion = "13" -export const RESTResOriginalReqSchemaVersion = "5" as const +export const RESTReqSchemaVersion = "14" export type HoppRESTParam = HoppRESTRequest["params"][number] export type HoppRESTHeader = HoppRESTRequest["headers"][number] @@ -210,7 +216,6 @@ export function safelyExtractRESTRequest( if ("responses" in x) { const result = HoppRESTRequestResponses.safeParse(x.responses) - if (result.success) { req.responses = result.data } diff --git a/packages/hoppscotch-data/src/rest/v/10.ts b/packages/hoppscotch-data/src/rest/v/10/body.ts similarity index 76% rename from packages/hoppscotch-data/src/rest/v/10.ts rename to packages/hoppscotch-data/src/rest/v/10/body.ts index 9dc77e23..af22ca93 100644 --- a/packages/hoppscotch-data/src/rest/v/10.ts +++ b/packages/hoppscotch-data/src/rest/v/10/body.ts @@ -1,6 +1,5 @@ import { z } from "zod" -import { FormDataKeyValue, V9_SCHEMA } from "./9" -import { defineVersion } from "verzod" +import { FormDataKeyValue } from "../9/body" export const HoppRESTReqBody = z.union([ z.object({ @@ -39,19 +38,3 @@ export const HoppRESTReqBody = z.union([ ]) export type HoppRESTReqBody = z.infer - -export const V10_SCHEMA = V9_SCHEMA.extend({ - v: z.literal("10"), - body: HoppRESTReqBody, -}) - -export default defineVersion({ - schema: V10_SCHEMA, - initial: false, - up(old: z.infer) { - return { - ...old, - v: "10" as const, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/10/index.ts b/packages/hoppscotch-data/src/rest/v/10/index.ts new file mode 100644 index 00000000..95367fb3 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/10/index.ts @@ -0,0 +1,20 @@ +import { z } from "zod" +import { V9_SCHEMA } from "../9" +import { HoppRESTReqBody } from "./body" +import { defineVersion } from "verzod" + +export const V10_SCHEMA = V9_SCHEMA.extend({ + v: z.literal("10"), + body: HoppRESTReqBody, +}) + +export default defineVersion({ + schema: V10_SCHEMA, + initial: false, + up(old: z.infer) { + return { + ...old, + v: "10" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/11.ts b/packages/hoppscotch-data/src/rest/v/11.ts deleted file mode 100644 index 2db50114..00000000 --- a/packages/hoppscotch-data/src/rest/v/11.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthInherit, - HoppRESTAuthNone, -} from "./1" -import { HoppRESTAuthAPIKey } from "./4" -import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "./7" -import { - ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld, - HoppRESTAuthDigest, - PasswordGrantTypeParams, -} from "./8" -import { ImplicitOauthFlowParams } from "./3" -import { z } from "zod" - -import { HoppRESTReqBody, V10_SCHEMA } from "./10" -import { defineVersion } from "verzod" -import { - HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld, - HoppRESTRequestResponse as HoppRESTRequestResponseOld, -} from "./9" - -export const ClientCredentialsGrantTypeParams = - ClientCredentialsGrantTypeParamsOld.extend({ - clientAuthentication: z.enum(["AS_BASIC_AUTH_HEADERS", "IN_BODY"]), - }) - -export const HoppRESTAuthOAuth2 = z.object({ - authType: z.literal("oauth-2"), - grantTypeInfo: z.discriminatedUnion("grantType", [ - AuthCodeGrantTypeParams, - ClientCredentialsGrantTypeParams, - PasswordGrantTypeParams, - ImplicitOauthFlowParams, - ]), - addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), -}) - -export type HoppRESTAuthOAuth2 = z.infer - -export const HoppRESTAuth = z - .discriminatedUnion("authType", [ - HoppRESTAuthNone, - HoppRESTAuthInherit, - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthOAuth2, - HoppRESTAuthAPIKey, - HoppRESTAuthAWSSignature, - HoppRESTAuthDigest, - ]) - .and( - z.object({ - authActive: z.boolean(), - }) - ) - -export type HoppRESTAuth = z.infer - -export const HoppRESTResponseOriginalRequest = - HoppRESTResponseOriginalRequestOld.extend({ - v: z.literal("3"), - auth: HoppRESTAuth, - body: HoppRESTReqBody, - }) - -export type HoppRESTResponseOriginalRequest = z.infer< - typeof HoppRESTResponseOriginalRequest -> - -export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({ - originalRequest: HoppRESTResponseOriginalRequest, -}) - -export type HoppRESTRequestResponse = z.infer - -export const HoppRESTRequestResponses = z.record( - z.string(), - HoppRESTRequestResponse -) - -export type HoppRESTRequestResponses = z.infer - -export const V11_SCHEMA = V10_SCHEMA.extend({ - v: z.literal("11"), - auth: HoppRESTAuth, - responses: HoppRESTRequestResponses, -}) - -export default defineVersion({ - schema: V11_SCHEMA, - initial: false, - up(old: z.infer) { - const auth = old.auth - - // update auth for responses - const responses = Object.fromEntries( - Object.entries(old.responses).map(([key, response]) => [ - key, - { - ...response, - originalRequest: { - ...response.originalRequest, - - auth: - auth.authType === "oauth-2" - ? { - ...auth, - grantTypeInfo: - auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS" - ? { - ...auth.grantTypeInfo, - clientAuthentication: "IN_BODY" as const, - } - : auth.grantTypeInfo, - } - : auth, - - // just following the previous pattern here, but is this a good idea to overwrite the request version ? - v: "3" as const, - }, - }, - ]) - ) - - return { - ...old, - v: "11" as const, - auth: - auth.authType === "oauth-2" - ? { - ...auth, - grantTypeInfo: - auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS" - ? { - ...auth.grantTypeInfo, - clientAuthentication: "IN_BODY" as const, - } - : auth.grantTypeInfo, - } - : auth, - responses, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/11/auth.ts b/packages/hoppscotch-data/src/rest/v/11/auth.ts new file mode 100644 index 00000000..81be6cc8 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/11/auth.ts @@ -0,0 +1,52 @@ +import { + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthInherit, + HoppRESTAuthNone, +} from "../1" +import { HoppRESTAuthAPIKey } from "../4" +import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "../7" +import { + ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld, + HoppRESTAuthDigest, + PasswordGrantTypeParams, +} from "../8/auth" +import { ImplicitOauthFlowParams } from "../3" +import { z } from "zod" + +export const ClientCredentialsGrantTypeParams = + ClientCredentialsGrantTypeParamsOld.extend({ + clientAuthentication: z.enum(["AS_BASIC_AUTH_HEADERS", "IN_BODY"]), + }) + +export const HoppRESTAuthOAuth2 = z.object({ + authType: z.literal("oauth-2"), + grantTypeInfo: z.discriminatedUnion("grantType", [ + AuthCodeGrantTypeParams, + ClientCredentialsGrantTypeParams, + PasswordGrantTypeParams, + ImplicitOauthFlowParams, + ]), + addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), +}) + +export type HoppRESTAuthOAuth2 = z.infer + +export const HoppRESTAuth = z + .discriminatedUnion("authType", [ + HoppRESTAuthNone, + HoppRESTAuthInherit, + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthOAuth2, + HoppRESTAuthAPIKey, + HoppRESTAuthAWSSignature, + HoppRESTAuthDigest, + ]) + .and( + z.object({ + authActive: z.boolean(), + }) + ) + +export type HoppRESTAuth = z.infer diff --git a/packages/hoppscotch-data/src/rest/v/11/index.ts b/packages/hoppscotch-data/src/rest/v/11/index.ts new file mode 100644 index 00000000..4fb0d977 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/11/index.ts @@ -0,0 +1,37 @@ +import { z } from "zod" + +import { V10_SCHEMA } from "../10" +import { defineVersion } from "verzod" + +import { HoppRESTAuth } from "./auth" + +export const V11_SCHEMA = V10_SCHEMA.extend({ + v: z.literal("11"), + auth: HoppRESTAuth, +}) + +export default defineVersion({ + schema: V11_SCHEMA, + initial: false, + up(old: z.infer) { + const auth = old.auth + + return { + ...old, + v: "11" as const, + auth: + auth.authType === "oauth-2" + ? { + ...auth, + grantTypeInfo: + auth.grantTypeInfo.grantType === "CLIENT_CREDENTIALS" + ? { + ...auth.grantTypeInfo, + clientAuthentication: "IN_BODY" as const, + } + : auth.grantTypeInfo, + } + : auth, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/12.ts b/packages/hoppscotch-data/src/rest/v/12.ts deleted file mode 100644 index 9f42b8b3..00000000 --- a/packages/hoppscotch-data/src/rest/v/12.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthInherit, - HoppRESTAuthNone, -} from "./1" -import { HoppRESTAuthAPIKey } from "./4" -import { HoppRESTAuthAWSSignature } from "./7" -import { HoppRESTAuthDigest } from "./8" - -import { z } from "zod" -import { defineVersion } from "verzod" -import { HoppRESTAuthOAuth2, V11_SCHEMA } from "./11" - -import { - HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld, - HoppRESTRequestResponse as HoppRESTRequestResponseOld, -} from "./9" - -export const HoppRESTAuthHAWK = z.object({ - authType: z.literal("hawk"), - authId: z.string().catch(""), - authKey: z.string().catch(""), - algorithm: z.enum(["sha256", "sha1"]).catch("sha256"), - includePayloadHash: z.boolean().catch(false), - - // Optional fields - user: z.string().optional(), - nonce: z.string().optional(), - ext: z.string().optional(), - app: z.string().optional(), - dlg: z.string().optional(), - timestamp: z.string().optional(), -}) - -export const HoppRESTAuthAkamaiEdgeGrid = z.object({ - authType: z.literal("akamai-eg"), - accessToken: z.string().catch(""), - clientToken: z.string().catch(""), - clientSecret: z.string().catch(""), - - // Optional fields - nonce: z.string().optional(), - timestamp: z.string().optional(), - host: z.string().optional(), - headersToSign: z.string().optional(), - maxBodySize: z.string().optional(), -}) - -export type HoppRESTAuthHAWK = z.infer -export type HoppRESTAuthAkamaiEdgeGrid = z.infer< - typeof HoppRESTAuthAkamaiEdgeGrid -> - -export const HoppRESTAuth = z - .discriminatedUnion("authType", [ - HoppRESTAuthNone, - HoppRESTAuthInherit, - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthOAuth2, - HoppRESTAuthAPIKey, - HoppRESTAuthAWSSignature, - HoppRESTAuthDigest, - HoppRESTAuthHAWK, - HoppRESTAuthAkamaiEdgeGrid, - ]) - .and( - z.object({ - authActive: z.boolean(), - }) - ) - -export type HoppRESTAuth = z.infer - -export const HoppRESTResponseOriginalRequest = - HoppRESTResponseOriginalRequestOld.extend({ - v: z.literal("4"), - auth: HoppRESTAuth, - }) - -export type HoppRESTResponseOriginalRequest = z.infer< - typeof HoppRESTResponseOriginalRequest -> - -export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({ - originalRequest: HoppRESTResponseOriginalRequest, -}) - -export type HoppRESTRequestResponse = z.infer - -export const HoppRESTRequestResponses = z.record( - z.string(), - HoppRESTRequestResponse -) - -export type HoppRESTRequestResponses = z.infer - -export const V12_SCHEMA = V11_SCHEMA.extend({ - v: z.literal("12"), - auth: HoppRESTAuth, - responses: HoppRESTRequestResponses, -}) - -export default defineVersion({ - schema: V12_SCHEMA, - initial: false, - up(old: z.infer) { - // update the version number of response original request - const responses = Object.fromEntries( - Object.entries(old.responses).map(([key, response]) => [ - key, - { - ...response, - originalRequest: { - ...response.originalRequest, - v: "4" as const, - }, - }, - ]) - ) - return { - ...old, - v: "12" as const, - responses, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/12/auth.ts b/packages/hoppscotch-data/src/rest/v/12/auth.ts new file mode 100644 index 00000000..4eb3af75 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/12/auth.ts @@ -0,0 +1,67 @@ +import { z } from "zod" +import { + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthInherit, + HoppRESTAuthNone, +} from "../1" +import { HoppRESTAuthAPIKey } from "../4" +import { HoppRESTAuthAWSSignature } from "../7" +import { HoppRESTAuthDigest } from "../8/auth" +import { HoppRESTAuthOAuth2 } from "../11/auth" + +export const HoppRESTAuthHAWK = z.object({ + authType: z.literal("hawk"), + authId: z.string().catch(""), + authKey: z.string().catch(""), + algorithm: z.enum(["sha256", "sha1"]).catch("sha256"), + includePayloadHash: z.boolean().catch(false), + + // Optional fields + user: z.string().optional(), + nonce: z.string().optional(), + ext: z.string().optional(), + app: z.string().optional(), + dlg: z.string().optional(), + timestamp: z.string().optional(), +}) + +export const HoppRESTAuthAkamaiEdgeGrid = z.object({ + authType: z.literal("akamai-eg"), + accessToken: z.string().catch(""), + clientToken: z.string().catch(""), + clientSecret: z.string().catch(""), + + // Optional fields + nonce: z.string().optional(), + timestamp: z.string().optional(), + host: z.string().optional(), + headersToSign: z.string().optional(), + maxBodySize: z.string().optional(), +}) + +export type HoppRESTAuthHAWK = z.infer +export type HoppRESTAuthAkamaiEdgeGrid = z.infer< + typeof HoppRESTAuthAkamaiEdgeGrid +> + +export const HoppRESTAuth = z + .discriminatedUnion("authType", [ + HoppRESTAuthNone, + HoppRESTAuthInherit, + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthOAuth2, + HoppRESTAuthAPIKey, + HoppRESTAuthAWSSignature, + HoppRESTAuthDigest, + HoppRESTAuthHAWK, + HoppRESTAuthAkamaiEdgeGrid, + ]) + .and( + z.object({ + authActive: z.boolean(), + }) + ) + +export type HoppRESTAuth = z.infer diff --git a/packages/hoppscotch-data/src/rest/v/12/index.ts b/packages/hoppscotch-data/src/rest/v/12/index.ts new file mode 100644 index 00000000..49460101 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/12/index.ts @@ -0,0 +1,21 @@ +import { z } from "zod" +import { defineVersion } from "verzod" +import { V11_SCHEMA } from "../11" + +import { HoppRESTAuth } from "./auth" + +export const V12_SCHEMA = V11_SCHEMA.extend({ + v: z.literal("12"), + auth: HoppRESTAuth, +}) + +export default defineVersion({ + schema: V12_SCHEMA, + initial: false, + up(old: z.infer) { + return { + ...old, + v: "12" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/13.ts b/packages/hoppscotch-data/src/rest/v/13.ts deleted file mode 100644 index 994df695..00000000 --- a/packages/hoppscotch-data/src/rest/v/13.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthInherit, - HoppRESTAuthNone, -} from "./1" -import { HoppRESTAuthAPIKey } from "./4" -import { HoppRESTAuthAWSSignature } from "./7" -import { HoppRESTAuthDigest } from "./8" - -import { HoppRESTAuthHAWK, HoppRESTAuthAkamaiEdgeGrid, V12_SCHEMA } from "./12" - -import { z } from "zod" -import { defineVersion } from "verzod" -import { HoppRESTAuthOAuth2 } from "./11" - -import { - HoppRESTResponseOriginalRequest as HoppRESTResponseOriginalRequestOld, - HoppRESTRequestResponse as HoppRESTRequestResponseOld, -} from "./9" - -export const HoppRESTAuthJWT = z.object({ - authType: z.literal("jwt"), - secret: z.string().catch(""), - privateKey: z.string().catch(""), // For RSA/ECDSA algorithms - algorithm: z - .enum([ - "HS256", - "HS384", - "HS512", - "RS256", - "RS384", - "RS512", - "PS256", - "PS384", - "PS512", - "ES256", - "ES384", - "ES512", - ]) - .catch("HS256"), - payload: z.string().catch("{}"), - addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), - isSecretBase64Encoded: z.boolean().catch(false), - headerPrefix: z.string().catch("Bearer "), - paramName: z.string().catch("token"), - jwtHeaders: z.string().catch("{}"), -}) - -export type HoppRESTAuthJWT = z.infer - -export const HoppRESTAuth = z - .discriminatedUnion("authType", [ - HoppRESTAuthNone, - HoppRESTAuthInherit, - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthOAuth2, - HoppRESTAuthAPIKey, - HoppRESTAuthAWSSignature, - HoppRESTAuthDigest, - HoppRESTAuthHAWK, - HoppRESTAuthAkamaiEdgeGrid, - HoppRESTAuthJWT, - ]) - .and( - z.object({ - authActive: z.boolean(), - }) - ) - -export type HoppRESTAuth = z.infer - -export const HoppRESTResponseOriginalRequest = - HoppRESTResponseOriginalRequestOld.extend({ - v: z.literal("5"), - auth: HoppRESTAuth, - }) - -export type HoppRESTResponseOriginalRequest = z.infer< - typeof HoppRESTResponseOriginalRequest -> - -export const HoppRESTRequestResponse = HoppRESTRequestResponseOld.extend({ - originalRequest: HoppRESTResponseOriginalRequest, -}) - -export type HoppRESTRequestResponse = z.infer - -export const HoppRESTRequestResponses = z.record( - z.string(), - HoppRESTRequestResponse -) - -export type HoppRESTRequestResponses = z.infer - -export const V13_SCHEMA = V12_SCHEMA.extend({ - v: z.literal("13"), - auth: HoppRESTAuth, - responses: HoppRESTRequestResponses, -}) - -export default defineVersion({ - schema: V13_SCHEMA, - initial: false, - up(old: z.infer) { - // update the version number of response original request - const responses = Object.fromEntries( - Object.entries(old.responses).map(([key, response]) => [ - key, - { - ...response, - originalRequest: { - ...response.originalRequest, - v: "5" as const, - }, - }, - ]) - ) - return { - ...old, - v: "13" as const, - responses, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/13/auth.ts b/packages/hoppscotch-data/src/rest/v/13/auth.ts new file mode 100644 index 00000000..01d20317 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/13/auth.ts @@ -0,0 +1,64 @@ +import { z } from "zod" +import { + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthInherit, + HoppRESTAuthNone, +} from "../1" +import { HoppRESTAuthAPIKey } from "../4" +import { HoppRESTAuthAWSSignature } from "../7" +import { HoppRESTAuthDigest } from "../8/auth" +import { HoppRESTAuthOAuth2 } from "../11/auth" +import { HoppRESTAuthAkamaiEdgeGrid, HoppRESTAuthHAWK } from "../12/auth" + +export const HoppRESTAuthJWT = z.object({ + authType: z.literal("jwt"), + secret: z.string().catch(""), + privateKey: z.string().catch(""), // For RSA/ECDSA algorithms + algorithm: z + .enum([ + "HS256", + "HS384", + "HS512", + "RS256", + "RS384", + "RS512", + "PS256", + "PS384", + "PS512", + "ES256", + "ES384", + "ES512", + ]) + .catch("HS256"), + payload: z.string().catch("{}"), + addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), + isSecretBase64Encoded: z.boolean().catch(false), + headerPrefix: z.string().catch("Bearer "), + paramName: z.string().catch("token"), + jwtHeaders: z.string().catch("{}"), +}) + +export type HoppRESTAuthJWT = z.infer + +export const HoppRESTAuth = z + .discriminatedUnion("authType", [ + HoppRESTAuthNone, + HoppRESTAuthInherit, + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthOAuth2, + HoppRESTAuthAPIKey, + HoppRESTAuthAWSSignature, + HoppRESTAuthDigest, + HoppRESTAuthHAWK, + HoppRESTAuthAkamaiEdgeGrid, + HoppRESTAuthJWT, + ]) + .and( + z.object({ + authActive: z.boolean(), + }) + ) + +export type HoppRESTAuth = z.infer diff --git a/packages/hoppscotch-data/src/rest/v/13/index.ts b/packages/hoppscotch-data/src/rest/v/13/index.ts new file mode 100644 index 00000000..ff4ca826 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/13/index.ts @@ -0,0 +1,22 @@ +import { V12_SCHEMA } from "../12" + +import { z } from "zod" +import { defineVersion } from "verzod" + +import { HoppRESTAuth } from "./auth" + +export const V13_SCHEMA = V12_SCHEMA.extend({ + v: z.literal("13"), + auth: HoppRESTAuth, +}) + +export default defineVersion({ + schema: V13_SCHEMA, + initial: false, + up(old: z.infer) { + return { + ...old, + v: "13" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/14.ts b/packages/hoppscotch-data/src/rest/v/14.ts new file mode 100644 index 00000000..f49167ba --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/14.ts @@ -0,0 +1,19 @@ +import { z } from "zod" +import { defineVersion } from "verzod" +import { V13_SCHEMA } from "./13" + +// Update the HoppRESTRequestResponses while migrating HoppRESTRequest +export const V14_SCHEMA = V13_SCHEMA.extend({ + v: z.literal("14"), +}) + +export default defineVersion({ + schema: V14_SCHEMA, + initial: false, + up(old: z.infer) { + return { + ...old, + v: "14" as const, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/8.ts b/packages/hoppscotch-data/src/rest/v/8.ts deleted file mode 100644 index 868fa3a4..00000000 --- a/packages/hoppscotch-data/src/rest/v/8.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { defineVersion } from "verzod" -import { z } from "zod" - -import { - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthInherit, - HoppRESTAuthNone, -} from "./1" - -import { HoppRESTAuthAPIKey } from "./4" - -import { - ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld, - ImplicitOauthFlowParams, - PasswordGrantTypeParams as PasswordGrantTypeParamsOld, -} from "./3" - -import { - AuthCodeGrantTypeParams, - HoppRESTAuthAWSSignature, - HoppRESTHeaders, - HoppRESTParams, - V7_SCHEMA, -} from "./7" - -import { StatusCodes } from "../../utils/statusCodes" -import { HoppRESTReqBody } from "./6" -import { HoppRESTRequestVariables } from "./2" - -export const ClientCredentialsGrantTypeParams = - ClientCredentialsGrantTypeParamsOld.extend({ - clientSecret: z.string().optional(), - }) - -export const PasswordGrantTypeParams = PasswordGrantTypeParamsOld.extend({ - clientSecret: z.string().optional(), -}) - -export const HoppRESTAuthOAuth2 = z.object({ - authType: z.literal("oauth-2"), - grantTypeInfo: z.discriminatedUnion("grantType", [ - AuthCodeGrantTypeParams, - ClientCredentialsGrantTypeParams, - PasswordGrantTypeParams, - ImplicitOauthFlowParams, - ]), - addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), -}) - -export type HoppRESTAuthOAuth2 = z.infer - -// in this new version, we add a new auth type for Digest authentication -export const HoppRESTAuthDigest = z.object({ - authType: z.literal("digest"), - username: z.string().catch(""), - password: z.string().catch(""), - realm: z.string().catch(""), - nonce: z.string().catch(""), - algorithm: z.enum(["MD5", "MD5-sess"]).catch("MD5"), - qop: z.enum(["auth", "auth-int"]).catch("auth"), - nc: z.string().catch(""), - cnonce: z.string().catch(""), - opaque: z.string().catch(""), - disableRetry: z.boolean().catch(false), -}) - -export type HoppRESTAuthDigest = z.infer - -export const HoppRESTAuth = z - .discriminatedUnion("authType", [ - HoppRESTAuthNone, - HoppRESTAuthInherit, - HoppRESTAuthBasic, - HoppRESTAuthBearer, - HoppRESTAuthOAuth2, - HoppRESTAuthAPIKey, - HoppRESTAuthAWSSignature, - HoppRESTAuthDigest, - ]) - .and( - z.object({ - authActive: z.boolean(), - }) - ) - -export type HoppRESTAuth = z.infer - -export const ValidCodes = z.union( - Object.keys(StatusCodes).map((code) => z.literal(parseInt(code))) as [ - z.ZodLiteral, - z.ZodLiteral, - ...z.ZodLiteral[] - ] -) - -export const HoppRESTResponseHeaders = z.array( - z.object({ - key: z.string(), - value: z.string(), - }) -) - -export type HoppRESTResponseHeader = z.infer - -/** - * The original request that was made to get this response - * Only the necessary fields are saved - */ -export const HoppRESTResponseOriginalRequest = z.object({ - v: z.literal("1"), - name: z.string(), - method: z.string(), - endpoint: z.string(), - headers: HoppRESTHeaders, - params: HoppRESTParams, - body: HoppRESTReqBody, - auth: HoppRESTAuth, - requestVariables: HoppRESTRequestVariables, -}) - -export type HoppRESTResponseOriginalRequest = z.infer< - typeof HoppRESTResponseOriginalRequest -> - -export const HoppRESTRequestResponse = z.object({ - name: z.string(), - originalRequest: HoppRESTResponseOriginalRequest, - status: z.string(), - code: z.optional(ValidCodes), - headers: HoppRESTResponseHeaders, - body: z.string(), -}) - -export type HoppRESTRequestResponse = z.infer - -/** - * The responses saved for a request - * The key is the name of the response saved by the user - * The value is the response - */ -export const HoppRESTRequestResponses = z.record( - z.string(), - HoppRESTRequestResponse -) - -export type HoppRESTRequestResponses = z.infer - -export const V8_SCHEMA = V7_SCHEMA.extend({ - v: z.literal("8"), - auth: HoppRESTAuth, - responses: HoppRESTRequestResponses, -}) - -export default defineVersion({ - schema: V8_SCHEMA, - initial: false, - up(old: z.infer) { - return { - ...old, - v: "8" as const, - // no need to update anything for HoppRESTAuth, because we loosened the previous schema by making `clientSecret` optional - responses: {}, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/8/auth.ts b/packages/hoppscotch-data/src/rest/v/8/auth.ts new file mode 100644 index 00000000..1ff66e10 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/8/auth.ts @@ -0,0 +1,75 @@ +import { + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthInherit, + HoppRESTAuthNone, +} from "../1" + +import { HoppRESTAuthAPIKey } from "../4" + +import { + ClientCredentialsGrantTypeParams as ClientCredentialsGrantTypeParamsOld, + ImplicitOauthFlowParams, + PasswordGrantTypeParams as PasswordGrantTypeParamsOld, +} from "../3" + +import { AuthCodeGrantTypeParams, HoppRESTAuthAWSSignature } from "../7" +import { z } from "zod" + +export const ClientCredentialsGrantTypeParams = + ClientCredentialsGrantTypeParamsOld.extend({ + clientSecret: z.string().optional(), + }) + +export const PasswordGrantTypeParams = PasswordGrantTypeParamsOld.extend({ + clientSecret: z.string().optional(), +}) + +export const HoppRESTAuthOAuth2 = z.object({ + authType: z.literal("oauth-2"), + grantTypeInfo: z.discriminatedUnion("grantType", [ + AuthCodeGrantTypeParams, + ClientCredentialsGrantTypeParams, + PasswordGrantTypeParams, + ImplicitOauthFlowParams, + ]), + addTo: z.enum(["HEADERS", "QUERY_PARAMS"]).catch("HEADERS"), +}) + +export type HoppRESTAuthOAuth2 = z.infer + +// in this new version, we add a new auth type for Digest authentication +export const HoppRESTAuthDigest = z.object({ + authType: z.literal("digest"), + username: z.string().catch(""), + password: z.string().catch(""), + realm: z.string().catch(""), + nonce: z.string().catch(""), + algorithm: z.enum(["MD5", "MD5-sess"]).catch("MD5"), + qop: z.enum(["auth", "auth-int"]).catch("auth"), + nc: z.string().catch(""), + cnonce: z.string().catch(""), + opaque: z.string().catch(""), + disableRetry: z.boolean().catch(false), +}) + +export type HoppRESTAuthDigest = z.infer + +export const HoppRESTAuth = z + .discriminatedUnion("authType", [ + HoppRESTAuthNone, + HoppRESTAuthInherit, + HoppRESTAuthBasic, + HoppRESTAuthBearer, + HoppRESTAuthOAuth2, + HoppRESTAuthAPIKey, + HoppRESTAuthAWSSignature, + HoppRESTAuthDigest, + ]) + .and( + z.object({ + authActive: z.boolean(), + }) + ) + +export type HoppRESTAuth = z.infer diff --git a/packages/hoppscotch-data/src/rest/v/8/index.ts b/packages/hoppscotch-data/src/rest/v/8/index.ts new file mode 100644 index 00000000..c1b818f5 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/8/index.ts @@ -0,0 +1,27 @@ +import { defineVersion } from "verzod" +import { z } from "zod" + +import { V7_SCHEMA } from "../7" + +import { HoppRESTRequestResponses } from "../../../rest-request-response" + +import { HoppRESTAuth } from "./auth" + +export const V8_SCHEMA = V7_SCHEMA.extend({ + v: z.literal("8"), + auth: HoppRESTAuth, + responses: HoppRESTRequestResponses, +}) + +export default defineVersion({ + schema: V8_SCHEMA, + initial: false, + up(old: z.infer) { + return { + ...old, + v: "8" as const, + // no need to update anything for HoppRESTAuth, because we loosened the previous schema by making `clientSecret` optional + responses: {}, + } + }, +}) diff --git a/packages/hoppscotch-data/src/rest/v/9.ts b/packages/hoppscotch-data/src/rest/v/9.ts deleted file mode 100644 index ffacb537..00000000 --- a/packages/hoppscotch-data/src/rest/v/9.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { defineVersion } from "verzod" -import { z } from "zod" - -import { HoppRESTRequestVariables } from "./2" -import { HoppRESTHeaders, HoppRESTParams } from "./7" -import { - HoppRESTAuth, - HoppRESTResponseHeaders, - V8_SCHEMA, - ValidCodes, -} from "./8" - -export const FormDataKeyValue = z - .object({ - key: z.string(), - active: z.boolean(), - contentType: z.string().optional().catch(undefined), - }) - .and( - z.union([ - z.object({ - isFile: z.literal(true), - value: z.array(z.instanceof(Blob).nullable()).catch([]), - }), - z.object({ - isFile: z.literal(false), - value: z.string(), - }), - ]) - ) - .transform((data) => { - // Sample use case about restoring the `value` field in an empty state during page reload - // for files chosen in the previous attempt - if (data.isFile && Array.isArray(data.value) && data.value.length === 0) { - return { - ...data, - isFile: false, - value: "", - } - } - - return data - }) - -export type FormDataKeyValue = z.infer - -export const HoppRESTReqBody = z.union([ - z.object({ - contentType: z.literal(null), - body: z.literal(null).catch(null), - }), - z.object({ - contentType: z.literal("multipart/form-data"), - body: z.array(FormDataKeyValue).catch([]), - showIndividualContentType: z.boolean().optional().catch(false), - }), - z.object({ - contentType: z.literal("application/octet-stream"), - body: z.instanceof(File).nullable().catch(null), - }), - z.object({ - contentType: z.union([ - z.literal("application/json"), - z.literal("application/ld+json"), - z.literal("application/hal+json"), - z.literal("application/vnd.api+json"), - z.literal("application/xml"), - z.literal("text/xml"), - z.literal("application/x-www-form-urlencoded"), - z.literal("binary"), - z.literal("text/html"), - z.literal("text/plain"), - ]), - body: z.string().catch(""), - }), -]) - -export type HoppRESTReqBody = z.infer - -/** - * The original request that was made to get this response - * Only the necessary fields are saved - */ -export const HoppRESTResponseOriginalRequest = z.object({ - v: z.literal("2"), - name: z.string(), - method: z.string(), - endpoint: z.string(), - headers: HoppRESTHeaders, - params: HoppRESTParams, - body: HoppRESTReqBody, - auth: HoppRESTAuth, - requestVariables: HoppRESTRequestVariables, -}) - -export type HoppRESTResponseOriginalRequest = z.infer< - typeof HoppRESTResponseOriginalRequest -> - -export const HoppRESTRequestResponse = z.object({ - name: z.string(), - originalRequest: HoppRESTResponseOriginalRequest, - status: z.string(), - code: z.optional(ValidCodes).nullable().catch(null), - headers: HoppRESTResponseHeaders, - body: z.string(), -}) - -export type HoppRESTRequestResponse = z.infer - -/** - * The responses saved for a request - * The key is the name of the response saved by the user - * The value is the response - */ -export const HoppRESTRequestResponses = z.record( - z.string(), - HoppRESTRequestResponse -) - -export type HoppRESTRequestResponses = z.infer - -export const V9_SCHEMA = V8_SCHEMA.extend({ - v: z.literal("9"), - body: HoppRESTReqBody, - responses: HoppRESTRequestResponses, -}) - -export default defineVersion({ - schema: V9_SCHEMA, - initial: false, - up(old: z.infer) { - // update the version number of response original request - const responses = Object.fromEntries( - Object.entries(old.responses).map(([key, response]) => [ - key, - { - ...response, - originalRequest: { - ...response.originalRequest, - v: "2" as const, - }, - }, - ]) - ) - - // No migration for body, the new contentType added to each formdata field is optional - return { - ...old, - v: "9" as const, - responses, - } - }, -}) diff --git a/packages/hoppscotch-data/src/rest/v/9/body.ts b/packages/hoppscotch-data/src/rest/v/9/body.ts new file mode 100644 index 00000000..1ad19ae3 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/9/body.ts @@ -0,0 +1,68 @@ +import { z } from "zod" + +export const FormDataKeyValue = z + .object({ + key: z.string(), + active: z.boolean(), + contentType: z.string().optional().catch(undefined), + }) + .and( + z.union([ + z.object({ + isFile: z.literal(true), + value: z.array(z.instanceof(Blob).nullable()).catch([]), + }), + z.object({ + isFile: z.literal(false), + value: z.string(), + }), + ]) + ) + .transform((data) => { + // Sample use case about restoring the `value` field in an empty state during page reload + // for files chosen in the previous attempt + if (data.isFile && Array.isArray(data.value) && data.value.length === 0) { + return { + ...data, + isFile: false, + value: "", + } + } + + return data + }) + +export type FormDataKeyValue = z.infer + +export const HoppRESTReqBody = z.union([ + z.object({ + contentType: z.literal(null), + body: z.literal(null).catch(null), + }), + z.object({ + contentType: z.literal("multipart/form-data"), + body: z.array(FormDataKeyValue).catch([]), + showIndividualContentType: z.boolean().optional().catch(false), + }), + z.object({ + contentType: z.literal("application/octet-stream"), + body: z.instanceof(File).nullable().catch(null), + }), + z.object({ + contentType: z.union([ + z.literal("application/json"), + z.literal("application/ld+json"), + z.literal("application/hal+json"), + z.literal("application/vnd.api+json"), + z.literal("application/xml"), + z.literal("text/xml"), + z.literal("application/x-www-form-urlencoded"), + z.literal("binary"), + z.literal("text/html"), + z.literal("text/plain"), + ]), + body: z.string().catch(""), + }), +]) + +export type HoppRESTReqBody = z.infer diff --git a/packages/hoppscotch-data/src/rest/v/9/index.ts b/packages/hoppscotch-data/src/rest/v/9/index.ts new file mode 100644 index 00000000..e38a64d1 --- /dev/null +++ b/packages/hoppscotch-data/src/rest/v/9/index.ts @@ -0,0 +1,22 @@ +import { defineVersion } from "verzod" +import { z } from "zod" + +import { V8_SCHEMA } from "../8" +import { HoppRESTReqBody } from "./body" + +export const V9_SCHEMA = V8_SCHEMA.extend({ + v: z.literal("9"), + body: HoppRESTReqBody, +}) + +export default defineVersion({ + schema: V9_SCHEMA, + initial: false, + up(old: z.infer) { + // No migration for body, the new contentType added to each formdata field is optional + return { + ...old, + v: "9" as const, + } + }, +}) diff --git a/packages/hoppscotch-selfhost-desktop/src/platform/environments/environments.platform.ts b/packages/hoppscotch-selfhost-desktop/src/platform/environments/environments.platform.ts index 95a741b3..e0a55a61 100644 --- a/packages/hoppscotch-selfhost-desktop/src/platform/environments/environments.platform.ts +++ b/packages/hoppscotch-selfhost-desktop/src/platform/environments/environments.platform.ts @@ -96,11 +96,10 @@ async function loadUserEnvironments() { replaceEnvironments( formatedEnvironments.map((environment) => { - const parsedEnv = - entityReference(Environment).safeParse(environment) + const parsedEnv = Environment.safeParse(environment) - return parsedEnv.success - ? parsedEnv.data + return parsedEnv.type === "ok" + ? parsedEnv.value : { ...environment, v: EnvironmentSchemaVersion, @@ -191,12 +190,24 @@ function setupUserEnvironmentUpdatedSubscription() { ) if ((localIndex || localIndex == 0) && name) { + const environment = { + id, + name, + variables: JSON.parse(variables), + } + + const parsedEnvResult = Environment.safeParse(environment) + + const parsedEnv: Environment = + parsedEnvResult.type === "ok" + ? parsedEnvResult.value + : { + ...environment, + v: EnvironmentSchemaVersion, + } + runDispatchWithOutSyncing(() => { - updateEnvironment(localIndex, { - id, - name, - variables: JSON.parse(variables), - }) + updateEnvironment(localIndex, parsedEnv) }) } } diff --git a/packages/hoppscotch-selfhost-web/src/platform/environments/desktop/index.ts b/packages/hoppscotch-selfhost-web/src/platform/environments/desktop/index.ts index 02749f79..fd65e24d 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/environments/desktop/index.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/environments/desktop/index.ts @@ -97,11 +97,10 @@ async function loadUserEnvironments() { replaceEnvironments( formatedEnvironments.map((environment) => { - const parsedEnv = - entityReference(Environment).safeParse(environment) + const parsedEnv = Environment.safeParse(environment) - return parsedEnv.success - ? parsedEnv.data + return parsedEnv.type === "ok" + ? parsedEnv.value : { ...environment, v: EnvironmentSchemaVersion, diff --git a/packages/hoppscotch-selfhost-web/src/platform/environments/web/index.ts b/packages/hoppscotch-selfhost-web/src/platform/environments/web/index.ts index 03b0292a..f80136e8 100644 --- a/packages/hoppscotch-selfhost-web/src/platform/environments/web/index.ts +++ b/packages/hoppscotch-selfhost-web/src/platform/environments/web/index.ts @@ -97,11 +97,10 @@ async function loadUserEnvironments() { replaceEnvironments( formatedEnvironments.map((environment) => { - const parsedEnv = - entityReference(Environment).safeParse(environment) + const parsedEnv = Environment.safeParse(environment) - return parsedEnv.success - ? parsedEnv.data + return parsedEnv.type === "ok" + ? parsedEnv.value : { ...environment, v: EnvironmentSchemaVersion, @@ -192,13 +191,24 @@ function setupUserEnvironmentUpdatedSubscription() { ) if ((localIndex || localIndex == 0) && name) { + const environment = { + id, + name, + variables: JSON.parse(variables), + } + + const parsedEnvResult = Environment.safeParse(environment) + + const parsedEnv: Environment = + parsedEnvResult.type === "ok" + ? parsedEnvResult.value + : { + ...environment, + v: EnvironmentSchemaVersion, + } + runDispatchWithOutSyncing(() => { - updateEnvironment(localIndex, { - v: 2, - id, - name, - variables: JSON.parse(variables), - }) + updateEnvironment(localIndex, parsedEnv) }) } }