oas-normalize
Version:
Tooling for converting, validating, and parsing OpenAPI, Swagger, and Postman API definitions
1 lines • 15.9 kB
Source Map (JSON)
{"version":3,"sources":["/Users/erunion/code/readme/oas/packages/oas-normalize/dist/index.cjs","../src/index.ts"],"names":[],"mappings":"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","file":"/Users/erunion/code/readme/oas/packages/oas-normalize/dist/index.cjs","sourcesContent":[null,"import type { Options } from './lib/types.js';\nimport type { ParserOptions, ValidationResult } from '@readme/openapi-parser';\nimport type { OpenAPI, OpenAPIV2, OpenAPIV3 } from 'openapi-types';\n\nimport fs from 'node:fs';\n\nimport { bundle, compileErrors, dereference, validate } from '@readme/openapi-parser';\nimport { isUnsafeURL } from '@readme/openapi-parser/lib/urls';\nimport postmanToOpenAPI from '@readme/postman-to-openapi';\nimport converter from 'swagger2openapi';\n\nimport { ValidationError } from './lib/errors.js';\nimport {\n getAPIDefinitionType,\n getType,\n isOpenAPI,\n isPostman,\n isSwagger,\n prepareURL,\n stringToJSON,\n} from './lib/utils.js';\n\nexport default class OASNormalize {\n cache: {\n bundle?: OpenAPI.Document | false;\n convert?: OpenAPI.Document | false;\n deref?: OpenAPI.Document | false;\n load?: Record<string, unknown> | false;\n };\n\n file: any;\n\n opts: Options;\n\n type: ReturnType<typeof getType>;\n\n constructor(file: any, opts?: Options) {\n this.file = file;\n this.opts = {\n colorizeErrors: false,\n enablePaths: false,\n parser: {},\n ...opts,\n };\n\n if (!this.opts.enablePaths) {\n if (!this.opts.parser) this.opts.parser = {};\n if (!this.opts.parser.resolve) this.opts.parser.resolve = {};\n this.opts.parser.resolve = { file: false };\n }\n\n this.type = getType(this.file);\n\n this.cache = {\n load: false,\n bundle: false,\n deref: false,\n };\n }\n\n /**\n * Load and return the API definition that `oas-normalize` was initialized with.\n *\n */\n async load(): Promise<Record<string, unknown>> {\n if (this.cache.load) return this.cache.load;\n\n const resolve = (obj: Parameters<typeof stringToJSON>[0]) => {\n const ret = stringToJSON(obj);\n if (!ret) {\n throw new Error(`Unable to retrieve API definition, it does not appear to be valid JSON.`);\n }\n\n this.cache.load = ret;\n return ret;\n };\n\n switch (this.type) {\n case 'json':\n case 'string-json':\n case 'string-yaml':\n return resolve(this.file);\n\n case 'buffer':\n return resolve(this.file.toString());\n\n case 'url': {\n const { url, options } = prepareURL(this.file);\n if (isUnsafeURL(url)) {\n throw new Error(`Sorry, we cannot access ${url}.`);\n }\n\n const resp = await fetch(url, options).then(res => {\n if (!res.ok) {\n throw new Error(`Failed to fetch ${url}: ${res.statusText}`);\n }\n\n return res.text();\n });\n return resolve(resp);\n }\n\n case 'path': {\n // Load a local file\n if (!this.opts.enablePaths) {\n throw new Error('Use `opts.enablePaths` to enable accessing local files.');\n }\n\n const contents = fs.readFileSync(this.file).toString();\n if (!contents.trim()) {\n throw new Error('No file contents found.');\n }\n return resolve(contents);\n }\n\n default:\n throw new Error('Could not load this file.');\n }\n }\n\n private static async convertPostmanToOpenAPI(schema: Record<string, unknown>) {\n return postmanToOpenAPI(JSON.stringify(schema), undefined, {\n outputFormat: 'json',\n replaceVars: true,\n }).then(JSON.parse);\n }\n\n /**\n * Bundle up the given API definition, resolving any external `$ref` pointers in the process.\n *\n */\n async bundle(): Promise<OpenAPI.Document> {\n if (this.cache.bundle) return this.cache.bundle;\n const parserOptions = this.opts.parser || {};\n\n return this.load()\n .then(schema => {\n // Though Postman collections don't support `$ref` pointers for us to bundle we'll still\n // upconvert it to an OpenAPI definition file so our returned dataset is always one of\n // those for a Postman dataset.\n if (isPostman(schema)) {\n return OASNormalize.convertPostmanToOpenAPI(schema);\n }\n\n return schema;\n })\n .then(schema => bundle(schema, parserOptions))\n .then(bundled => {\n this.cache.bundle = bundled;\n return bundled;\n });\n }\n\n /**\n * Dereference the given API definition.\n *\n */\n async dereference(): Promise<OpenAPI.Document> {\n if (this.cache.deref) return this.cache.deref;\n const parserOptions = this.opts.parser || {};\n\n return this.load()\n .then(schema => {\n // Though Postman collections don't support `$ref` pointers for us to dereference we'll\n // still upconvert it to an OpenAPI definition file so our returned dataset is always one\n // of those for a Postman dataset.\n if (isPostman(schema)) {\n return OASNormalize.convertPostmanToOpenAPI(schema);\n }\n\n return schema;\n })\n .then(schema => dereference(schema, parserOptions))\n .then(dereferenced => {\n this.cache.deref = dereferenced;\n return dereferenced;\n });\n }\n\n /**\n * Dereference the given API definition.\n *\n * This method is deprecated in favor of `dereference`. It will be removed in a future release.\n *\n * @deprecated\n */\n async deref(): Promise<OpenAPI.Document> {\n return this.dereference();\n }\n\n /**\n * Convert a given API definition to OpenAPI if it is not already.\n *\n */\n async convert(): Promise<OpenAPI.Document> {\n if (this.cache.convert) return this.cache.convert;\n\n return this.load()\n .then(async schema => {\n // If we have a Postman collection we need to convert it to OpenAPI.\n return isPostman(schema) ? OASNormalize.convertPostmanToOpenAPI(schema) : schema;\n })\n .then(async schema => {\n if (!isSwagger(schema) && !isOpenAPI(schema)) {\n throw new Error('The supplied API definition is unsupported.');\n } else if (isOpenAPI(schema)) {\n return schema;\n }\n\n const baseVersion = parseInt(schema.swagger, 10);\n if (baseVersion === 1) {\n throw new Error('Swagger v1.2 is unsupported.');\n }\n\n return converter\n .convertObj(schema, { anchors: true })\n .then((options: { openapi: OpenAPI.Document }) => options.openapi);\n });\n }\n\n /**\n * Validate a given API definition.\n *\n * If supplied a Postman collection it will be converted to OpenAPI first and then run through\n * standard OpenAPI validation.\n *\n */\n async validate(\n opts: {\n /**\n * Options to supply to our OpenAPI parser. See `@readme/openapi-parser` for documentation.\n * This option is deprecated in favor of the `parser` option on the constructor. It will be\n * removed in a future release.\n *\n * @see {@link https://npm.im/@readme/openapi-parser}\n * @deprecated\n */\n parser?: ParserOptions;\n\n /**\n * Determines if `.validate()` should throw a `ValidationError` if the API definition is\n * invalid.\n *\n * By default this will always happen however if you want to always instead receive a\n * `ValidationResult` object back, which will still allow you to determine if the supplied\n * API definition is valid or not, you can set this to `false`.\n */\n shouldThrowIfInvalid?: boolean;\n } = {},\n ): Promise<ValidationResult> {\n const shouldThrowIfInvalid = opts.shouldThrowIfInvalid ?? true;\n const parserOptions = opts.parser || this.opts.parser || {};\n if (!parserOptions.validate) parserOptions.validate = {};\n if (!parserOptions.validate.errors) parserOptions.validate.errors = {};\n\n parserOptions.validate.errors.colorize = this.opts.colorizeErrors;\n\n return this.load()\n .then(async schema => {\n // Because we don't have something akin to `openapi-parser` for Postman collections we just\n // always convert them to OpenAPI.\n return isPostman(schema) ? OASNormalize.convertPostmanToOpenAPI(schema) : schema;\n })\n .then(async schema => {\n if (!isSwagger(schema) && !isOpenAPI(schema)) {\n if (shouldThrowIfInvalid) {\n throw new ValidationError('The supplied API definition is unsupported.');\n }\n\n return {\n valid: false,\n errors: [{ message: 'The supplied API definition is unsupported.' }],\n warnings: [],\n additionalErrors: 0,\n specification: null,\n };\n } else if (isSwagger(schema)) {\n const baseVersion = parseInt(schema.swagger, 10);\n if (baseVersion === 1) {\n if (shouldThrowIfInvalid) {\n throw new ValidationError('Swagger v1.2 is unsupported.');\n }\n\n return {\n valid: false,\n errors: [{ message: 'Swagger v1.2 is unsupported.' }],\n warnings: [],\n additionalErrors: 0,\n specification: 'Swagger',\n };\n }\n }\n\n /**\n * `OpenAPIParser.validate()` dereferences schemas at the same time as validation, mutating\n * the supplied parameter in the process, and does not give us an option to disable this.\n * As we already have a dereferencing method on this library, and this method just needs to\n * tell us if the API definition is valid or not, we need to clone the schema before\n * supplying it to `openapi-parser`.\n */\n const clonedSchema = structuredClone(schema);\n\n const result = await validate(clonedSchema, parserOptions);\n if (!result.valid && shouldThrowIfInvalid) {\n throw new ValidationError(compileErrors(result), {\n errors: result.errors,\n warnings: result.warnings,\n });\n }\n\n // The API definition, whatever its format or specification, is valid.\n return result;\n });\n }\n\n /**\n * Retrieve OpenAPI, Swagger, or Postman version information about the supplied API definition.\n *\n */\n async version(): Promise<{\n specification: 'openapi' | 'postman' | 'swagger';\n version: string | 'unknown';\n }> {\n return this.load().then(schema => {\n switch (getAPIDefinitionType(schema)) {\n case 'openapi':\n return {\n specification: 'openapi',\n version: (schema as unknown as OpenAPIV3.Document).openapi,\n };\n\n case 'postman': {\n let version = 'unknown';\n if ((schema?.info as Record<string, string>)?.schema) {\n // Though `info.schema` is required by the Postman spec there's no strictness to its\n // contents so we'll do our best to extract a version out of this schema URL that they\n // seem to usually match. If not we'll fallback to treating it as an `unknown` version.\n // oxlint-disable-next-line no-unsafe-optional-chaining -- We've already narrowed `.schema` down to be present.\n const match = (schema?.info as Record<string, string>).schema.match(\n /http(s?):\\/\\/schema.getpostman.com\\/json\\/collection\\/v([0-9.]+)\\//,\n );\n\n if (match) {\n version = match[2];\n }\n }\n\n return {\n specification: 'postman',\n version,\n };\n }\n\n case 'swagger':\n return {\n specification: 'swagger',\n version: (schema as unknown as OpenAPIV2.Document).swagger,\n };\n\n default:\n throw new Error('Unknown file detected.');\n }\n });\n }\n}\n"]}