From 4cec5a7bb48a05216640efb4f0997987c3a97896 Mon Sep 17 00:00:00 2001 From: Andrew Bastin Date: Thu, 6 Jan 2022 22:47:25 +0530 Subject: [PATCH] feat: initial new openapi importer implementation --- packages/hoppscotch-app/globals.d.ts | 8 + .../helpers/import-export/import/importers.ts | 6 +- .../import-export/import/newopenapi.ts | 491 ++++++++++++++++++ packages/hoppscotch-app/package.json | 2 + pnpm-lock.yaml | 83 ++- 5 files changed, 581 insertions(+), 9 deletions(-) create mode 100644 packages/hoppscotch-app/globals.d.ts create mode 100644 packages/hoppscotch-app/helpers/import-export/import/newopenapi.ts diff --git a/packages/hoppscotch-app/globals.d.ts b/packages/hoppscotch-app/globals.d.ts new file mode 100644 index 000000000..bc3fc3300 --- /dev/null +++ b/packages/hoppscotch-app/globals.d.ts @@ -0,0 +1,8 @@ +/* + * Some helpful type definitions to help with type checking + */ + +interface Object { + // Allows for TypeScript to know this field now exist + hasOwnProperty(key: K): this is Record +} diff --git a/packages/hoppscotch-app/helpers/import-export/import/importers.ts b/packages/hoppscotch-app/helpers/import-export/import/importers.ts index ebf73e5c5..69ee495ed 100644 --- a/packages/hoppscotch-app/helpers/import-export/import/importers.ts +++ b/packages/hoppscotch-app/helpers/import-export/import/importers.ts @@ -1,3 +1,7 @@ import HoppRESTCollImporter from "./hopp" +import NewOpenAPIImporter from "./newopenapi" -export const RESTCollectionImporters = [HoppRESTCollImporter] as const +export const RESTCollectionImporters = [ + HoppRESTCollImporter, + NewOpenAPIImporter, +] as const diff --git a/packages/hoppscotch-app/helpers/import-export/import/newopenapi.ts b/packages/hoppscotch-app/helpers/import-export/import/newopenapi.ts new file mode 100644 index 000000000..b0be040a8 --- /dev/null +++ b/packages/hoppscotch-app/helpers/import-export/import/newopenapi.ts @@ -0,0 +1,491 @@ +import { + OpenAPI, + OpenAPIV2, + OpenAPIV3, + OpenAPIV3_1 as OpenAPIV31, +} from "openapi-types" +import SwaggerParser from "@apidevtools/swagger-parser" +import { + FormDataKeyValue, + HoppRESTAuth, + HoppRESTHeader, + HoppRESTParam, + HoppRESTReqBody, + HoppRESTRequest, + knownContentTypes, + makeRESTRequest, +} from "@hoppscotch/data" +import { pipe, flow } from "fp-ts/function" +import * as A from "fp-ts/Array" +import * as S from "fp-ts/string" +import * as O from "fp-ts/Option" +import * as TE from "fp-ts/TaskEither" +import * as RA from "fp-ts/ReadonlyArray" +import { step } from "../steps" +import { defineImporter, IMPORTER_INVALID_FILE_FORMAT } from "." +import { Collection, makeCollection } from "~/newstore/collections" + +const OPENAPI_DEREF_ERROR = "openapi/deref_error" as const + +// TODO: YAMLLLLLLL import support!!!!! +// TODO: Oauth! + +const safeParseJSON = (str: string) => O.tryCatch(() => JSON.parse(str)) + +const objectHasProperty = ( + obj: unknown, + propName: T + // eslint-disable-next-line +): obj is { [propName in T]: unknown } => + !!obj && + typeof obj === "object" && + Object.prototype.hasOwnProperty.call(obj, propName) + +type OpenAPIPathInfoType = + | OpenAPIV2.PathItemObject<{}> + | OpenAPIV3.PathItemObject<{}> + | OpenAPIV31.PathItemObject<{}> + +type OpenAPIParamsType = + | OpenAPIV2.ParameterObject + | OpenAPIV3.ParameterObject + | OpenAPIV31.ParameterObject + +type OpenAPIOperationType = + | OpenAPIV2.OperationObject + | OpenAPIV3.OperationObject + | OpenAPIV31.OperationObject + +// Removes the OpenAPI Path Templating to the Hoppscotch Templating (<< ? >>) +const replaceOpenApiPathTemplating = flow( + S.replace(/{/g, "<<"), + S.replace(/}/g, ">>") +) + +const parseOpenAPIParams = (params: OpenAPIParamsType[]): HoppRESTParam[] => + pipe( + params, + + A.filterMap( + flow( + O.fromPredicate((param) => param.in === "query"), + O.map( + (param) => + { + key: param.name, + value: "", // TODO: Can we do anything more ? (parse default values maybe) + active: true, + } + ) + ) + ) + ) + +const parseOpenAPIHeaders = (params: OpenAPIParamsType[]): HoppRESTHeader[] => + pipe( + params, + + A.filterMap( + flow( + O.fromPredicate((param) => param.in === "header"), + O.map( + (header) => + { + key: header.name, + value: "", // TODO: Can we do anything more ? (parse default values maybe) + active: true, + } + ) + ) + ) + ) + +const parseOpenAPIV2Body = (op: OpenAPIV2.OperationObject): HoppRESTReqBody => { + const obj = (op.consumes ?? [])[0] as string | undefined + + // Not a content-type Hoppscotch supports + if (!obj || !(obj in knownContentTypes)) + return { contentType: null, body: null } + + // Textual Content Types, so we just parse it and keep + if ( + obj !== "multipart/form-data" && + obj !== "application/x-www-form-urlencoded" + ) + return { contentType: obj as any, body: "" } + + const formDataValues = pipe( + (op.parameters ?? []) as OpenAPIV2.Parameter[], + + A.filterMap( + flow( + O.fromPredicate((param) => param.in === "body"), + O.map( + (param) => + { + key: param.name, + isFile: false, + value: "", + active: true, + } + ) + ) + ) + ) + + return obj === "application/x-www-form-urlencoded" + ? { + contentType: obj, + body: formDataValues.map(({ key }) => `${key}: `).join("\n"), + } + : { contentType: obj, body: formDataValues } +} + +const parseOpenAPIV3BodyFormData = ( + contentType: "multipart/form-data" | "application/x-www-form-urlencoded", + mediaObj: OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject +): HoppRESTReqBody => { + const schema = mediaObj.schema as + | OpenAPIV3.SchemaObject + | OpenAPIV31.SchemaObject + | undefined + + if (!schema || schema.type !== "object") { + return contentType === "application/x-www-form-urlencoded" + ? { contentType, body: "" } + : { contentType, body: [] } + } + + const keys = Object.keys(schema.properties ?? {}) + + if (contentType === "application/x-www-form-urlencoded") { + return { + contentType, + body: keys.map((key) => `${key}: `).join("\n"), + } + } else { + return { + contentType, + body: keys.map( + (key) => + { key, value: "", isFile: false, active: true } + ), + } + } +} + +const parseOpenAPIV3Body = ( + op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject +): HoppRESTReqBody => { + const objs = Object.entries( + ( + op.requestBody as + | OpenAPIV3.RequestBodyObject + | OpenAPIV31.RequestBodyObject + | undefined + )?.content ?? {} + ) + + if (objs.length === 0) return { contentType: null, body: null } + + // We only take the first definition + const [contentType, media]: [ + string, + OpenAPIV3.MediaTypeObject | OpenAPIV31.MediaTypeObject + ] = objs[0] + + return contentType in knownContentTypes + ? contentType === "multipart/form-data" || + contentType === "application/x-www-form-urlencoded" + ? parseOpenAPIV3BodyFormData(contentType, media) + : { contentType: contentType as any, body: "" } + : { contentType: null, body: null } +} + +const isOpenAPIV3Operation = ( + doc: OpenAPI.Document, + op: OpenAPIOperationType +): op is OpenAPIV3.OperationObject | OpenAPIV31.OperationObject => + objectHasProperty(doc, "openapi") && + typeof doc.openapi === "string" && + doc.openapi.startsWith("3.") + +const parseOpenAPIBody = ( + doc: OpenAPI.Document, + op: OpenAPIOperationType +): HoppRESTReqBody => + isOpenAPIV3Operation(doc, op) + ? parseOpenAPIV3Body(op) + : parseOpenAPIV2Body(op) + +const resolveOpenAPIV3SecurityObj = ( + scheme: OpenAPIV3.SecuritySchemeObject | OpenAPIV31.SecuritySchemeObject, + _schemeData: string[] // Used for OAuth to pass params +): HoppRESTAuth => { + if (scheme.type === "http") { + if (scheme.scheme === "basic") { + // Basic + return { authType: "basic", authActive: true, username: "", password: "" } + } else if (scheme.scheme === "bearer") { + // Bearer + return { authType: "bearer", authActive: true, token: "" } + } else { + // Unknown/Unsupported Scheme + return { authType: "none", authActive: true } + } + } else if (scheme.type === "apiKey") { + if (scheme.in === "header") { + return { + authType: "api-key", + authActive: true, + addTo: "Headers", + key: scheme.name, + value: "", + } + } else if (scheme.in === "query") { + return { + authType: "api-key", + authActive: true, + addTo: "Query params", + key: scheme.in, + value: "", + } + } + } else if (scheme.type === "oauth2") { + // TODO: Implement Oauth + } + + return { authType: "none", authActive: true } +} + +const resolveOpenAPIV3SecurityScheme = ( + doc: OpenAPIV3.Document | OpenAPIV31.Document, + schemeName: string, + schemeData: string[] +): HoppRESTAuth => { + const scheme = doc.components?.securitySchemes?.[schemeName] as + | OpenAPIV3.SecuritySchemeObject + | undefined + + if (!scheme) return { authType: "none", authActive: true } + else return resolveOpenAPIV3SecurityObj(scheme, schemeData) +} + +const resolveOpenAPIV3Security = ( + doc: OpenAPIV3.Document | OpenAPIV31.Document, + security: + | OpenAPIV3.SecurityRequirementObject[] + | OpenAPIV31.SecurityRequirementObject[] +): HoppRESTAuth => { + // NOTE: Hoppscotch only considers the first security requirement + const sec = security[0] as OpenAPIV3.SecurityRequirementObject | undefined + + if (!sec) return { authType: "none", authActive: true } + + // NOTE: We only consider the first security condition within the first condition + const [schemeName, schemeData] = (Object.entries(sec)[0] ?? [ + undefined, + undefined, + ]) as [string | undefined, string[] | undefined] + + if (!schemeName || !schemeData) return { authType: "none", authActive: true } + + return resolveOpenAPIV3SecurityScheme(doc, schemeName, schemeData) +} + +const parseOpenAPIV3Auth = ( + doc: OpenAPIV3.Document | OpenAPIV31.Document, + op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject +): HoppRESTAuth => { + const rootAuth = doc.security + ? resolveOpenAPIV3Security(doc, doc.security) + : undefined + const opAuth = op.security + ? resolveOpenAPIV3Security(doc, op.security) + : undefined + + return opAuth ?? rootAuth ?? { authType: "none", authActive: true } +} + +const resolveOpenAPIV2SecurityScheme = ( + scheme: OpenAPIV2.SecuritySchemeObject, + _schemeData: string[] +): HoppRESTAuth => { + if (scheme.type === "basic") { + return { authType: "basic", authActive: true, username: "", password: "" } + } else if (scheme.type === "apiKey") { + // V2 only supports in: header and in: query + return { + authType: "api-key", + addTo: scheme.in === "header" ? "Headers" : "Query params", + authActive: true, + key: scheme.name, + value: "", + } + } else if (scheme.type === "oauth2") { + // TODO: Implement OAuth2 + } + + return { authType: "none", authActive: true } +} + +const resolveOpenAPIV2SecurityDef = ( + doc: OpenAPIV2.Document, + schemeName: string, + schemeData: string[] +): HoppRESTAuth => { + const scheme = Object.entries(doc.securityDefinitions ?? {}).find( + ([name]) => schemeName === name + ) + + if (!scheme) return { authType: "none", authActive: true } + + const schemeObj = scheme[1] + + return resolveOpenAPIV2SecurityScheme(schemeObj, schemeData) +} + +const resolveOpenAPIV2Security = ( + doc: OpenAPIV2.Document, + security: OpenAPIV2.SecurityRequirementObject[] +): HoppRESTAuth => { + // NOTE: Hoppscotch only considers the first security requirement + const sec = security[0] as OpenAPIV2.SecurityRequirementObject | undefined + + if (!sec) return { authType: "none", authActive: true } + + // NOTE: We only consider the first security condition within the first condition + const [schemeName, schemeData] = (Object.entries(sec)[0] ?? [ + undefined, + undefined, + ]) as [string | undefined, string[] | undefined] + + if (!schemeName || !schemeData) return { authType: "none", authActive: true } + + return resolveOpenAPIV2SecurityDef(doc, schemeName, schemeData) +} + +const parseOpenAPIV2Auth = ( + doc: OpenAPIV2.Document, + op: OpenAPIV2.OperationObject +): HoppRESTAuth => { + const rootAuth = doc.security + ? resolveOpenAPIV2Security(doc, doc.security) + : undefined + const opAuth = op.security + ? resolveOpenAPIV2Security(doc, op.security) + : undefined + + return opAuth ?? rootAuth ?? { authType: "none", authActive: true } +} + +const parseOpenAPIAuth = ( + doc: OpenAPI.Document, + op: OpenAPIOperationType +): HoppRESTAuth => + isOpenAPIV3Operation(doc, op) + ? parseOpenAPIV3Auth(doc as OpenAPIV3.Document | OpenAPIV31.Document, op) + : parseOpenAPIV2Auth(doc as OpenAPIV2.Document, op) + +const convertPathToHoppReqs = ( + doc: OpenAPI.Document, + pathName: string, + pathObj: OpenAPIPathInfoType +) => + pipe( + ["get", "head", "post", "put", "delete", "options"] as const, + + // Filter and map out path info + RA.filterMap( + flow( + O.fromPredicate((method) => !!pathObj[method]), + O.map((method) => ({ method, info: pathObj[method]! })) + ) + ), + + // Construct request object + RA.map(({ method, info }) => + makeRESTRequest({ + name: info.operationId ?? "Untitled Request", + method, + endpoint: `<>${replaceOpenApiPathTemplating(pathName)}`, // TODO: Make this proper + + // We don't need to worry about reference types as the Dereferencing pass should remove them + params: parseOpenAPIParams( + (info.parameters as OpenAPIParamsType[]) ?? [] + ), + headers: parseOpenAPIHeaders( + (info.parameters as OpenAPIParamsType[]) ?? [] + ), + + auth: parseOpenAPIAuth(doc, info), + + body: parseOpenAPIBody(doc, info), + + preRequestScript: "", + testScript: "", + }) + ), + + // Disable Readonly + RA.toArray + ) + +const convertOpenApiDocToHopp = ( + doc: OpenAPI.Document +): TE.TaskEither> => { + const name = doc.info.title + + const paths = Object.entries(doc.paths ?? {}) + .map(([pathName, pathObj]) => convertPathToHoppReqs(doc, pathName, pathObj)) + .flat() + + return TE.of( + makeCollection({ + name, + folders: [], + requests: paths, + }) + ) +} + +export default defineImporter({ + name: "Swagger/OpenAPI v3 Schema", + steps: [ + step({ + stepName: "FILE_OR_URL_IMPORT", + metadata: { + acceptedFileTypes: "application/json", + }, + }), + ] as const, + importer: ([fileContent]) => + pipe( + // See if we can parse JSON properly + fileContent, + safeParseJSON, + TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT), + + // Try validating, else the importer is invalid file format + TE.chainW((obj) => + pipe( + TE.tryCatch( + () => SwaggerParser.validate(obj), + () => IMPORTER_INVALID_FILE_FORMAT + ) + ) + ), + + // Deference the references + TE.chainW((obj) => + pipe( + TE.tryCatch( + () => SwaggerParser.dereference(obj), + () => OPENAPI_DEREF_ERROR + ) + ) + ), + + TE.chainW(convertOpenApiDocToHopp) + ), +}) diff --git a/packages/hoppscotch-app/package.json b/packages/hoppscotch-app/package.json index c9b2c1e16..b976317ac 100644 --- a/packages/hoppscotch-app/package.json +++ b/packages/hoppscotch-app/package.json @@ -33,6 +33,7 @@ "gql-codegen": "graphql-codegen --config gql-codegen.yml" }, "dependencies": { + "@apidevtools/swagger-parser": "^10.0.3", "@apollo/client": "^3.5.6", "@codemirror/autocomplete": "^0.19.9", "@codemirror/closebrackets": "^0.19.0", @@ -88,6 +89,7 @@ "mustache": "^4.2.0", "node-interval-tree": "^1.3.3", "nuxt": "^2.15.8", + "openapi-types": "^10.0.0", "paho-mqtt": "^1.1.0", "rxjs": "^7.5.1", "socket.io-client-v2": "npm:socket.io-client@^2.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ace7077a6..f8e3735d7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,7 @@ importers: packages/hoppscotch-app: specifiers: + '@apidevtools/swagger-parser': ^10.0.3 '@apollo/client': ^3.5.6 '@babel/core': ^7.16.7 '@babel/preset-env': ^7.16.7 @@ -144,6 +145,7 @@ importers: npm-run-all: ^4.1.5 nuxt: ^2.15.8 nuxt-windicss: ^2.2.2 + openapi-types: ^10.0.0 paho-mqtt: ^1.1.0 prettier: ^2.5.1 raw-loader: ^4.0.2 @@ -177,6 +179,7 @@ importers: worker-loader: ^3.0.8 yargs-parser: ^21.0.0 dependencies: + '@apidevtools/swagger-parser': 10.0.3_openapi-types@10.0.0 '@apollo/client': 3.5.6_f3f7eb5e21785ef7d5faca94c1a68824 '@codemirror/autocomplete': 0.19.9 '@codemirror/closebrackets': 0.19.0 @@ -232,6 +235,7 @@ importers: mustache: 4.2.0 node-interval-tree: 1.3.3 nuxt: 2.15.8_typescript@4.5.4 + openapi-types: 10.0.0 paho-mqtt: 1.1.0 rxjs: 7.5.1 socket.io-client-v2: /socket.io-client/2.4.0 @@ -372,6 +376,38 @@ packages: dependencies: '@types/throttle-debounce': 2.1.0 + /@apidevtools/json-schema-ref-parser/9.0.9: + resolution: {integrity: sha512-GBD2Le9w2+lVFoc4vswGI/TjkNIZSVp7+9xPf+X3uidBfWnAeUWmquteSyt0+VCrhNMWj/FTABISQrD3Z/YA+w==} + dependencies: + '@jsdevtools/ono': 7.1.3 + '@types/json-schema': 7.0.9 + call-me-maybe: 1.0.1 + js-yaml: 4.1.0 + dev: false + + /@apidevtools/openapi-schemas/2.1.0: + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + dev: false + + /@apidevtools/swagger-methods/3.0.2: + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + dev: false + + /@apidevtools/swagger-parser/10.0.3_openapi-types@10.0.0: + resolution: {integrity: sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==} + peerDependencies: + openapi-types: '>=7' + dependencies: + '@apidevtools/json-schema-ref-parser': 9.0.9 + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@jsdevtools/ono': 7.1.3 + call-me-maybe: 1.0.1 + openapi-types: 10.0.0 + z-schema: 5.0.2 + dev: false + /@apollo/client/3.5.6_f3f7eb5e21785ef7d5faca94c1a68824: resolution: {integrity: sha512-XHoouuEJ4L37mtfftcHHO1caCRrKKAofAwqRoq28UQIPMJk+e7n3X9OtRRNXKk/9tmhNkwelSary+EilfPwI7A==} peerDependencies: @@ -3413,6 +3449,10 @@ packages: chalk: 4.1.2 dev: true + /@jsdevtools/ono/7.1.3: + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} + dev: false + /@lezer/common/0.15.10: resolution: {integrity: sha512-vlr+be73zTDoQBIknBVOh/633tmbQcjxUu9PIeVeYESeBK3V6TuBW96RRFg93Y2cyK9lglz241gOgSn452HFvA==} dev: false @@ -3662,11 +3702,11 @@ packages: ufo: 0.7.9 dev: false - /@nuxt/kit-edge/3.0.0-27338323.1e98259: - resolution: {integrity: sha512-lABruok/JkKOoEJNpkZ2M6YMSKBgOaWAyWjDq+Ekk4Ga/FZgok+epTcN3cQBOP/MpH3PXsM2L3zdM8DoEMxeug==} + /@nuxt/kit-edge/3.0.0-27356801.e9128f3: + resolution: {integrity: sha512-hGiqZydtMiK+UhHBsdD0WC+fHeI3xyJixhY2rNPGwFrzdeTrbUy8YH451SbRPOJ8TS0RYQ+cUT6JfMr0YTpnfg==} engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0} dependencies: - '@nuxt/schema': /@nuxt/schema-edge/3.0.0-27338323.1e98259 + '@nuxt/schema': /@nuxt/schema-edge/3.0.0-27356801.e9128f3 consola: 2.15.3 defu: 5.0.0 dotenv: 10.0.0 @@ -3704,8 +3744,8 @@ packages: node-fetch: 2.6.6 dev: false - /@nuxt/schema-edge/3.0.0-27338323.1e98259: - resolution: {integrity: sha512-TV8HMFEkD2cMLnsYBE0+wQcOSwElw6SW4rnDOFoDhHI61GeflpGZOfXLQRJS2FLAyMv/QVeBHdd+PWWOxDqGgA==} + /@nuxt/schema-edge/3.0.0-27356801.e9128f3: + resolution: {integrity: sha512-LOi5OLFzxrHL7t/a7sn2+fnkNN8y9ipnLG6l6McI+vEuzXi11YeHCYt7PmoqqySXx411hd6lISmYd/aZzBgYkg==} engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0} dependencies: create-require: 1.1.1 @@ -5843,7 +5883,6 @@ packages: /argparse/2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true /aria-query/5.0.0: resolution: {integrity: sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==} @@ -6702,6 +6741,10 @@ packages: function-bind: 1.1.1 get-intrinsic: 1.1.1 + /call-me-maybe/1.0.1: + resolution: {integrity: sha1-JtII6onje1y95gJQoV8DHBak1ms=} + dev: false + /caller-callsite/2.0.0: resolution: {integrity: sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=} engines: {node: '>=4'} @@ -11875,7 +11918,6 @@ packages: hasBin: true dependencies: argparse: 2.0.1 - dev: true /jsdom/16.7.0: resolution: {integrity: sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==} @@ -12374,6 +12416,10 @@ packages: resolution: {integrity: sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=} dev: true + /lodash.isequal/4.5.0: + resolution: {integrity: sha1-QVxEePK8wwEgwizhDtMib30+GOA=} + dev: false + /lodash.isinteger/4.0.4: resolution: {integrity: sha1-YZwK89A/iwTDH1iChAt3sRzWg0M=} dev: true @@ -13320,7 +13366,7 @@ packages: /nuxt-windicss/2.2.2: resolution: {integrity: sha512-4tvzk9d2TUFxloty187D+wmO8ZNAvpSmRJ5HQO3/AvZdMMhAl4gomXR9fCgFKQe7Fxcj9nIKNInOx8TWowjiqA==} dependencies: - '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27338323.1e98259 + '@nuxt/kit': /@nuxt/kit-edge/3.0.0-27356801.e9128f3 '@windicss/plugin-utils': 1.6.1 consola: 2.15.3 defu: 5.0.0 @@ -13475,6 +13521,10 @@ packages: is-wsl: 2.2.0 dev: true + /openapi-types/10.0.0: + resolution: {integrity: sha512-Y8xOCT2eiKGYDzMW9R4x5cmfc3vGaaI4EL2pwhDmodWw1HlK18YcZ4uJxc7Rdp7/gGzAygzH9SXr6GKYIXbRcQ==} + dev: false + /opener/1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true @@ -17666,6 +17716,11 @@ packages: spdx-expression-parse: 3.0.1 dev: true + /validator/13.7.0: + resolution: {integrity: sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==} + engines: {node: '>= 0.10'} + dev: false + /value-or-promise/1.0.11: resolution: {integrity: sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==} engines: {node: '>=12'} @@ -18502,6 +18557,18 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + /z-schema/5.0.2: + resolution: {integrity: sha512-40TH47ukMHq5HrzkeVE40Ad7eIDKaRV2b+Qpi2prLc9X9eFJFzV7tMe5aH12e6avaSS/u5l653EQOv+J9PirPw==} + engines: {node: '>=8.0.0'} + hasBin: true + dependencies: + lodash.get: 4.4.2 + lodash.isequal: 4.5.0 + validator: 13.7.0 + optionalDependencies: + commander: 2.20.3 + dev: false + /zen-observable-ts/1.2.3: resolution: {integrity: sha512-hc/TGiPkAWpByykMwDcem3SdUgA4We+0Qb36bItSuJC9xD0XVBZoFHYoadAomDSNf64CG8Ydj0Qb8Od8BUWz5g==} dependencies: