oas
Version:
Comprehensive tooling for working with OpenAPI definitions
1,424 lines (1,411 loc) • 67.6 kB
JavaScript
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