UNPKG

oas

Version:

Comprehensive tooling for working with OpenAPI definitions

1,424 lines (1,411 loc) 67.6 kB
import { applyDiscriminatorOneOfToUsedSchemas, cloneObject, collectRefsInSchema, decorateComponentSchemasWithRefName, dereferenceRef, dereferenceRefDeep, filterRequiredRefsToReferenced, getDereferencingOptions, getParametersAsJSONSchema, getSchemaVersionString, isObject, isPrimitive, matches_mimetype_default, mergeReferencedSchemasIntoRoot, supportedMethods, toJSONSchema } from "./chunk-65ZD6K3I.js"; import { getExtension } from "./chunk-S27IGTVG.js"; import { isRef } from "./chunk-PSNTODZL.js"; // src/operation/index.ts import { $RefParser } from "@apidevtools/json-schema-ref-parser"; // src/operation/lib/dedupe-common-parameters.ts function dedupeCommonParameters(parameters, commonParameters) { return commonParameters.filter((param) => { return !parameters.find((param2) => { if (param.name && param2.name) { return param.name === param2.name && param.in === param2.in; } else if (isRef(param) && isRef(param2)) { return param.$ref === param2.$ref; } return false; }); }); } // src/samples/index.ts import mergeJSONSchemaAllOf from "json-schema-merge-allof"; import memoize from "memoizee"; // src/samples/utils.ts function usesPolymorphism(schema) { if (schema.oneOf) { return "oneOf"; } else if (schema.anyOf) { return "anyOf"; } else if (schema.allOf) { return "allOf"; } return false; } function objectify(thing) { if (!isObject(thing)) { return {}; } return thing; } function normalizeArray(arr) { if (Array.isArray(arr)) { return arr; } return [arr]; } function isFunc(thing) { return typeof thing === "function"; } function deeplyStripKey(input, keyToStrip, predicate) { if (typeof input !== "object" || Array.isArray(input) || input === null || !keyToStrip) { return input; } const obj = { ...input }; Object.keys(obj).forEach((k) => { if (k === keyToStrip && predicate?.(obj[k], k)) { delete obj[k]; return; } obj[k] = deeplyStripKey(obj[k], keyToStrip, predicate); }); return obj; } // src/samples/index.ts var sampleDefaults = (genericSample) => { return (schema) => typeof schema.default === typeof genericSample ? schema.default : genericSample; }; var primitives = { string: sampleDefaults("string"), string_email: sampleDefaults("user@example.com"), "string_date-time": sampleDefaults((/* @__PURE__ */ new Date()).toISOString()), string_date: sampleDefaults((/* @__PURE__ */ new Date()).toISOString().substring(0, 10)), "string_YYYY-MM-DD": sampleDefaults((/* @__PURE__ */ new Date()).toISOString().substring(0, 10)), string_uuid: sampleDefaults("3fa85f64-5717-4562-b3fc-2c963f66afa6"), string_hostname: sampleDefaults("example.com"), string_ipv4: sampleDefaults("198.51.100.42"), string_ipv6: sampleDefaults("2001:0db8:5b96:0000:0000:426f:8e17:642a"), number: sampleDefaults(0), number_float: sampleDefaults(0), integer: sampleDefaults(0), boolean: sampleDefaults(true) }; var primitive = (schema) => { const objectifiedSchema = objectify(schema); const { format } = objectifiedSchema; let { type } = objectifiedSchema; if (type === "null") { return null; } else if (Array.isArray(type)) { if (type.length === 1) { type = type[0]; } else { if (type.includes("null")) { type = type.filter((t) => t !== "null"); } type = type.shift(); } } const fn = primitives[`${type}_${format}`] || primitives[type]; if (isFunc(fn)) { return fn(objectifiedSchema); } return `Unknown Type: ${objectifiedSchema.type}`; }; function sampleFromSchema(schema, opts = {}) { const seenRefs = opts.seenRefs || /* @__PURE__ */ new Set(); let objectifySchema = objectify(schema); let refToRelease; if (opts.definition && isRef(objectifySchema)) { refToRelease = objectifySchema.$ref; if (seenRefs.has(refToRelease)) { return void 0; } objectifySchema = dereferenceRef(objectifySchema, opts.definition, seenRefs); if (!objectifySchema || isRef(objectifySchema)) { return void 0; } } try { return sampleFromResolvedSchema(objectifySchema, opts, seenRefs); } finally { if (refToRelease) { seenRefs.delete(refToRelease); } } } function sampleFromResolvedSchema(schema, opts, seenRefs) { let { type } = schema; const hasPolymorphism = usesPolymorphism(schema); if (hasPolymorphism === "allOf") { try { const definition = opts.definition; const resolvedAllOf = schema.allOf.map((subSchema) => { let sub = objectify(subSchema); if (definition && isRef(sub)) { const resolved = dereferenceRef(sub, definition, /* @__PURE__ */ new Set()); if (resolved && !isRef(resolved)) { sub = resolved; } } return sub; }); return sampleFromSchema( mergeJSONSchemaAllOf( { ...schema, allOf: resolvedAllOf }, { resolvers: { // Ignore any unrecognized OAS-specific keywords that might be present on the schema // (like `xml`). defaultResolver: mergeJSONSchemaAllOf.options.resolvers.title } } ), { ...opts, seenRefs } ); } catch { return; } } else if (hasPolymorphism) { const samples = schema[hasPolymorphism].map((s) => { return sampleFromSchema(s, { ...opts, seenRefs }); }); if (samples.length === 1) { return samples[0]; } else if (samples.some((s) => s === null)) { return samples.find((s) => s !== null); } return samples[0]; } const { example, additionalProperties, properties, items } = schema; const { includeReadOnly, includeWriteOnly } = opts; if (example !== void 0) { const cleanedExample = deeplyStripKey(example, "$$ref", (val) => { return typeof val === "string" && val.indexOf("#") > -1; }); return dereferenceRefDeep(cleanedExample, opts.definition, seenRefs); } if (!type) { if (properties || additionalProperties) { type = "object"; } else if (items) { type = "array"; } else { return; } } if (type === "object" || Array.isArray(type) && type.includes("object")) { const props = objectify(properties); const obj = {}; for (const name in props) { if (props?.[name].deprecated) { continue; } if (props?.[name].readOnly && !includeReadOnly) { continue; } if (props?.[name].writeOnly && !includeWriteOnly) { continue; } if (props[name].examples?.length) { obj[name] = props[name].examples[0]; continue; } obj[name] = sampleFromSchema(props[name], { ...opts, seenRefs }); } if (additionalProperties === true) { obj.additionalProp = {}; } else if (additionalProperties) { const additionalProps = objectify(additionalProperties); const additionalPropVal = sampleFromSchema(additionalProps, { ...opts, seenRefs }); obj.additionalProp = additionalPropVal; } return obj; } if (type === "array" || Array.isArray(type) && type.includes("array")) { if (typeof items === "undefined") { return []; } if (Array.isArray(items.anyOf)) { return items.anyOf.map( (i) => sampleFromSchema(i, { ...opts, seenRefs }) ); } if (Array.isArray(items.oneOf)) { return items.oneOf.map( (i) => sampleFromSchema(i, { ...opts, seenRefs }) ); } return [sampleFromSchema(items, { ...opts, seenRefs })]; } if (schema.enum) { if (schema.default) { return schema.default; } return normalizeArray(schema.enum)[0]; } if (type === "file") { return; } return primitive(schema); } var memo = memoize(sampleFromSchema); var samples_default = memo; // src/operation/lib/get-mediatype-examples.ts function getMediaTypeExamples(mediaType, mediaTypeObject, definition, opts = {}) { if (mediaTypeObject.example) { mediaTypeObject.example = dereferenceRefDeep(mediaTypeObject.example, definition); if (mediaTypeObject.example === void 0 || collectRefsInSchema(mediaTypeObject.example).size > 0) { return []; } return [ { value: mediaTypeObject.example } ]; } else if (mediaTypeObject.examples) { const { examples } = mediaTypeObject; const multipleExamples = Object.keys(examples).map((key) => { let summary = key; let description; let example = examples[key]; if (example !== null && typeof example === "object") { if (isRef(example)) { example = dereferenceRef(example, definition); if (!example || isRef(example)) { return false; } } if ("summary" in example) { summary = example.summary; } if ("description" in example) { description = example.description; } if ("value" in example) { example.value = dereferenceRefDeep(example.value, definition); if (example.value === void 0 || collectRefsInSchema(example.value).size > 0) { return false; } example = example.value; } } const ret = { summary, title: key, value: example }; if (description) { ret.description = description; } return ret; }).filter((item) => item !== false); if (multipleExamples.length) { return multipleExamples; } } if (mediaTypeObject.schema) { if (!matches_mimetype_default.xml(mediaType)) { return [ { value: samples_default(structuredClone(mediaTypeObject.schema), { ...opts, definition }) } ]; } } return []; } // src/operation/lib/get-response-examples.ts function getResponseExamples(operation, definition) { return Object.keys(operation.responses || {}).map((status) => { let response = operation.responses?.[status]; let onlyHeaders = false; if (!response) return false; if (isRef(response)) { response = dereferenceRef(response, definition); if (!response || isRef(response)) return false; } const mediaTypes = {}; (response?.content ? Object.keys(response.content) : []).forEach((mediaType) => { if (!mediaType) return; const mediaTypeObject = response.content?.[mediaType]; if (!mediaTypeObject) return; const examples = getMediaTypeExamples(mediaType, mediaTypeObject, definition, { includeReadOnly: true, includeWriteOnly: false }); if (examples) { mediaTypes[mediaType] = examples; } }); if (response.headers && Object.keys(response.headers).length && !Object.keys(mediaTypes).length) { mediaTypes["*/*"] = []; onlyHeaders = true; } if (!Object.keys(mediaTypes).length) { return false; } return { status, mediaTypes, ...onlyHeaders ? { onlyHeaders } : {} }; }).filter((item) => item !== false); } // src/operation/lib/get-callback-examples.ts function getCallbackExamples(operation, definition) { if (!operation.callbacks) { return []; } const examples = Object.keys(operation.callbacks).map((identifier) => { let callback = operation.callbacks?.[identifier]; if (!callback) return []; if (isRef(callback)) { callback = dereferenceRef(callback, definition); if (!callback || isRef(callback)) return []; } const items = Object.keys(callback).map((expression) => { let callbackPath = callback[expression]; if (!callbackPath) return []; if (isRef(callbackPath)) { callbackPath = dereferenceRef(callbackPath, definition); if (!callbackPath || isRef(callbackPath)) return []; } return Object.keys(callbackPath).map((method) => { if (["servers", "parameters", "summary", "description"].includes(method)) { return false; } const pathItem = callbackPath; const example = getResponseExamples(pathItem[method], definition); if (!example.length) return false; return { identifier, expression, method, example }; }); }); return items.flat().filter((item) => item !== false); }); return examples.flat(); } // src/operation/lib/get-example-groups.ts var noCorrespondingResponseKey = "NoCorrespondingResponseForCustomCodeSample"; function addMatchingResponseExamples(groups, operation) { operation.getResponseExamples().forEach((example) => { Object.entries(example.mediaTypes || {}).forEach(([mediaType, mediaTypeExamples]) => { mediaTypeExamples.forEach((mediaTypeExample) => { if (mediaTypeExample.title && Object.keys(groups).includes(mediaTypeExample.title)) { groups[mediaTypeExample.title].response = { mediaType, mediaTypeExample, status: example.status }; if (!groups[mediaTypeExample.title].name) { groups[mediaTypeExample.title].name = mediaTypeExample.summary || mediaTypeExample.title; } } }); }); }); } function getDefaultName(sample, count) { return sample.name && sample.name.length > 0 ? sample.name : `Default${count[sample.language] > 1 ? ` #${count[sample.language]}` : ""}`; } function getExampleGroups(operation) { const namelessCodeSampleCounts = {}; const groups = {}; const codeSamples = getExtension("code-samples", operation.api, operation); codeSamples?.forEach((sample, i) => { if (namelessCodeSampleCounts[sample.language]) { namelessCodeSampleCounts[sample.language] += 1; } else { namelessCodeSampleCounts[sample.language] = 1; } const name = getDefaultName(sample, namelessCodeSampleCounts); if (sample.correspondingExample) { if (groups[sample.correspondingExample]?.customCodeSamples?.length) { groups[sample.correspondingExample].customCodeSamples.push({ ...sample, name, originalIndex: i }); } else if (sample.correspondingExample) { groups[sample.correspondingExample] = { name, customCodeSamples: [{ ...sample, name, originalIndex: i }] }; } } else if (groups[noCorrespondingResponseKey]?.customCodeSamples?.length) { groups[noCorrespondingResponseKey].customCodeSamples.push({ ...sample, name, originalIndex: i }); } else { groups[noCorrespondingResponseKey] = { name, customCodeSamples: [{ ...sample, name, originalIndex: i }] }; } }); if (Object.keys(groups).length) { addMatchingResponseExamples(groups, operation); return groups; } operation.getParameters().forEach((param) => { Object.entries(param.examples || {}).forEach(([exampleKey, paramExample]) => { let example = paramExample; if (isRef(example)) { example = dereferenceRef(example, operation.api); if (!example || isRef(example)) return; } groups[exampleKey] = { ...groups[exampleKey], name: groups[exampleKey]?.name || example.summary || exampleKey, request: { ...groups[exampleKey]?.request, [param.in]: { ...groups[exampleKey]?.request?.[param.in], [param.name]: example.value } } }; }); }); operation.getRequestBodyExamples().forEach((requestExample) => { requestExample.examples.forEach((mediaTypeExample) => { if (mediaTypeExample.title) { const mediaType = requestExample.mediaType === "application/x-www-form-urlencoded" ? "formData" : "body"; groups[mediaTypeExample.title] = { ...groups[mediaTypeExample.title], name: groups[mediaTypeExample.title]?.name || mediaTypeExample.summary || mediaTypeExample.title, request: { ...groups[mediaTypeExample.title]?.request, [mediaType]: mediaTypeExample.value } }; } }); }); if (Object.keys(groups).length) { addMatchingResponseExamples(groups, operation); } Object.entries(groups).forEach(([groupId, group]) => { if (group.request && !group.response) { delete groups[groupId]; } }); return groups; } // src/operation/lib/get-requestbody-examples.ts function getRequestBodyExamples(operation, definition) { let requestBody = operation.requestBody; if (!requestBody) { return []; } else if (isRef(requestBody)) { requestBody = dereferenceRef(requestBody, definition); } if (!requestBody || isRef(requestBody) || !requestBody.content) { return []; } return Object.keys(requestBody.content || {}).map((mediaType) => { const mediaTypeObject = requestBody.content[mediaType]; const examples = getMediaTypeExamples(mediaType, mediaTypeObject, definition, { includeReadOnly: false, includeWriteOnly: true }); if (!examples.length) { return false; } return { mediaType, examples }; }).filter((item) => item !== false); } // src/operation/lib/operationId.ts function hasOperationId(operation) { return Boolean("operationId" in operation && operation.operationId?.length); } function getOperationId(path, method, operation, opts = {}) { function sanitize(id) { return id.replace(opts?.camelCase || opts?.friendlyCase ? /[^a-zA-Z0-9_]/g : /[^a-zA-Z0-9]/g, "-").replace(/--+/g, "-").replace(/^-|-$/g, ""); } const operationIdExists = hasOperationId(operation); let operationId; if (operationIdExists) { operationId = operation.operationId; } else { operationId = sanitize(path).toLowerCase(); } const currMethod = method.toLowerCase(); if (opts?.camelCase || opts?.friendlyCase) { if (opts?.friendlyCase) { operationId = operationId.replaceAll("_", " "); if (!operationIdExists) { operationId = operationId.replace(/[^a-zA-Z0-9_]+(.)/g, (_, chr) => ` ${chr}`).split(" ").filter((word, i, arr) => word !== arr[i - 1]).join(" "); } } operationId = operationId.replace(/[^a-zA-Z0-9_]+(.)/g, (_, chr) => chr.toUpperCase()); if (operationIdExists) { operationId = sanitize(operationId); } operationId = operationId.replace(/^[0-9]/g, (match) => `_${match}`); operationId = operationId.charAt(0).toLowerCase() + operationId.slice(1); if (operationId.startsWith(currMethod)) { return operationId; } if (operationIdExists) { return operationId; } operationId = operationId.charAt(0).toUpperCase() + operationId.slice(1); return `${currMethod}${operationId}`; } else if (operationIdExists) { return operationId; } return `${currMethod}_${operationId}`; } // src/operation/transformers/get-response-as-json-schema.ts var isJSON = matches_mimetype_default.json; function buildHeadersSchema(response, schemaOptions) { const headersSchema = { type: "object", properties: {} }; const api = schemaOptions.definition; const seenRefs = schemaOptions.seenRefs ?? /* @__PURE__ */ new Set(); if (response.headers) { Object.keys(response.headers).forEach((key) => { let headerEntry = response.headers?.[key]; if (!headerEntry) return; if (isRef(headerEntry)) { headerEntry = dereferenceRef(headerEntry, api, seenRefs); if (!headerEntry || isRef(headerEntry)) return; } if (headerEntry.schema) { const header = headerEntry; let headerSchema = header.schema; if (!headerSchema) return; if (isRef(headerSchema)) { headerSchema = dereferenceRef(headerSchema, api, seenRefs); if (!headerSchema || isRef(headerSchema)) return; } headersSchema.properties[key] = toJSONSchema(cloneObject(headerSchema), { addEnumsToDescriptions: true, ...schemaOptions }); if (header.description) { headersSchema.properties[key].description = header.description; } } }); } const headersWrapper = { schema: headersSchema, type: "object", label: "Headers" }; if (response.description && headersWrapper.schema) { headersWrapper.description = response.description; } return headersWrapper; } function getResponseAsJSONSchema(operation, api, statusCode, opts) { const response = operation.getResponseByStatusCode(statusCode); const jsonSchema = []; if (!response) { return null; } const usedSchemas = /* @__PURE__ */ new Map(); const seenRefs = /* @__PURE__ */ new Set(); const refsByGroup = /* @__PURE__ */ new Map(); function refLoggerForSchemaGroup(group) { let set = refsByGroup.get(group); if (!set) { set = /* @__PURE__ */ new Set(); refsByGroup.set(group, set); } return set; } const baseSchemaOptions = { addEnumsToDescriptions: true, definition: api, seenRefs, usedSchemas, refLogger: (ref) => refLoggerForSchemaGroup("body").add(ref) }; function getPreferredSchema(content, preferredContentType) { if (!content) { return null; } const contentTypes = Object.keys(content); if (!contentTypes.length) { return null; } if (preferredContentType) { if (contentTypes.includes(preferredContentType)) { const schema2 = cloneObject(content[preferredContentType].schema); if (!schema2) { return null; } return toJSONSchema(schema2, baseSchemaOptions); } return null; } for (let i = 0; i < contentTypes.length; i++) { if (isJSON(contentTypes[i])) { const schema2 = cloneObject(content[contentTypes[i]].schema); if (!schema2) { return {}; } return toJSONSchema(schema2, baseSchemaOptions); } } const contentType = contentTypes.shift(); if (!contentType) { return {}; } const schema = cloneObject(content[contentType].schema); if (!schema) { return {}; } return toJSONSchema(schema, baseSchemaOptions); } const foundSchema = getPreferredSchema(response.content, opts?.contentType); if (opts?.contentType && !foundSchema) { return null; } if (foundSchema) { const schema = structuredClone(foundSchema); let schemaType = foundSchema.type; if (schemaType === void 0 && isRef(foundSchema) && usedSchemas.size > 0) { const resolvedSchema = usedSchemas.get(foundSchema.$ref); const resolvedType = resolvedSchema && typeof resolvedSchema === "object" && "type" in resolvedSchema ? resolvedSchema.type : void 0; schemaType = Array.isArray(resolvedType) ? resolvedType[0] : resolvedType; } const schemaWrapper = { // If there's no `type` then the root schema is a circular `$ref` that we likely won't be // able to render so instead of generating a JSON Schema with an `undefined` type we should // default to `string` so there's at least *something* the end-user can interact with. type: schemaType ?? "string", schema: isPrimitive(schema) ? schema : { ...schema, $schema: getSchemaVersionString(schema, api) }, label: "Response body" }; if (response.description && schemaWrapper.schema) { schemaWrapper.description = response.description; } applyDiscriminatorOneOfToUsedSchemas(api, usedSchemas, (ref) => { if (usedSchemas.has(ref)) { return usedSchemas.get(ref); } try { const resolved = dereferenceRef({ $ref: ref }, api, seenRefs); if (isRef(resolved)) return; const converted = toJSONSchema(structuredClone(resolved), { ...baseSchemaOptions, seenRefs }); usedSchemas.set(ref, converted); return converted; } catch { } }); if (schemaWrapper.schema && usedSchemas.size > 0) { const refsInOutput = collectRefsInSchema(schemaWrapper.schema); const refsInGroup = refsByGroup.get("body") ?? /* @__PURE__ */ new Set(); const referencedSchemas = filterRequiredRefsToReferenced(/* @__PURE__ */ new Set([...refsInGroup, ...refsInOutput]), usedSchemas); if (referencedSchemas.size > 0) { mergeReferencedSchemasIntoRoot(schemaWrapper.schema, referencedSchemas); } } jsonSchema.push(schemaWrapper); } if (response.headers) { const headersWrapper = buildHeadersSchema(response, { ...baseSchemaOptions, refLogger: (ref) => refLoggerForSchemaGroup("headers").add(ref) }); if (headersWrapper.schema && usedSchemas.size > 0) { const refsInGroup = refsByGroup.get("headers") ?? /* @__PURE__ */ new Set(); const refsInOutput = collectRefsInSchema(headersWrapper.schema); const referencedSchemas = filterRequiredRefsToReferenced(/* @__PURE__ */ new Set([...refsInGroup, ...refsInOutput]), usedSchemas); if (referencedSchemas.size > 0) { mergeReferencedSchemasIntoRoot(headersWrapper.schema, referencedSchemas); } } jsonSchema.push(headersWrapper); } return jsonSchema.length ? jsonSchema : null; } // src/operation/index.ts var Operation = class { /** * The `Oas` instance that this operation belongs to. */ oas; /** * Schema of the operation from the API Definition. */ schema; /** * OpenAPI API Definition that this operation originated from. */ api; /** * Path that this operation is targeted towards. */ path; /** * HTTP Method that this operation is targeted towards. */ method; /** * The primary Content Type that this operation accepts. */ contentType; /** * An object with groups of all example definitions (body/header/query/path/response/etc.) */ exampleGroups; /** * Request body examples for this operation. */ requestBodyExamples; /** * Response examples for this operation. */ responseExamples; /** * Callback examples for this operation (if it has callbacks). */ callbackExamples; /** * Flattened out arrays of both request and response headers that are utilized on this operation. */ headers; /** * Internal storage array that the library utilizes to keep track of the times the * {@see Operation.dereference} has been called so that if you initiate multiple promises they'll * all end up returning the same data set once the initial dereference call completed. */ promises; /** * Internal storage array that the library utilizes to keep track of its `dereferencing` state so * it doesn't initiate multiple dereferencing processes. */ dereferencing; /** * Have the component schemas within this API definition been decorated with our * `x-readme-ref-name` extension? * * @see {@link decorateComponentSchemas} */ schemasDecorated = false; constructor(oas, path, method, operation) { this.oas = oas; this.schema = operation; this.api = oas.api; this.path = path; this.method = method; this.contentType = void 0; this.requestBodyExamples = void 0; this.responseExamples = void 0; this.callbackExamples = void 0; this.exampleGroups = void 0; this.headers = { request: [], response: [] }; this.promises = []; this.dereferencing = { processing: false, complete: false, circularRefs: [] }; } /** * Retrieve the `summary` for this operation. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationsummary} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-summary} */ getSummary() { if (this.schema?.summary && typeof this.schema.summary === "string") { return this.schema.summary; } const pathItem = this.api.paths?.[this.path]; if (pathItem?.summary && typeof pathItem.summary === "string") { return pathItem.summary; } return void 0; } /** * Retrieve the `description` for this operation. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationdescription} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-description} */ getDescription() { if (this.schema?.description && typeof this.schema.description === "string") { return this.schema.description; } const pathItem = this.api.paths?.[this.path]; if (pathItem?.description && typeof pathItem.description === "string") { return pathItem.description; } return void 0; } /** * Retrieve the primary content type for this operation. If multiple exist, the first JSON-like * type will be returned. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-requestbodycontent} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-request-body-content} */ getContentType() { if (this.contentType) { return this.contentType; } let types = []; if (this.schema.requestBody) { if (isRef(this.schema.requestBody)) { this.schema.requestBody = dereferenceRef(this.schema.requestBody, this.api); } if (this.schema.requestBody && "content" in this.schema.requestBody) { types = Object.keys(this.schema.requestBody.content); } } this.contentType = "application/json"; if (types?.length) { this.contentType = types[0]; } types.forEach((t) => { if (matches_mimetype_default.json(t)) { this.contentType = t; } }); return this.contentType; } /** * Checks if the current operation has a `x-www-form-urlencoded` content type payload. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-requestbodycontent} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-request-body-content} */ isFormUrlEncoded() { return matches_mimetype_default.formUrlEncoded(this.getContentType()); } /** * Checks if the current operation has a mutipart content type payload. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-requestbodycontent} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-request-body-content} */ isMultipart() { return matches_mimetype_default.multipart(this.getContentType()); } /** * Checks if the current operation has a JSON-like content type payload. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-requestbodycontent} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-request-body-content} */ isJson() { return matches_mimetype_default.json(this.getContentType()); } /** * Checks if the current operation has an XML content type payload. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-requestbodycontent} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-request-body-content} */ isXml() { return matches_mimetype_default.xml(this.getContentType()); } /** * Checks if the current operation is a webhook or not. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#oas-webhooks} */ isWebhook() { return this instanceof Webhook; } /** * Returns an array of all security requirements associated wtih this operation. If none are * defined at the operation level, the securities for the entire API definition are returned * (with an empty array as a final fallback). * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#security-requirement-object} */ getSecurity() { if (!this.api?.components?.securitySchemes || !Object.keys(this.api.components.securitySchemes).length) { return []; } return this.schema.security || this.api.security || []; } /** * Retrieve a collection of grouped security schemes. The inner array determines AND-grouped * security schemes, the outer array determines OR-groups. * * @see {@link https://swagger.io/docs/specification/authentication/#multiple} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#security-requirement-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-requirement-object} * @param filterInvalid Optional flag that, when set to `true`, filters out invalid/nonexistent * security schemes, rather than returning `false`. */ getSecurityWithTypes(filterInvalid = false) { return this.getSecurity().map((requirement) => { let keys; try { keys = Object.keys(requirement); } catch { return false; } const keysWithTypes = keys.map((key) => { let security; try { security = this.api?.components?.securitySchemes?.[key]; if (!security) return false; if (isRef(security)) { security = dereferenceRef(security, this.api); if (!security || isRef(security)) return false; } } catch { return false; } if (!security || isRef(security)) return false; let type = null; if (security.type === "http") { if (security.scheme === "basic") type = "Basic"; else if (security.scheme === "bearer") type = "Bearer"; else type = security.type; } else if (security.type === "oauth2") { type = "OAuth2"; } else if (security.type === "apiKey") { if (security.in === "query") type = "Query"; else if (security.in === "header") type = "Header"; else if (security.in === "cookie") type = "Cookie"; else type = security.type; } else { return false; } return { type, security: { ...security, _key: key, _requirements: requirement[key] } }; }); if (filterInvalid) return keysWithTypes.filter((key) => key !== false); return keysWithTypes; }); } /** * Retrieve an object where the keys are unique scheme types, and the values are arrays * containing each security scheme of that type. * */ prepareSecurity() { return this.getSecurityWithTypes().reduce( (prev, securities) => { if (!securities) return prev; securities.forEach((security) => { if (!security) return; if (!prev[security.type]) prev[security.type] = []; const exists = prev[security.type].some((sec) => sec._key === security.security._key); if (!exists) { if (security.security?._requirements) delete security.security._requirements; prev[security.type].push(security.security); } }); return prev; }, {} ); } /** * Retrieve all of the headers, request and response, that are associated with this operation. * */ getHeaders() { const security = this.prepareSecurity(); if (security.Header) { this.headers.request = security.Header.map((h) => { if (!("name" in h)) return false; return h.name; }).filter((item) => item !== false); } if (security.Bearer || security.Basic || security.OAuth2) { this.headers.request.push("Authorization"); } if (security.Cookie) { this.headers.request.push("Cookie"); } if (this.schema.parameters) { this.headers.request = this.headers.request.concat( this.schema.parameters.map((p) => { let param = p; if (isRef(param)) { param = dereferenceRef(param, this.api); if (!param || isRef(param)) return; } if (param.in && param.in === "header") return param.name; return; }).filter((item) => item !== void 0) ); } if (this.schema.responses) { this.headers.response = Object.keys(this.schema.responses).map((r) => { let response = this.schema.responses[r]; if (!response) return []; if (isRef(response)) { this.schema.responses[r] = dereferenceRef(response, this.api); response = this.schema.responses[r]; if (!response || isRef(response)) { return []; } } return response?.headers ? Object.keys(response.headers) : []; }).reduce((a, b) => a.concat(b), []); } if (!this.headers.request.includes("Content-Type") && this.schema.requestBody) { let requestBody = this.schema.requestBody; if (requestBody) { if (isRef(requestBody)) { this.schema.requestBody = dereferenceRef(requestBody, this.api); requestBody = this.schema.requestBody; } if (requestBody && !isRef(requestBody) && "content" in requestBody && Object.keys(requestBody.content)) { this.headers.request.push("Content-Type"); } } } if (this.schema.responses) { const hasResponseContent = Object.keys(this.schema.responses).some((r) => { let response = this.schema.responses?.[r]; if (!response) return false; if (isRef(response)) { this.schema.responses[r] = dereferenceRef(response, this.api); response = this.schema.responses[r]; if (!response || isRef(response)) { return false; } } return response.content && Object.keys(response.content).length > 0; }); if (hasResponseContent) { if (!this.headers.request.includes("Accept")) this.headers.request.push("Accept"); if (!this.headers.response.includes("Content-Type")) this.headers.response.push("Content-Type"); } } return this.headers; } /** * Determine if this operation has an `operationId` present in its schema. Note that if one is * present in the schema but is an empty string then this will return `false`. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationid} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-id} */ hasOperationId() { return hasOperationId(this.schema); } /** * Determine if an operation has an `operationId` present in its schema. Note that if one is * present in the schema but is an empty string then this will return `false`. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationid} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-id} */ static hasOperationId(schema) { return hasOperationId(schema); } /** * Get an `operationId` for this operation. If one is not present (it's not required by the spec!) * a hash of the path and method will be returned instead. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationid} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-id} */ getOperationId(opts = {}) { return getOperationId(this.path, this.method, this.schema, opts); } /** * Get an `operationId` for an operation. If one is not present (it's not required by the spec!) * a hash of the path and method will be returned instead. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationid} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-id} */ static getOperationId(path, method, schema, opts = {}) { return getOperationId(path, method, schema, opts); } /** * Return an array of all tags, and their metadata, that exist on this operation. * */ getTags() { if (!("tags" in this.schema)) { return []; } const oasTagMap = /* @__PURE__ */ new Map(); if (Array.isArray(this.api?.tags)) { this.api.tags.forEach((tag) => { oasTagMap.set(tag.name, tag); }); } const oasTags = Object.fromEntries(oasTagMap); const tags = []; if (Array.isArray(this.schema.tags)) { this.schema.tags.forEach((tag) => { if (tag in oasTags) { tags.push(oasTags[tag]); } else { tags.push({ name: tag }); } }); } return tags; } /** * Return is the operation is flagged as `deprecated` or not. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationdeprecated} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-deprecated} */ isDeprecated() { return Boolean("deprecated" in this.schema ? this.schema.deprecated : false); } /** * Determine if the operation has any (non-request body) parameters. * */ hasParameters() { return !!this.getParameters().length; } /** * Return the parameters (non-request body) on the operation. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationparameters} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-parameters} */ getParameters() { let parameters = (this.schema?.parameters || []).map((p) => { let param = p; if (isRef(param)) { param = dereferenceRef(param, this.api); if (!param || isRef(param)) return; } return param; }).filter((param) => param !== void 0); const commonParams = (this.api?.paths?.[this.path]?.parameters || []).map((p) => { let param = p; if (isRef(param)) { param = dereferenceRef(param, this.api); if (!param || isRef(param)) return; } return param; }).filter((param) => param !== void 0); if (commonParams.length) { parameters = parameters.concat(dedupeCommonParameters(parameters, commonParams) || []); } return parameters; } /** * Determine if this operation has any required parameters. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationparameters} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-parameters} */ hasRequiredParameters() { return this.getParameters().some((param) => "required" in param && param.required); } /** * Convert the operation into an array of JSON Schema schemas for each available type of * parameter available on the operation. * * Note that this method is not compatible with an operation or OpenAPI definition that has been * processed with `.dereference()`. This method can only be called with the _original_ API * definition that was used to initialize the `Operation` and `Oas` instance. If a dereferenced * schema is present when this is called a `TypeError` will be thrown. * * @throws {TypeError} If the operation or OpenAPI definition has been run through `.dereference().` * */ getParametersAsJSONSchema(opts = {}) { if (this.isDereferenced()) { throw new Error( "`.getParametersAsJSONSchema()` is not compatible with an operation or OpenAPI definition that has been run through `.dereference().`" ); } if (!this.schemasDecorated) { decorateComponentSchemasWithRefName(this.api); this.schemasDecorated = true; } return getParametersAsJSONSchema(this, this.api, { includeDiscriminatorMappingRefs: true, ...opts }); } /** * Get a single response for this status code, formatted as JSON schema. * * Note that this method is not compatible with an operation or OpenAPI definition that has been * processed with `.dereference()`. This method can only be called with the _original_ API * definition that was used to initialize the `Operation` and `Oas` instance. If a dereferenced * schema is present when this is called a `TypeError` will be thrown. * * @param statusCode Status code to pull a JSON Schema response for. * @param opts Options for schema generation. * @param opts.contentType Optional content-type to use. If specified and the response doesn't have * this content-type, the function will return null. */ getResponseAsJSONSchema(statusCode, opts = {}) { if (this.isDereferenced()) { throw new Error( "`.getResponseAsJSONSchema()` is not compatible with an operation or OpenAPI definition that has been run through `.dereference().`" ); } if (!this.schemasDecorated) { decorateComponentSchemasWithRefName(this.api); this.schemasDecorated = true; } return getResponseAsJSONSchema(this, this.api, statusCode, { includeDiscriminatorMappingRefs: true, ...opts }); } /** * Get an array of all valid response status codes for this operation. * */ getResponseStatusCodes() { if (!this.schema.responses) return []; if (isRef(this.schema.responses)) { this.schema.responses = dereferenceRef(this.schema.responses, this.api); if (!this.schema.responses || isRef(this.schema.responses)) { return []; } } return Object.keys(this.schema.responses).filter((key) => { if (key.startsWith("x-")) { return false; } const response = this.schema.responses?.[key]; return response && typeof response === "object"; }); } /** * Retrieve an array of all content types that this operation can return. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#response-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#response-object} */ getResponseContentTypes() { if (!this.schema.responses) return []; const contentTypes = /* @__PURE__ */ new Set(); Object.values(this.schema.responses).forEach((response) => { let resp = response; if (!resp) return; if (isRef(resp)) { resp = dereferenceRef(resp, this.api); if (!resp || isRef(resp)) { return; } } Object.keys(resp.content || {}).forEach((mimeType) => { contentTypes.add(mimeType); }); }); return Array.from(contentTypes); } /** * Determine if the operation has any request bodies. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#user-content-operationrequestbody} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.2.md#user-content-operation-request-body} */ hasRequestBody() { return !!this.schema.requestBody; } /** * Return the current `requestBody` object, dereferencing it in the process if it's a `$ref` * pointer. * */ getResolvedRequestBody() { let requestBody = this.schema.requestBody; if (!requestBody) return false; if (isRef(requestBody)) { this.schema.requestBody = dereferenceRef(requestBody, this.api); requestBody = this.schema.requestBody; if (!requestBody || isRef(requestBody)) { return false; } } return requestBody; } /** * Retrieve the list of all available media types that the operations request body can accept. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object} */ getRequestBodyMediaTypes() { if (!this.hasRequestBody()) { return []; } const requestBody = this.getResolvedRequestBody(); if (!requestBody) return []; return Object.keys(requestBody.content); } /** * Determine if this operation has a required request body. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#media-type-object} */ hasRequiredRequestBody() { if (!this.hasRequestBody()) { return false; } const requestBody = this.getResolvedRequestBody(); if (!requestBody) return false; if (requestBody.required) { return true; } const parameters = this.getParametersAsJSONSchema(); if (parameters === null) { return false; } return !!parameters.filter((js) => ["body", "formData"].includes(js.type)).find((js) => js.schema && Array.isArray(js.schema.required) && js.schema.required.length); } /** * Retrieve a specific request body content schema off this operation. * * If no media type is supplied this will return either the first available JSON-like request * body, or the first available if there are no JSON-like media types present. The return value * is an object containing the selected media type, the media type object, and an optional * description. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#media-type-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versio