559 lines
14 KiB
TypeScript
559 lines
14 KiB
TypeScript
import {
|
|
Environment,
|
|
GQLHeader,
|
|
HoppGQLAuth,
|
|
HoppGQLRequest,
|
|
HoppRESTAuth,
|
|
HoppRESTRequest,
|
|
HoppRESTHeaders,
|
|
HoppRESTRequestResponse,
|
|
} from "@hoppscotch/data"
|
|
import { entityReference } from "verzod"
|
|
import { z } from "zod"
|
|
import { HoppAccentColors, HoppBgColors } from "~/newstore/settings"
|
|
|
|
const ThemeColorSchema = z.enum([
|
|
"green",
|
|
"teal",
|
|
"blue",
|
|
"indigo",
|
|
"purple",
|
|
"yellow",
|
|
"orange",
|
|
"red",
|
|
"pink",
|
|
])
|
|
|
|
const BgColorSchema = z.enum(["system", "light", "dark", "black"])
|
|
|
|
const EncodeMode = z.enum(["enable", "disable", "auto"])
|
|
|
|
const SettingsDefSchema = z.object({
|
|
syncCollections: z.boolean(),
|
|
syncHistory: z.boolean(),
|
|
syncEnvironments: z.boolean(),
|
|
PROXY_URL: z.string(),
|
|
CURRENT_INTERCEPTOR_ID: z.string(),
|
|
URL_EXCLUDES: z.object({
|
|
auth: z.boolean(),
|
|
httpUser: z.boolean(),
|
|
httpPassword: z.boolean(),
|
|
bearerToken: z.boolean(),
|
|
oauth2Token: z.optional(z.boolean()),
|
|
}),
|
|
THEME_COLOR: ThemeColorSchema,
|
|
BG_COLOR: BgColorSchema,
|
|
ENCODE_MODE: EncodeMode.catch("enable"),
|
|
TELEMETRY_ENABLED: z.boolean(),
|
|
EXPAND_NAVIGATION: z.boolean(),
|
|
SIDEBAR: z.boolean(),
|
|
SIDEBAR_ON_LEFT: z.boolean(),
|
|
COLUMN_LAYOUT: z.boolean(),
|
|
|
|
WRAP_LINES: z.optional(
|
|
z.object({
|
|
httpRequestBody: z.boolean().catch(true),
|
|
httpResponseBody: z.boolean().catch(true),
|
|
httpHeaders: z.boolean().catch(true),
|
|
httpParams: z.boolean().catch(true),
|
|
httpUrlEncoded: z.boolean().catch(true),
|
|
httpPreRequest: z.boolean().catch(true),
|
|
httpTest: z.boolean().catch(true),
|
|
httpRequestVariables: z.boolean().catch(true),
|
|
graphqlQuery: z.boolean().catch(true),
|
|
graphqlResponseBody: z.boolean().catch(true),
|
|
graphqlHeaders: z.boolean().catch(false),
|
|
graphqlVariables: z.boolean().catch(false),
|
|
graphqlSchema: z.boolean().catch(true),
|
|
importCurl: z.boolean().catch(true),
|
|
codeGen: z.boolean().catch(true),
|
|
cookie: z.boolean().catch(true),
|
|
})
|
|
),
|
|
|
|
HAS_OPENED_SPOTLIGHT: z.optional(z.boolean()),
|
|
ENABLE_AI_EXPERIMENTS: z.optional(z.boolean()),
|
|
})
|
|
|
|
// Common properties shared across REST & GQL collections
|
|
const HoppCollectionSchemaCommonProps = z
|
|
.object({
|
|
v: z.number(),
|
|
name: z.string(),
|
|
id: z.optional(z.string()),
|
|
})
|
|
.strict()
|
|
|
|
const HoppRESTRequestSchema = entityReference(HoppRESTRequest)
|
|
|
|
const HoppGQLRequestSchema = entityReference(HoppGQLRequest)
|
|
|
|
// @ts-expect-error recursive schema
|
|
const HoppRESTCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
|
folders: z.array(z.lazy(() => HoppRESTCollectionSchema)),
|
|
requests: z.optional(z.array(HoppRESTRequestSchema)),
|
|
|
|
auth: z.optional(HoppRESTAuth),
|
|
headers: z.optional(HoppRESTHeaders),
|
|
}).strict()
|
|
|
|
// @ts-expect-error recursive schema
|
|
const HoppGQLCollectionSchema = HoppCollectionSchemaCommonProps.extend({
|
|
folders: z.array(z.lazy(() => HoppGQLCollectionSchema)),
|
|
requests: z.optional(z.array(HoppGQLRequestSchema)),
|
|
|
|
auth: z.optional(HoppGQLAuth),
|
|
headers: z.optional(z.array(GQLHeader)),
|
|
}).strict()
|
|
|
|
export const VUEX_SCHEMA = z.object({
|
|
postwoman: z.optional(
|
|
z.object({
|
|
settings: z.optional(SettingsDefSchema),
|
|
//! Versioned entities
|
|
collections: z.optional(z.array(HoppRESTCollectionSchema)),
|
|
collectionsGraphql: z.optional(z.array(HoppGQLCollectionSchema)),
|
|
environments: z.optional(z.array(entityReference(Environment))),
|
|
})
|
|
),
|
|
})
|
|
|
|
export const THEME_COLOR_SCHEMA = z.enum(HoppAccentColors)
|
|
|
|
export const NUXT_COLOR_MODE_SCHEMA = z.enum(HoppBgColors)
|
|
|
|
export const LOCAL_STATE_SCHEMA = z.union([
|
|
z.object({}).strict(),
|
|
z
|
|
.object({
|
|
REMEMBERED_TEAM_ID: z.optional(z.string()),
|
|
})
|
|
.strict(),
|
|
])
|
|
|
|
export const SETTINGS_SCHEMA = SettingsDefSchema.extend({
|
|
EXTENSIONS_ENABLED: z.optional(z.boolean()),
|
|
PROXY_ENABLED: z.optional(z.boolean()),
|
|
})
|
|
|
|
export const REST_HISTORY_ENTRY_SCHEMA = z
|
|
.object({
|
|
v: z.number(),
|
|
//! Versioned entity
|
|
request: HoppRESTRequestSchema,
|
|
responseMeta: z
|
|
.object({
|
|
duration: z.nullable(z.number()),
|
|
statusCode: z.nullable(z.number()),
|
|
})
|
|
.strict(),
|
|
star: z.boolean(),
|
|
id: z.optional(z.string()),
|
|
updatedOn: z.optional(z.union([z.date(), z.string()])),
|
|
})
|
|
.strict()
|
|
|
|
export const GQL_HISTORY_ENTRY_SCHEMA = z
|
|
.object({
|
|
v: z.number(),
|
|
//! Versioned entity
|
|
request: HoppGQLRequestSchema,
|
|
response: z.string(),
|
|
star: z.boolean(),
|
|
id: z.optional(z.string()),
|
|
updatedOn: z.optional(z.union([z.date(), z.string()])),
|
|
})
|
|
.strict()
|
|
|
|
export const REST_COLLECTION_SCHEMA = HoppRESTCollectionSchema
|
|
|
|
export const GQL_COLLECTION_SCHEMA = HoppGQLCollectionSchema
|
|
|
|
export const ENVIRONMENTS_SCHEMA = z.array(entityReference(Environment))
|
|
|
|
export const SELECTED_ENV_INDEX_SCHEMA = z.nullable(
|
|
z.discriminatedUnion("type", [
|
|
z
|
|
.object({
|
|
type: z.literal("NO_ENV_SELECTED"),
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
type: z.literal("MY_ENV"),
|
|
index: z.number(),
|
|
})
|
|
.strict(),
|
|
z.object({
|
|
type: z.literal("TEAM_ENV"),
|
|
teamID: z.string(),
|
|
teamEnvID: z.string(),
|
|
environment: entityReference(Environment),
|
|
}),
|
|
])
|
|
)
|
|
|
|
export const WEBSOCKET_REQUEST_SCHEMA = z.nullable(
|
|
z
|
|
.object({
|
|
endpoint: z.string(),
|
|
protocols: z.array(
|
|
z
|
|
.object({
|
|
value: z.string(),
|
|
active: z.boolean(),
|
|
})
|
|
.strict()
|
|
),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
export const SOCKET_IO_REQUEST_SCHEMA = z.nullable(
|
|
z
|
|
.object({
|
|
endpoint: z.string(),
|
|
path: z.string(),
|
|
version: z.union([z.literal("v4"), z.literal("v3"), z.literal("v2")]),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
export const SSE_REQUEST_SCHEMA = z.nullable(
|
|
z
|
|
.object({
|
|
endpoint: z.string(),
|
|
eventType: z.string(),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
export const MQTT_REQUEST_SCHEMA = z.nullable(
|
|
z
|
|
.object({
|
|
endpoint: z.string(),
|
|
clientID: z.optional(z.string()),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
const EnvironmentVariablesSchema = z.union([
|
|
z.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
secret: z.literal(false).catch(false),
|
|
}),
|
|
z.object({
|
|
key: z.string(),
|
|
secret: z.literal(true),
|
|
}),
|
|
z.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
}),
|
|
])
|
|
|
|
const OperationTypeSchema = z.enum([
|
|
"subscription",
|
|
"query",
|
|
"mutation",
|
|
"teardown",
|
|
])
|
|
|
|
const RunQueryOptionsSchema = z
|
|
.object({
|
|
name: z.optional(z.string()),
|
|
url: z.string(),
|
|
headers: z.array(GQLHeader),
|
|
query: z.string(),
|
|
variables: z.string(),
|
|
auth: HoppGQLAuth,
|
|
operationName: z.optional(z.string()),
|
|
operationType: OperationTypeSchema,
|
|
})
|
|
.strict()
|
|
|
|
const HoppGQLSaveContextSchema = z.nullable(
|
|
z.discriminatedUnion("originLocation", [
|
|
z
|
|
.object({
|
|
originLocation: z.literal("user-collection"),
|
|
folderPath: z.string(),
|
|
requestIndex: z.number(),
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
originLocation: z.literal("team-collection"),
|
|
requestID: z.string(),
|
|
teamID: z.optional(z.string()),
|
|
collectionID: z.optional(z.string()),
|
|
})
|
|
.strict(),
|
|
])
|
|
)
|
|
|
|
const GQLResponseEventSchema = z.array(
|
|
z
|
|
.object({
|
|
time: z.number(),
|
|
operationName: z.optional(z.string()),
|
|
operationType: OperationTypeSchema,
|
|
data: z.string(),
|
|
rawQuery: z.optional(RunQueryOptionsSchema),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
const validGqlOperations = [
|
|
"query",
|
|
"headers",
|
|
"variables",
|
|
"authorization",
|
|
] as const
|
|
|
|
const HoppInheritedPropertySchema = z
|
|
.object({
|
|
auth: z.object({
|
|
parentID: z.string(),
|
|
parentName: z.string(),
|
|
inheritedAuth: z.union([HoppRESTAuth, HoppGQLAuth]),
|
|
}),
|
|
headers: z.array(
|
|
z.object({
|
|
parentID: z.string(),
|
|
parentName: z.string(),
|
|
inheritedHeader: z.union([HoppRESTHeaders, GQLHeader]),
|
|
})
|
|
),
|
|
})
|
|
.strict()
|
|
|
|
export const GQL_TAB_STATE_SCHEMA = z
|
|
.object({
|
|
lastActiveTabID: z.string(),
|
|
orderedDocs: z.array(
|
|
z.object({
|
|
tabID: z.string(),
|
|
doc: z
|
|
.object({
|
|
// Versioned entity
|
|
request: entityReference(HoppGQLRequest),
|
|
isDirty: z.boolean(),
|
|
saveContext: z.optional(HoppGQLSaveContextSchema),
|
|
response: z.optional(z.nullable(GQLResponseEventSchema)),
|
|
responseTabPreference: z.optional(z.string()),
|
|
optionTabPreference: z.optional(z.enum(validGqlOperations)),
|
|
inheritedProperties: z.optional(HoppInheritedPropertySchema),
|
|
})
|
|
.strict(),
|
|
})
|
|
),
|
|
})
|
|
.strict()
|
|
|
|
const HoppTestExpectResultSchema = z
|
|
.object({
|
|
status: z.enum(["fail", "pass", "error"]),
|
|
message: z.string(),
|
|
})
|
|
.strict()
|
|
|
|
// @ts-expect-error recursive schema
|
|
const HoppTestDataSchema = z.lazy(() =>
|
|
z
|
|
.object({
|
|
description: z.string(),
|
|
expectResults: z.array(HoppTestExpectResultSchema),
|
|
tests: z.array(HoppTestDataSchema),
|
|
})
|
|
.strict()
|
|
)
|
|
|
|
export const SECRET_ENVIRONMENT_VARIABLE_SCHEMA = z.union([
|
|
z.object({}).strict(),
|
|
|
|
z.record(
|
|
z.string(),
|
|
z.array(
|
|
z
|
|
.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
varIndex: z.number(),
|
|
})
|
|
.strict()
|
|
)
|
|
),
|
|
])
|
|
|
|
const HoppTestResultSchema = z
|
|
.object({
|
|
tests: z.array(HoppTestDataSchema),
|
|
expectResults: z.array(HoppTestExpectResultSchema),
|
|
description: z.string(),
|
|
scriptError: z.boolean(),
|
|
envDiff: z
|
|
.object({
|
|
global: 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()),
|
|
})
|
|
)
|
|
),
|
|
deletions: z.array(EnvironmentVariablesSchema),
|
|
})
|
|
.strict(),
|
|
selected: 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()),
|
|
})
|
|
)
|
|
),
|
|
deletions: z.array(EnvironmentVariablesSchema),
|
|
})
|
|
.strict(),
|
|
})
|
|
.strict(),
|
|
})
|
|
.strict()
|
|
|
|
const HoppRESTResponseHeaderSchema = z
|
|
.object({
|
|
key: z.string(),
|
|
value: z.string(),
|
|
})
|
|
.strict()
|
|
|
|
const HoppRESTResponseSchema = z.discriminatedUnion("type", [
|
|
z
|
|
.object({
|
|
type: z.literal("loading"),
|
|
// !Versioned entity
|
|
req: HoppRESTRequestSchema,
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
type: z.literal("fail"),
|
|
headers: z.array(HoppRESTResponseHeaderSchema),
|
|
body: z.instanceof(ArrayBuffer),
|
|
statusCode: z.number(),
|
|
meta: z
|
|
.object({
|
|
responseSize: z.number(),
|
|
responseDuration: z.number(),
|
|
})
|
|
.strict(),
|
|
// !Versioned entity
|
|
req: HoppRESTRequestSchema,
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
type: z.literal("network_fail"),
|
|
error: z.unknown(),
|
|
// !Versioned entity
|
|
req: HoppRESTRequestSchema,
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
type: z.literal("script_fail"),
|
|
error: z.instanceof(Error),
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
type: z.literal("success"),
|
|
headers: z.array(HoppRESTResponseHeaderSchema),
|
|
body: z.instanceof(ArrayBuffer),
|
|
statusCode: z.number(),
|
|
meta: z
|
|
.object({
|
|
responseSize: z.number(),
|
|
responseDuration: z.number(),
|
|
})
|
|
.strict(),
|
|
// !Versioned entity
|
|
req: HoppRESTRequestSchema,
|
|
})
|
|
.strict(),
|
|
])
|
|
|
|
const HoppRESTSaveContextSchema = z.nullable(
|
|
z.discriminatedUnion("originLocation", [
|
|
z
|
|
.object({
|
|
originLocation: z.literal("user-collection"),
|
|
folderPath: z.string(),
|
|
requestIndex: z.number(),
|
|
exampleID: z.optional(z.string()),
|
|
})
|
|
.strict(),
|
|
z
|
|
.object({
|
|
originLocation: z.literal("team-collection"),
|
|
requestID: z.string(),
|
|
teamID: z.optional(z.string()),
|
|
collectionID: z.optional(z.string()),
|
|
exampleID: z.optional(z.string()),
|
|
})
|
|
.strict(),
|
|
])
|
|
)
|
|
|
|
const validRestOperations = [
|
|
"params",
|
|
"bodyParams",
|
|
"headers",
|
|
"authorization",
|
|
"preRequestScript",
|
|
"tests",
|
|
"requestVariables",
|
|
] as const
|
|
|
|
export const REST_TAB_STATE_SCHEMA = z
|
|
.object({
|
|
lastActiveTabID: z.string(),
|
|
orderedDocs: z.array(
|
|
z.object({
|
|
tabID: z.string(),
|
|
doc: z.union([
|
|
z.object({
|
|
// !Versioned entity
|
|
request: entityReference(HoppRESTRequest),
|
|
type: z.literal("request").catch("request"),
|
|
isDirty: z.boolean(),
|
|
saveContext: z.optional(HoppRESTSaveContextSchema),
|
|
response: z.optional(z.nullable(HoppRESTResponseSchema)),
|
|
testResults: z.optional(z.nullable(HoppTestResultSchema)),
|
|
responseTabPreference: z.optional(z.string()),
|
|
optionTabPreference: z.optional(z.enum(validRestOperations)),
|
|
inheritedProperties: z.optional(HoppInheritedPropertySchema),
|
|
cancelFunction: z.optional(z.function()),
|
|
}),
|
|
z.object({
|
|
type: z.literal("example-response").catch("example-response"),
|
|
response: HoppRESTRequestResponse,
|
|
saveContext: z.optional(HoppRESTSaveContextSchema),
|
|
isDirty: z.boolean(),
|
|
}),
|
|
]),
|
|
})
|
|
),
|
|
})
|
|
.strict()
|