UNPKG

@kubb/oas

Version:

OpenAPI Specification (OAS) utilities and helpers for Kubb, providing parsing, normalization, and manipulation of OpenAPI/Swagger schemas.

367 lines (361 loc) • 11.4 kB
'use strict'; var utils = require('oas/utils'); var types = require('oas/types'); var remeda = require('remeda'); var openapiCore = require('@redocly/openapi-core'); var OASNormalize = require('oas-normalize'); var swagger2openapi = require('swagger2openapi'); var jsonpointer = require('jsonpointer'); var BaseOas = require('oas'); function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; } var OASNormalize__default = /*#__PURE__*/_interopDefault(OASNormalize); var swagger2openapi__default = /*#__PURE__*/_interopDefault(swagger2openapi); var jsonpointer__default = /*#__PURE__*/_interopDefault(jsonpointer); var BaseOas__default = /*#__PURE__*/_interopDefault(BaseOas); // src/types.ts var HttpMethods = { GET: "get", POST: "post", PUT: "put", PATCH: "patch", DELETE: "delete", HEAD: "head", OPTIONS: "options", TRACE: "trace" }; var Oas = class extends BaseOas__default.default { #options = { discriminator: "strict" }; document = void 0; constructor({ oas, user }) { if (typeof oas === "string") { oas = JSON.parse(oas); } super(oas, user); this.document = oas; } setOptions(options) { this.#options = options; } get options() { return this.#options; } get($ref) { const origRef = $ref; $ref = $ref.trim(); if ($ref === "") { return false; } if ($ref.startsWith("#")) { $ref = globalThis.decodeURIComponent($ref.substring(1)); } else { return null; } const current = jsonpointer__default.default.get(this.api, $ref); if (!current) { throw new Error(`Could not find a definition for ${origRef}.`); } return current; } getKey($ref) { const key = $ref.split("/").pop(); return key === "" ? void 0 : key; } set($ref, value) { $ref = $ref.trim(); if ($ref === "") { return false; } if ($ref.startsWith("#")) { $ref = globalThis.decodeURIComponent($ref.substring(1)); jsonpointer__default.default.set(this.api, $ref, value); } } getDiscriminator(schema) { if (!isDiscriminator(schema)) { return void 0; } const { mapping = {}, propertyName } = schema.discriminator; if (this.#options.discriminator === "inherit") { Object.entries(mapping).forEach(([mappingKey, mappingValue]) => { if (mappingValue) { const childSchema = this.get(mappingValue); if (!childSchema.properties) { childSchema.properties = {}; } const property = childSchema.properties[propertyName]; if (childSchema.properties) { childSchema.properties[propertyName] = { ...childSchema.properties ? childSchema.properties[propertyName] : {}, enum: [...property?.enum?.filter((value) => value !== mappingKey) ?? [], mappingKey] }; childSchema.required = [...childSchema.required ?? [], propertyName]; this.set(mappingValue, childSchema); } } }); } if (schema.oneOf) { schema.oneOf.forEach((schema2) => { if (isReference(schema2)) { const key = this.getKey(schema2.$ref); const refSchema = this.get(schema2.$ref); const propertySchema = refSchema.properties?.[propertyName]; const canAdd = key && !Object.values(mapping).includes(schema2.$ref); if (canAdd && propertySchema?.enum?.length === 1) { mapping[propertySchema.enum[0]] = schema2.$ref; } else if (canAdd) { mapping[key] = schema2.$ref; } } }); } if (schema.anyOf) { schema.anyOf.forEach((schema2) => { if (isReference(schema2)) { const key = this.getKey(schema2.$ref); const refSchema = this.get(schema2.$ref); const propertySchema = refSchema.properties?.[propertyName]; const canAdd = key && !Object.values(mapping).includes(schema2.$ref); if (canAdd && propertySchema?.enum?.length === 1) { mapping[propertySchema.enum[0]] = schema2.$ref; } else if (canAdd) { mapping[key] = schema2.$ref; } } }); } return { ...schema.discriminator, mapping }; } // TODO add better typing dereferenceWithRef(schema) { if (isReference(schema)) { return { ...schema, ...this.get(schema.$ref), $ref: schema.$ref }; } return schema; } /** * Oas does not have a getResponseBody(contentType) */ #getResponseBodyFactory(responseBody) { function hasResponseBody(res = responseBody) { return !!res; } return (contentType) => { if (!hasResponseBody(responseBody)) { return false; } if (isReference(responseBody)) { return false; } if (!responseBody.content) { return false; } if (contentType) { if (!(contentType in responseBody.content)) { return false; } return responseBody.content[contentType]; } let availablecontentType; const contentTypes = Object.keys(responseBody.content); contentTypes.forEach((mt) => { if (!availablecontentType && utils.matchesMimeType.json(mt)) { availablecontentType = mt; } }); if (!availablecontentType) { contentTypes.forEach((mt) => { if (!availablecontentType) { availablecontentType = mt; } }); } if (availablecontentType) { return [availablecontentType, responseBody.content[availablecontentType], ...responseBody.description ? [responseBody.description] : []]; } return false; }; } getResponseSchema(operation, statusCode) { if (operation.schema.responses) { Object.keys(operation.schema.responses).forEach((key) => { const schema2 = operation.schema.responses[key]; const $ref = isReference(schema2) ? schema2.$ref : void 0; if (schema2 && $ref) { operation.schema.responses[key] = this.get($ref); } }); } const getResponseBody = this.#getResponseBodyFactory(operation.getResponseByStatusCode(statusCode)); const { contentType } = this.#options; const responseBody = getResponseBody(contentType); if (responseBody === false) { return {}; } const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema; if (!schema) { return {}; } return this.dereferenceWithRef(schema); } getRequestSchema(operation) { const { contentType } = this.#options; if (operation.schema.requestBody) { operation.schema.requestBody = this.dereferenceWithRef(operation.schema.requestBody); } const requestBody = operation.getRequestBody(contentType); if (requestBody === false) { return void 0; } const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema; if (!schema) { return void 0; } return this.dereferenceWithRef(schema); } getParametersSchema(operation, inKey) { const { contentType = operation.getContentType() } = this.#options; const params = operation.getParameters().map((schema) => { return this.dereferenceWithRef(schema); }).filter((v) => v.in === inKey); if (!params.length) { return null; } return params.reduce( (schema, pathParameters) => { const property = pathParameters.content?.[contentType]?.schema ?? pathParameters.schema; const required = [...schema.required || [], pathParameters.required ? pathParameters.name : void 0].filter(Boolean); return { ...schema, description: schema.description, deprecated: schema.deprecated, example: schema.example, required, properties: { ...schema.properties, [pathParameters.name]: { description: pathParameters.description, ...property } } }; }, { type: "object", required: [], properties: {} } ); } async valdiate() { const oasNormalize = new OASNormalize__default.default(this.api, { enablePaths: true, colorizeErrors: true }); return oasNormalize.validate({ parser: { validate: { errors: { colorize: true } } } }); } }; // src/utils.ts function isOpenApiV2Document(doc) { return doc && remeda.isPlainObject(doc) && !("openapi" in doc); } function isOpenApiV3_1Document(doc) { return doc && remeda.isPlainObject(doc) && "openapi" in doc && doc.openapi.startsWith("3.1"); } function isParameterObject(obj) { return obj && "in" in obj; } function isNullable(schema) { return schema?.nullable ?? schema?.["x-nullable"] ?? false; } function isReference(obj) { return !!obj && types.isRef(obj); } function isDiscriminator(obj) { return !!obj && obj?.["discriminator"] && typeof obj.discriminator !== "string"; } function isRequired(schema) { if (!schema) { return false; } return Array.isArray(schema.required) ? !!schema.required?.length : !!schema.required; } function isOptional(schema) { return !isRequired(schema); } async function parse(pathOrApi, { oasClass = Oas, canBundle = true, enablePaths = true } = {}) { if (typeof pathOrApi === "string" && canBundle) { const config = await openapiCore.loadConfig(); const bundleResults = await openapiCore.bundle({ ref: pathOrApi, config, base: pathOrApi }); return parse(bundleResults.bundle.parsed); } const oasNormalize = new OASNormalize__default.default(pathOrApi, { enablePaths, colorizeErrors: true }); const document = await oasNormalize.load(); if (isOpenApiV2Document(document)) { const { openapi } = await swagger2openapi__default.default.convertObj(document, { anchors: true }); return new oasClass({ oas: openapi }); } return new oasClass({ oas: document }); } async function merge(pathOrApi, { oasClass = Oas } = {}) { const instances = await Promise.all(pathOrApi.map((p) => parse(p, { oasClass, enablePaths: false, canBundle: false }))); if (instances.length === 0) { throw new Error("No OAS instances provided for merging."); } const merged = instances.reduce( (acc, current) => { return remeda.mergeDeep(acc, current.document); }, { openapi: "3.0.0", info: { title: "Merged API", version: "1.0.0" }, paths: {}, components: { schemas: {} } } ); return parse(merged, { oasClass }); } Object.defineProperty(exports, "findSchemaDefinition", { enumerable: true, get: function () { return utils.findSchemaDefinition; } }); Object.defineProperty(exports, "matchesMimeType", { enumerable: true, get: function () { return utils.matchesMimeType; } }); exports.HttpMethods = HttpMethods; exports.Oas = Oas; exports.isDiscriminator = isDiscriminator; exports.isNullable = isNullable; exports.isOpenApiV3_1Document = isOpenApiV3_1Document; exports.isOptional = isOptional; exports.isParameterObject = isParameterObject; exports.isReference = isReference; exports.isRequired = isRequired; exports.merge = merge; exports.parse = parse; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map