{"version":3,"sources":["/Users/erunion/code/readme/oas/packages/oas/dist/reducer/index.cjs","../../src/reducer/index.ts"],"names":[],"mappings":"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","file":"/Users/erunion/code/readme/oas/packages/oas/dist/reducer/index.cjs","sourcesContent":[null,"import type { ComponentsObject, HttpMethods, OASDocument, TagObject } from '../types.js';\n\nimport jsonPointer from 'jsonpointer';\n\nimport { query } from '../analyzer/util.js';\n\ninterface ReducerOptions {\n  /** A key-value object of path + method combinations to reduce by. */\n  paths?: Record<string, string[] | '*'>;\n  /** An array of tags in the OpenAPI definition to reduce by. */\n  tags?: string[];\n}\n\n/**\n * Query a JSON Schema object for any `$ref` pointers. Return any pointers that were found.\n *\n * @param schema JSON Schema object to look for any `$ref` pointers within it.\n */\nfunction getUsedRefs(schema: any) {\n  return query([\"$..['$ref']\"], schema);\n}\n\n/**\n * Recursively process a `$ref` pointer and accumulate any other `$ref` pointers that it or its\n * children use.\n *\n * @param schema JSON Schema object to look for and accumulate any `$ref` pointers that it may have.\n * @param $refs Known set of `$ref` pointers.\n * @param $ref `$ref` pointer to fetch a schema from out of the supplied schema.\n */\nfunction accumulateUsedRefs(schema: Record<string, unknown>, $refs: Set<string>, $ref: any): void {\n  let $refSchema;\n  if (typeof $ref === 'string') $refSchema = jsonPointer.get(schema, $ref.substring(1));\n  if ($refSchema === undefined) {\n    // If the schema we have wasn't fully dereferenced or bundled for whatever reason and this\n    // `$ref` that we have doesn't exist here we shouldn't try to search for more `$ref` pointers\n    // in a schema that doesn't exist.\n    return;\n  }\n\n  getUsedRefs($refSchema).forEach(({ value: currRef }) => {\n    // Because it's possible to have a parameter named `$ref`, which our lookup would pick up as a\n    // false positive, we want to exclude that from `$ref` matching as it's not really a reference.\n    if (typeof currRef !== 'string') {\n      return;\n    }\n\n    // If we've already processed this $ref don't send us into an infinite loop.\n    if ($refs.has(currRef)) {\n      return;\n    }\n\n    $refs.add(currRef);\n    accumulateUsedRefs(schema, $refs, currRef);\n  });\n}\n\n/**\n * With an array of tags or object of paths+method combinations, reduce an OpenAPI definition to a\n * new definition that just contains those tags or path + methods.\n *\n * @example <caption>Reduce by an array of tags only.</caption>\n * reducer(apiDefinition, { tags: ['pet'] })\n *\n * @example <caption>Reduce by a specific path and methods.</caption>\n * reducer(apiDefinition, { paths: { '/pet': ['get', 'post'] } })\n *\n * @example <caption>Reduce by a specific path and all methods it has.</caption>\n * reducer(apiDefinition, { paths: { '/pet': '*' } })\n *\n * @param definition A valid OpenAPI 3.x definition\n */\nexport default function reducer(definition: OASDocument, opts: ReducerOptions = {}): OASDocument {\n  // Convert tags and paths to lowercase since casing should not matter.\n  const reduceTags = 'tags' in opts ? opts.tags.map(tag => tag.toLowerCase()) : [];\n  const reducePaths =\n    'paths' in opts\n      ? Object.entries(opts.paths).reduce((acc: Record<string, string[] | string>, [key, value]) => {\n          const newKey = key.toLowerCase();\n          const newValue = Array.isArray(value) ? value.map(v => v.toLowerCase()) : value.toLowerCase();\n          acc[newKey] = newValue;\n          return acc;\n        }, {})\n      : {};\n\n  const $refs: Set<string> = new Set();\n  const usedTags: Set<string> = new Set();\n\n  if (!definition.openapi) {\n    throw new Error('Sorry, only OpenAPI definitions are supported.');\n  }\n\n  // Stringify and parse so we get a full non-reference clone of the API definition to work with.\n  // eslint-disable-next-line try-catch-failsafe/json-parse\n  const reduced = JSON.parse(JSON.stringify(definition)) as OASDocument;\n\n  // Retain any root-level security definitions.\n  if ('security' in reduced) {\n    Object.values(reduced.security).forEach(sec => {\n      Object.keys(sec).forEach(scheme => {\n        $refs.add(`#/components/securitySchemes/${scheme}`);\n      });\n    });\n  }\n\n  if ('paths' in reduced) {\n    Object.keys(reduced.paths).forEach(path => {\n      const pathLC = path.toLowerCase();\n\n      if (Object.keys(reducePaths).length) {\n        if (!(pathLC in reducePaths)) {\n          delete reduced.paths[path];\n          return;\n        }\n      }\n\n      Object.keys(reduced.paths[path]).forEach((method: HttpMethods | 'parameters') => {\n        // If this method is `parameters` we should always retain it.\n        if (method !== 'parameters') {\n          if (Object.keys(reducePaths).length) {\n            if (\n              reducePaths[pathLC] !== '*' &&\n              Array.isArray(reducePaths[pathLC]) &&\n              !reducePaths[pathLC].includes(method)\n            ) {\n              delete reduced.paths[path][method];\n              return;\n            }\n          }\n        }\n\n        const operation = reduced.paths[path][method];\n\n        // If we're reducing by tags and this operation doesn't live in one of those, remove it.\n        if (reduceTags.length) {\n          if (!('tags' in operation)) {\n            delete reduced.paths[path][method];\n            return;\n          } else if (!operation.tags.filter(tag => reduceTags.includes(tag.toLowerCase())).length) {\n            delete reduced.paths[path][method];\n            return;\n          }\n        }\n\n        // Accumulate a list of used tags so we can filter out any ones that we don't need later.\n        if ('tags' in operation) {\n          operation.tags.forEach((tag: string) => {\n            usedTags.add(tag);\n          });\n        }\n\n        // Accumulate a list of $ref pointers that are used within this operation.\n        getUsedRefs(operation).forEach(({ value: ref }) => {\n          $refs.add(ref);\n        });\n\n        // Accumulate any used security schemas that we need to retain.\n        if ('security' in operation) {\n          Object.values(operation.security).forEach(sec => {\n            Object.keys(sec).forEach(scheme => {\n              $refs.add(`#/components/securitySchemes/${scheme}`);\n            });\n          });\n        }\n      });\n\n      // If this path no longer has any methods, delete it.\n      if (!Object.keys(reduced.paths[path]).length) {\n        delete reduced.paths[path];\n      }\n    });\n\n    // If we don't have any more paths after cleanup, throw an error because an OpenAPI file must\n    // have at least one path.\n    if (!Object.keys(reduced.paths).length) {\n      throw new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?');\n    }\n  }\n\n  // Recursively accumulate any components that are in use.\n  $refs.forEach($ref => accumulateUsedRefs(reduced, $refs, $ref));\n\n  // Remove any unused components.\n  if ('components' in reduced) {\n    Object.keys(reduced.components).forEach((componentType: keyof ComponentsObject) => {\n      Object.keys(reduced.components[componentType]).forEach(component => {\n        // If our `$ref` either is a full, or deep match, then we should preserve it.\n        const refIsUsed =\n          $refs.has(`#/components/${componentType}/${component}`) ||\n          Array.from($refs).some(ref => {\n            // Because you can have a `$ref` like `#/components/examples/event-min/value`, which\n            // would be accumulated via our `$refs` query, we want to make sure we account for them.\n            // If we don't look for these then we'll end up removing them from the overall reduced\n            // definition, resulting in data loss and schema corruption.\n            return ref.startsWith(`#/components/${componentType}/${component}/`);\n          });\n\n        if (!refIsUsed) {\n          delete reduced.components[componentType][component];\n        }\n      });\n\n      // If this component group is now empty, delete it.\n      if (!Object.keys(reduced.components[componentType]).length) {\n        delete reduced.components[componentType];\n      }\n    });\n\n    // If this path no longer has any components, delete it.\n    if (!Object.keys(reduced.components).length) {\n      delete reduced.components;\n    }\n  }\n\n  // Remove any unused tags.\n  if ('tags' in reduced) {\n    reduced.tags.forEach((tag: TagObject, k: number) => {\n      if (!usedTags.has(tag.name)) {\n        delete reduced.tags[k];\n      }\n    });\n\n    // Remove any now empty items from the tags array.\n    reduced.tags = reduced.tags.filter(Boolean);\n\n    if (!reduced.tags.length) {\n      delete reduced.tags;\n    }\n  }\n\n  return reduced;\n}\n"]}