oas 25.2.1 → 25.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -1
- package/dist/reducer/index.cjs +7 -1
- package/dist/reducer/index.cjs.map +1 -1
- package/dist/reducer/index.js +7 -1
- package/dist/reducer/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -202,6 +202,7 @@ const operation = petstore.operation('/pet', 'post');
|
|
|
202
202
|
| `.getRequestBodyExamples()` | Retrieve an array of all request body examples that this operation has defined. |
|
|
203
203
|
| `.getRequestBodyMediaTypes()` | Retrieve a list of all the media/content types that the operation can accept a request body payload for. |
|
|
204
204
|
| `.hasRequestBody()` | Determine if this operation has a request body defined. |
|
|
205
|
+
| `.hasRequiredRequestBody()` | Determine if this operation has a required request body. |
|
|
205
206
|
<!-- prettier-ignore-end -->
|
|
206
207
|
|
|
207
208
|
#### Responses
|
|
@@ -213,7 +214,6 @@ const operation = petstore.operation('/pet', 'post');
|
|
|
213
214
|
| `.getResponseByStatusCode()` | Retrieve the raw response object for a given status code. |
|
|
214
215
|
| `.getResponseExamples()` | Retrieve an array of all response examples that this operation has defined. |
|
|
215
216
|
| `.getResponseStatusCodes()` | Retrieve all status codes that this operation may respond with. |
|
|
216
|
-
| `.hasRequiredRequestBody()` | Determine if this operation has a required request body. |
|
|
217
217
|
<!-- prettier-ignore-end -->
|
|
218
218
|
|
|
219
219
|
#### Security
|
|
@@ -297,6 +297,9 @@ console.log(await analyzer(petstore));
|
|
|
297
297
|
|
|
298
298
|
#### Reducer
|
|
299
299
|
|
|
300
|
+
> [!WARNING]
|
|
301
|
+
> This API is still very experimental and should not be used in production environments!
|
|
302
|
+
|
|
300
303
|
The reducer, `oas/reducer`, can be used to reduce an OpenAPI definition down to only the information necessary for a specific set of tags, paths, or operations. OpenAPI reduction can be helpful to isolate and troubleshoot issues with a very large API definition -- all while still having a fully functional and valid OpenAPI definition.
|
|
301
304
|
|
|
302
305
|
```ts
|
package/dist/reducer/index.cjs
CHANGED
|
@@ -14,6 +14,9 @@ function accumulateUsedRefs(schema, $refs, $ref) {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
getUsedRefs($refSchema).forEach(({ value: currRef }) => {
|
|
17
|
+
if (typeof currRef !== "string") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
17
20
|
if ($refs.has(currRef)) {
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
@@ -98,7 +101,10 @@ function reducer(definition, opts = {}) {
|
|
|
98
101
|
if ("components" in reduced) {
|
|
99
102
|
Object.keys(reduced.components).forEach((componentType) => {
|
|
100
103
|
Object.keys(reduced.components[componentType]).forEach((component) => {
|
|
101
|
-
|
|
104
|
+
const refIsUsed = $refs.has(`#/components/${componentType}/${component}`) || Array.from($refs).some((ref) => {
|
|
105
|
+
return ref.startsWith(`#/components/${componentType}/${component}/`);
|
|
106
|
+
});
|
|
107
|
+
if (!refIsUsed) {
|
|
102
108
|
delete reduced.components[componentType][component];
|
|
103
109
|
}
|
|
104
110
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/erunion/code/readme/oas/packages/oas/dist/reducer/index.cjs","../../src/reducer/index.ts"],"names":[],"mappings":"AAAA;AACE;AACF,yDAAA;AACA;AACA;ACFA,oGAAwB;AAgBxB,SAAS,WAAA,CAAY,MAAA,EAAa;AAChC,EAAA,OAAO,qCAAA,CAAO,aAAa,CAAA,EAAG,MAAM,CAAA;AACtC;AAUA,SAAS,kBAAA,CAAmB,MAAA,EAAiC,KAAA,EAAoB,IAAA,EAAiB;AAChG,EAAA,IAAI,UAAA;AACJ,EAAA,GAAA,CAAI,OAAO,KAAA,IAAS,QAAA,EAAU,WAAA,EAAa,qBAAA,CAAY,GAAA,CAAI,MAAA,EAAQ,IAAA,CAAK,SAAA,CAAU,CAAC,CAAC,CAAA;AACpF,EAAA,GAAA,CAAI,WAAA,IAAe,KAAA,CAAA,EAAW;AAI5B,IAAA,MAAA;AAAA,EACF;AAEA,EAAA,WAAA,CAAY,UAAU,CAAA,CAAE,OAAA,CAAQ,CAAC,EAAE,KAAA,EAAO,QAAQ,CAAA,EAAA,GAAM;AAEtD,IAAA,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,OAAO,CAAA,EAAG;AACtB,MAAA,MAAA;AAAA,IACF;AAEA,IAAA,KAAA,CAAM,GAAA,CAAI,OAAO,CAAA;AACjB,IAAA,kBAAA,CAAmB,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AAAA,EAC3C,CAAC,CAAA;AACH;AAiBe,SAAR,OAAA,CAAyB,UAAA,EAAyB,KAAA,EAAuB,CAAC,CAAA,EAAG;AAElF,EAAA,MAAM,WAAA,EAAa,OAAA,GAAU,KAAA,EAAO,IAAA,CAAK,IAAA,CAAK,GAAA,CAAI,CAAA,GAAA,EAAA,GAAO,GAAA,CAAI,WAAA,CAAY,CAAC,EAAA,EAAI,CAAC,CAAA;AAC/E,EAAA,MAAM,YAAA,EACJ,QAAA,GAAW,KAAA,EACP,MAAA,CAAO,OAAA,CAAQ,IAAA,CAAK,KAAK,CAAA,CAAE,MAAA,CAAO,CAAC,GAAA,EAAwC,CAAC,GAAA,EAAK,KAAK,CAAA,EAAA,GAAM;AAC1F,IAAA,MAAM,OAAA,EAAS,GAAA,CAAI,WAAA,CAAY,CAAA;AAC/B,IAAA,MAAM,SAAA,EAAW,KAAA,CAAM,OAAA,CAAQ,KAAK,EAAA,EAAI,KAAA,CAAM,GAAA,CAAI,CAAA,CAAA,EAAA,GAAK,CAAA,CAAE,WAAA,CAAY,CAAC,EAAA,EAAI,KAAA,CAAM,WAAA,CAAY,CAAA;AAC5F,IAAA,GAAA,CAAI,MAAM,EAAA,EAAI,QAAA;AACd,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,CAAC,EAAA,EACL,CAAC,CAAA;AAEP,EAAA,MAAM,MAAA,kBAAqB,IAAI,GAAA,CAAI,CAAA;AACnC,EAAA,MAAM,SAAA,kBAAwB,IAAI,GAAA,CAAI,CAAA;AAEtC,EAAA,GAAA,CAAI,CAAC,UAAA,CAAW,OAAA,EAAS;AACvB,IAAA,MAAM,IAAI,KAAA,CAAM,gDAAgD,CAAA;AAAA,EAClE;AAIA,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,SAAA,CAAU,UAAU,CAAC,CAAA;AAGrD,EAAA,GAAA,CAAI,WAAA,GAAc,OAAA,EAAS;AACzB,IAAA,MAAA,CAAO,MAAA,CAAO,OAAA,CAAQ,QAAQ,CAAA,CAAE,OAAA,CAAQ,CAAA,GAAA,EAAA,GAAO;AAC7C,MAAA,MAAA,CAAO,IAAA,CAAK,GAAG,CAAA,CAAE,OAAA,CAAQ,CAAA,MAAA,EAAA,GAAU;AACjC,QAAA,KAAA,CAAM,GAAA,CAAI,CAAA,6BAAA,EAAgC,MAAM,CAAA,CAAA;AACjD,MAAA;AACF,IAAA;AACH,EAAA;AAEwB,EAAA;AACqB,IAAA;AACT,MAAA;AAEK,MAAA;AACL,QAAA;AACH,UAAA;AACzB,UAAA;AACF,QAAA;AACF,MAAA;AAE0C,MAAA;AAEX,QAAA;AACU,UAAA;AAG3B,YAAA;AAG2B,cAAA;AACjC,cAAA;AACF,YAAA;AACF,UAAA;AACF,QAAA;AAE4C,QAAA;AAGrB,QAAA;AACO,UAAA;AACO,YAAA;AACjC,YAAA;AACuC,UAAA;AACN,YAAA;AACjC,YAAA;AACF,UAAA;AACF,QAAA;AAGyB,QAAA;AACiB,UAAA;AACtB,YAAA;AACjB,UAAA;AACH,QAAA;AAG6C,QAAA;AAC9B,UAAA;AACd,QAAA;AAG4B,QAAA;AACe,UAAA;AACL,YAAA;AACS,cAAA;AAC3C,YAAA;AACF,UAAA;AACH,QAAA;AACD,MAAA;AAG6C,MAAA;AACnB,QAAA;AAC3B,MAAA;AACD,IAAA;AAIuC,IAAA;AACtB,MAAA;AAClB,IAAA;AACF,EAAA;AAGkD,EAAA;AAGrB,EAAA;AACc,IAAA;AACQ,MAAA;AACG,QAAA;AACL,UAAA;AAC3C,QAAA;AACD,MAAA;AAGiD,MAAA;AACT,QAAA;AACzC,MAAA;AACD,IAAA;AAG4C,IAAA;AAC5B,MAAA;AACjB,IAAA;AACF,EAAA;AAGuB,EAAA;AAC+B,IAAA;AACrB,MAAA;AACN,QAAA;AACvB,MAAA;AACD,IAAA;AAGyC,IAAA;AAEhB,IAAA;AACT,MAAA;AACjB,IAAA;AACF,EAAA;AAEO,EAAA;AACT;ADzFyD;AACA;AACA","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 // 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 = {}) {\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 (!$refs.has(`#/components/${componentType}/${component}`)) {\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"]}
|
|
1
|
+
{"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 = {}) {\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"]}
|
package/dist/reducer/index.js
CHANGED
|
@@ -14,6 +14,9 @@ function accumulateUsedRefs(schema, $refs, $ref) {
|
|
|
14
14
|
return;
|
|
15
15
|
}
|
|
16
16
|
getUsedRefs($refSchema).forEach(({ value: currRef }) => {
|
|
17
|
+
if (typeof currRef !== "string") {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
17
20
|
if ($refs.has(currRef)) {
|
|
18
21
|
return;
|
|
19
22
|
}
|
|
@@ -98,7 +101,10 @@ function reducer(definition, opts = {}) {
|
|
|
98
101
|
if ("components" in reduced) {
|
|
99
102
|
Object.keys(reduced.components).forEach((componentType) => {
|
|
100
103
|
Object.keys(reduced.components[componentType]).forEach((component) => {
|
|
101
|
-
|
|
104
|
+
const refIsUsed = $refs.has(`#/components/${componentType}/${component}`) || Array.from($refs).some((ref) => {
|
|
105
|
+
return ref.startsWith(`#/components/${componentType}/${component}/`);
|
|
106
|
+
});
|
|
107
|
+
if (!refIsUsed) {
|
|
102
108
|
delete reduced.components[componentType][component];
|
|
103
109
|
}
|
|
104
110
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/reducer/index.ts"],"sourcesContent":["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 // 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 = {}) {\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 (!$refs.has(`#/components/${componentType}/${component}`)) {\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"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/reducer/index.ts"],"sourcesContent":["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 = {}) {\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"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oas",
|
|
3
|
-
"version": "25.2.
|
|
3
|
+
"version": "25.2.2",
|
|
4
4
|
"description": "Comprehensive tooling for working with OpenAPI definitions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ReadMe <support@readme.io> (https://readme.com)",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"vitest": "^2.1.3"
|
|
110
110
|
},
|
|
111
111
|
"prettier": "@readme/eslint-config/prettier",
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "319ae00a13f32dba48868ac7116aa050dc4be655"
|
|
113
113
|
}
|