UNPKG

oas

Version:

Comprehensive tooling for working with OpenAPI definitions

649 lines (612 loc) 25.6 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _chunkYFZN44KBcjs = require('./chunk-YFZN44KB.cjs'); var _chunkZD7R5BNEcjs = require('./chunk-ZD7R5BNE.cjs'); var _chunk6HXOZ5GYcjs = require('./chunk-6HXOZ5GY.cjs'); var _chunk7PWF3F2Wcjs = require('./chunk-7PWF3F2W.cjs'); // src/lib/get-auth.ts function getPrimitiveDefaultAuth(scheme) { return typeof scheme["x-default"] === "number" || typeof scheme["x-default"] === "string" ? scheme["x-default"] : null; } function getKey(user, scheme) { switch (scheme.type) { case "oauth2": return user[scheme._key] || user.apiKey || getPrimitiveDefaultAuth(scheme) || null; case "apiKey": return user[scheme._key] || user.apiKey || scheme["x-default"] || null; case "http": if (scheme.scheme === "basic") { return user[scheme._key] || (user.user || user.pass ? { user: user.user || null, pass: user.pass || null } : scheme["x-default"]) || { user: null, pass: null }; } if (scheme.scheme === "bearer") { return user[scheme._key] || user.apiKey || scheme["x-default"] || null; } return null; default: return null; } } function getByScheme(user, scheme = {}, selectedApp) { if (_optionalChain([user, 'optionalAccess', _ => _.keys, 'optionalAccess', _2 => _2.length])) { if (selectedApp) { const userKey = user.keys.find((k) => k.name === selectedApp); if (!userKey) { return null; } return getKey(userKey, scheme); } return getKey(user.keys[0], scheme); } return getKey(user, scheme); } function getAuth(api, user, selectedApp) { return Object.keys(_optionalChain([api, 'optionalAccess', _3 => _3.components, 'optionalAccess', _4 => _4.securitySchemes]) || {}).map((scheme) => { const securityScheme = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, _optionalChain([api, 'access', _5 => _5.components, 'optionalAccess', _6 => _6.securitySchemes, 'optionalAccess', _7 => _7[scheme]]), api); if (!securityScheme || _chunk7PWF3F2Wcjs.isRef.call(void 0, securityScheme)) { return false; } return { [scheme]: getByScheme( user, { ...securityScheme, _key: scheme }, selectedApp ) }; }).filter((item) => item !== void 0).reduce((prev, next) => Object.assign(prev, next), {}); } // src/index.ts var Oas = class _Oas { /** * The current OpenAPI definition. */ /** * The current user that we should use when pulling auth tokens from security schemes. */ /** * @param oas An OpenAPI definition. * @param user The information about a user that we should use when pulling auth tokens from * security schemes. */ constructor(oas, user) { if (typeof oas === "string") { this.api = JSON.parse(oas) || {}; } else { this.api = oas || {}; } this.user = user || {}; } /** * This will initialize a new instance of the `Oas` class. This method is useful if you're using * Typescript and are attempting to supply an untyped JSON object into `Oas` as it will force-type * that object to an `OASDocument` for you. * * @param oas An OpenAPI definition. * @param user The information about a user that we should use when pulling auth tokens from * security schemes. */ static init(oas, user) { return new _Oas(oas, user); } /** * Retrieve the OpenAPI version that this API definition is targeted for. */ getVersion() { if (this.api.openapi) { return this.api.openapi; } throw new Error("Unable to recognize what specification version this API definition conforms to."); } /** * Retrieve the current OpenAPI API Definition. * */ getDefinition() { return this.api; } url(selected = 0, variables) { const url = _chunkYFZN44KBcjs.normalizedURLFromServers.call(void 0, this.api.servers, selected); return this.replaceUrl(url, variables || this.defaultVariables(selected)).trim(); } variables(selected = 0) { return _chunkYFZN44KBcjs.variablesFromServers.call(void 0, this.api.servers, selected); } defaultVariables(selected = 0) { return _chunkYFZN44KBcjs.defaultVariablesFromServers.call(void 0, this.api.servers, selected, this.user); } splitUrl(selected = 0) { return _chunkYFZN44KBcjs.splitUrlFromServers.call(void 0, this.api.servers, selected); } /** * With a fully composed server URL, run through our list of known OAS servers and return back * which server URL was selected along with any contained server variables split out. * * For example, if you have an OAS server URL of `https://{name}.example.com:{port}/{basePath}`, * and pass in `https://buster.example.com:3000/pet` to this function, you'll get back the * following: * * { selected: 0, variables: { name: 'buster', port: 3000, basePath: 'pet' } } * * Re-supplying this data to `oas.url()` should return the same URL you passed into this method. * * @param baseUrl A given URL to extract server variables out of. */ splitVariables(baseUrl) { const matchedServer = (this.api.servers || []).map((server, i) => { const rgx = _chunkYFZN44KBcjs.transformURLIntoRegex.call(void 0, server.url); const found = new RegExp(rgx).exec(baseUrl); if (!found) { return false; } const variables = {}; Array.from(server.url.matchAll(_chunkZD7R5BNEcjs.SERVER_VARIABLE_REGEX)).forEach((variable, y) => { variables[variable[1]] = found[y + 1]; }); return { selected: i, variables }; }).filter((item) => item !== false); return matchedServer.length ? matchedServer[0] : false; } /** * Replace templated variables with supplied data in a given URL. * * There are a couple ways that this will utilize variable data: * * - Supplying a `variables` object. If this is supplied, this data will always take priority. * This incoming `variables` object can be two formats: * `{ variableName: { default: 'value' } }` and `{ variableName: 'value' }`. If the former is * present, that will take precedence over the latter. * - If the supplied `variables` object is empty or does not match the current template name, * we fallback to the data stored in `this.user` and attempt to match against that. * See `getUserVariable` for some more information on how this data is pulled from `this.user`. * * If no variables supplied match up with the template name, the template name will instead be * used as the variable data. * * @param url A URL to swap variables into. * @param variables An object containing variables to swap into the URL. */ replaceUrl(url, variables = {}) { return _chunkYFZN44KBcjs.stripTrailingSlash.call(void 0, url.replace(_chunkZD7R5BNEcjs.SERVER_VARIABLE_REGEX, (original, key) => { if (key in variables) { const data = variables[key]; if (typeof data === "object") { if (!Array.isArray(data) && data !== null && "default" in data) { return String(data.default); } } else { return String(data); } } const userVariable = _chunkYFZN44KBcjs.getUserVariable.call(void 0, this.user, key); if (userVariable) { return String(userVariable); } return original; }) ); } /** * Retrieve an Operation of Webhook class instance for a given path and method. * * @param path Path to lookup and retrieve. * @param method HTTP Method to retrieve on the path. */ operation(path, method, opts = {}) { let operation = { parameters: [] }; if (opts.isWebhook) { if (_chunk7PWF3F2Wcjs.isOpenAPI31.call(void 0, this.api)) { const webhookPath = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, _optionalChain([this, 'access', _8 => _8.api, 'optionalAccess', _9 => _9.webhooks, 'optionalAccess', _10 => _10[path]]), this.api); if (webhookPath && !_chunk7PWF3F2Wcjs.isRef.call(void 0, webhookPath)) { if (_optionalChain([webhookPath, 'optionalAccess', _11 => _11[method]])) { operation = webhookPath[method]; return new (0, _chunkYFZN44KBcjs.Webhook)(this, path, method, operation); } } } } if (_optionalChain([this, 'optionalAccess', _12 => _12.api, 'optionalAccess', _13 => _13.paths, 'optionalAccess', _14 => _14[path]])) { const pathItem = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, this.api.paths[path], this.api); if (_optionalChain([pathItem, 'optionalAccess', _15 => _15[method]])) { operation = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, pathItem[method], this.api); } } return new (0, _chunkYFZN44KBcjs.Operation)(this, path, method, operation); } findOperationMatches(url) { const { origin, hostname } = new URL(url); const originRegExp = new RegExp(origin, "i"); const { servers, paths } = this.api; let pathName; let targetServer; let matchedServer; if (!servers || !servers.length) { matchedServer = { url: "https://example.com" }; } else { matchedServer = servers.find((s) => originRegExp.exec(this.replaceUrl(s.url, s.variables || {}))); if (!matchedServer) { const hostnameRegExp = new RegExp(hostname); matchedServer = servers.find((s) => hostnameRegExp.exec(this.replaceUrl(s.url, s.variables || {}))); } } if (matchedServer) { targetServer = { ...matchedServer, url: this.replaceUrl(matchedServer.url, matchedServer.variables || {}) }; [, pathName] = url.split(new RegExp(targetServer.url, "i")); } if (!matchedServer || !pathName) { const matchedServerAndPath = (servers || []).map((server) => { const rgx = _chunkYFZN44KBcjs.transformURLIntoRegex.call(void 0, server.url); const found = new RegExp(rgx).exec(url); if (!found) { return; } return { matchedServer: server, pathName: url.split(new RegExp(rgx)).slice(-1).pop() }; }).filter((item) => item !== void 0); if (!matchedServerAndPath.length) { return void 0; } pathName = matchedServerAndPath[0].pathName; targetServer = { ...matchedServerAndPath[0].matchedServer }; } if (pathName === void 0) return void 0; if (pathName === "") pathName = "/"; if (!paths || !targetServer) return void 0; const annotatedPaths = _chunkYFZN44KBcjs.generatePathMatches.call(void 0, paths, pathName, targetServer.url); if (!annotatedPaths.length) return void 0; return annotatedPaths; } /** * Discover an operation in an OAS from a fully-formed URL and HTTP method. Will return an object * containing a `url` object and another one for `operation`. This differs from `getOperation()` * in that it does not return an instance of the `Operation` class. * * @param url A full URL to look up. * @param method The cooresponding HTTP method to look up. */ findOperation(url, method) { const annotatedPaths = this.findOperationMatches(url); if (!annotatedPaths) { return void 0; } const matches = _chunkYFZN44KBcjs.filterPathMethods.call(void 0, annotatedPaths, method); if (!matches.length) return void 0; return _chunkYFZN44KBcjs.findTargetPath.call(void 0, matches); } /** * Discover an operation in an OAS from a fully-formed URL without an HTTP method. Will return an * object containing a `url` object and another one for `operation`. * * @param url A full URL to look up. */ findOperationWithoutMethod(url) { const annotatedPaths = this.findOperationMatches(url); if (!annotatedPaths) { return void 0; } return _chunkYFZN44KBcjs.findTargetPath.call(void 0, annotatedPaths); } /** * Retrieve an operation in an OAS from a fully-formed URL and HTTP method. Differs from * `findOperation` in that while this method will return an `Operation` instance, * `findOperation()` does not. * * @param url A full URL to look up. * @param method The cooresponding HTTP method to look up. */ getOperation(url, method) { const op = this.findOperation(url, method); if (op === void 0) { return void 0; } return this.operation(op.url.nonNormalizedPath, method); } /** * Retrieve an operation in an OAS by an `operationId`. * * If an operation does not have an `operationId` one will be generated in place, using the * default behavior of `Operation.getOperationId()`, and then asserted against your query. * * Note that because `operationId`s are unique that uniqueness does include casing so the ID * you are looking for will be asserted as an exact match. * * @see {Operation.getOperationId()} * @param id The `operationId` to look up. */ getOperationById(id) { let found; Object.values(this.getPaths()).forEach((operations) => { if (found) return; found = Object.values(operations).find((operation) => operation.getOperationId() === id); }); if (found) { return found; } Object.entries(this.getWebhooks()).forEach(([, webhooks]) => { if (found) return; found = Object.values(webhooks).find((webhook) => webhook.getOperationId() === id); }); return found; } /** * With an object of user information, retrieve the appropriate API auth keys from the current * OAS definition. * * @see {@link https://docs.readme.com/docs/passing-data-to-jwt} * @param user User * @param selectedApp The user app to retrieve an auth key for. */ getAuth(user, selectedApp) { if (!_optionalChain([this, 'access', _16 => _16.api, 'optionalAccess', _17 => _17.components, 'optionalAccess', _18 => _18.securitySchemes])) { return {}; } return getAuth(this.api, user, selectedApp); } /** * Determine if a security scheme exists within the API definition. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#security-scheme-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object} * @param name The name of the security scheme to check for. */ hasSecurityScheme(name) { return Boolean(_optionalChain([this, 'access', _19 => _19.api, 'optionalAccess', _20 => _20.components, 'optionalAccess', _21 => _21.securitySchemes, 'optionalAccess', _22 => _22[name]])); } /** * Retrieve a security scheme from the API definition. * * If the found security scheme is a `$ref` pointer it will be lazily dereferenced; if the scheme * cannot be resolved after that process (eg. it's circular or is an invalid `$ref`) then * `undefined` will be returned. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#security-scheme-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#security-scheme-object} * @param name The name of the security scheme to retrieve. */ getSecurityScheme(name) { if (!this.hasSecurityScheme(name)) { return void 0; } let scheme = _optionalChain([this, 'access', _23 => _23.api, 'optionalAccess', _24 => _24.components, 'optionalAccess', _25 => _25.securitySchemes, 'optionalAccess', _26 => _26[name]]); if (!scheme) return void 0; if (_chunk7PWF3F2Wcjs.isRef.call(void 0, scheme)) { scheme = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, scheme, this.api); if (!scheme || _chunk7PWF3F2Wcjs.isRef.call(void 0, scheme)) return void 0; } return scheme; } /** * Returns the `paths` object that exists in this API definition but with every `method` mapped * to an instance of the `Operation` class. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#openapi-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object} */ getPaths() { const paths = {}; if (!this.api.paths) { return paths; } Object.keys(this.api.paths).forEach((path) => { if (path.startsWith("x-")) { return; } paths[path] = {}; let pathItem = this.api.paths[path]; if (!pathItem) { return; } else if (_chunk7PWF3F2Wcjs.isRef.call(void 0, pathItem)) { this.api.paths[path] = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, pathItem, this.api); pathItem = this.api.paths[path]; if (!pathItem || _chunk7PWF3F2Wcjs.isRef.call(void 0, pathItem)) { return; } } Object.keys(pathItem).forEach((method) => { if (!_chunkZD7R5BNEcjs.supportedMethods.includes(method)) { return; } paths[path][method] = this.operation(path, method); }); }); return paths; } /** * Returns the `webhooks` object that exists in this API definition but with every `method` * mapped to an instance of the `Webhook` class. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#openapi-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object} */ getWebhooks() { const webhooks = {}; if (!_chunk7PWF3F2Wcjs.isOpenAPI31.call(void 0, this.api) || !this.api.webhooks) { return webhooks; } Object.keys(this.api.webhooks).forEach((id) => { webhooks[id] = {}; const webhookPath = _chunkZD7R5BNEcjs.dereferenceRef.call(void 0, _optionalChain([this, 'access', _27 => _27.api, 'access', _28 => _28.webhooks, 'optionalAccess', _29 => _29[id]]), this.api); if (webhookPath) { Object.keys(webhookPath).forEach((method) => { if (!_chunkZD7R5BNEcjs.supportedMethods.includes(method)) { return; } webhooks[id][method] = this.operation(id, method, { isWebhook: true }); }); } }); return webhooks; } /** * Return an array of all tag names that exist on this API definition. * * If the API definition uses the `x-disable-tag-sorting` extension then tags will be returned in * the order they're defined. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md#openapi-object} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#openapi-object} * @param setIfMissing If a tag is not present on an operation that operations path will be added * into the list of tags returned. */ getTags(setIfMissing = false) { const allTags = /* @__PURE__ */ new Set(); const oasTags = _optionalChain([this, 'access', _30 => _30.api, 'access', _31 => _31.tags, 'optionalAccess', _32 => _32.map, 'call', _33 => _33((tag) => tag.name)]) || []; const disableTagSorting = _chunk6HXOZ5GYcjs.getExtension.call(void 0, "disable-tag-sorting", this.api); Object.entries(this.getPaths()).forEach(([path, operations]) => { Object.values(operations).forEach((operation) => { const tags = operation.getTags(); if (setIfMissing && !tags.length) { allTags.add(path); return; } tags.forEach((tag) => { allTags.add(tag.name); }); }); }); Object.entries(this.getWebhooks()).forEach(([path, webhooks]) => { Object.values(webhooks).forEach((webhook) => { const tags = webhook.getTags(); if (setIfMissing && !tags.length) { allTags.add(path); return; } tags.forEach((tag) => { allTags.add(tag.name); }); }); }); const endpointTags = []; const tagsArray = []; if (disableTagSorting) { return Array.from(allTags); } Array.from(allTags).forEach((tag) => { if (oasTags.includes(tag)) { tagsArray.push(tag); } else { endpointTags.push(tag); } }); let sortedTags = tagsArray.toSorted((a, b) => { return oasTags.indexOf(a) - oasTags.indexOf(b); }); sortedTags = sortedTags.concat(endpointTags); return sortedTags; } /** * Determine if a given a custom specification extension exists within the API definition. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions} * @param extension Specification extension to lookup. */ hasExtension(extension) { return _chunk6HXOZ5GYcjs.hasRootExtension.call(void 0, extension, this.api); } /** * Retrieve a custom specification extension off of the API definition. * * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions} * @param extension Specification extension to lookup. */ getExtension(extension, operation) { return _chunk6HXOZ5GYcjs.getExtension.call(void 0, extension, this.api, operation); } /** * Determine if a given OpenAPI custom extension is valid or not. * * @see {@link https://docs.readme.com/docs/openapi-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions} * @param extension Specification extension to validate. * @throws */ validateExtension(extension) { if (this.hasExtension("x-readme")) { const data = this.getExtension("x-readme"); if (typeof data !== "object" || Array.isArray(data) || data === null) { throw new TypeError('"x-readme" must be of type "Object"'); } if (extension in data) { if ([_chunk6HXOZ5GYcjs.CODE_SAMPLES, _chunk6HXOZ5GYcjs.HEADERS, _chunk6HXOZ5GYcjs.PARAMETER_ORDERING, _chunk6HXOZ5GYcjs.SAMPLES_LANGUAGES].includes(extension)) { if (data[extension] !== void 0) { if (!Array.isArray(data[extension])) { throw new TypeError(`"x-readme.${extension}" must be of type "Array"`); } if (extension === _chunk6HXOZ5GYcjs.PARAMETER_ORDERING) { _chunk6HXOZ5GYcjs.validateParameterOrdering.call(void 0, data[extension], `x-readme.${extension}`); } } } else if (extension === _chunk6HXOZ5GYcjs.OAUTH_OPTIONS) { if (typeof data[extension] !== "object") { throw new TypeError(`"x-readme.${extension}" must be of type "Object"`); } } else if (extension === _chunk6HXOZ5GYcjs.STATUS_URL) { if (typeof data[extension] !== "string") { throw new TypeError(`"x-readme.${extension}" must be of type "String"`); } } else if (typeof data[extension] !== "boolean") { throw new TypeError(`"x-readme.${extension}" must be of type "Boolean"`); } } } if (this.hasExtension(`x-${extension}`)) { const data = this.getExtension(`x-${extension}`); if ([_chunk6HXOZ5GYcjs.CODE_SAMPLES, _chunk6HXOZ5GYcjs.HEADERS, _chunk6HXOZ5GYcjs.PARAMETER_ORDERING, _chunk6HXOZ5GYcjs.SAMPLES_LANGUAGES].includes(extension)) { if (!Array.isArray(data)) { throw new TypeError(`"x-${extension}" must be of type "Array"`); } if (extension === _chunk6HXOZ5GYcjs.PARAMETER_ORDERING) { _chunk6HXOZ5GYcjs.validateParameterOrdering.call(void 0, data, `x-${extension}`); } } else if (extension === _chunk6HXOZ5GYcjs.OAUTH_OPTIONS) { if (typeof data !== "object") { throw new TypeError(`"x-${extension}" must be of type "Object"`); } } else if (extension === _chunk6HXOZ5GYcjs.STATUS_URL) { if (typeof data !== "string") { throw new TypeError(`"x-${extension}" must be of type "String"`); } } else if (typeof data !== "boolean") { throw new TypeError(`"x-${extension}" must be of type "Boolean"`); } } } /** * Validate all of our custom or known OpenAPI extensions, throwing exceptions when necessary. * * @see {@link https://docs.readme.com/docs/openapi-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#specification-extensions} * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#specification-extensions} */ validateExtensions() { Object.keys(_chunk6HXOZ5GYcjs.extensionDefaults).forEach((extension) => { this.validateExtension(extension); }); } }; exports.default = Oas; module.exports = exports.default; //# sourceMappingURL=index.cjs.map