From 61660fc7b508fe94f98cbdd7d0b81353101f46e9 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 11:19:58 -0600 Subject: [PATCH 1/8] chore: fix debug --- src/cmds/openapi/reduce.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cmds/openapi/reduce.ts b/src/cmds/openapi/reduce.ts index 601f50788..02d7d3ec0 100644 --- a/src/cmds/openapi/reduce.ts +++ b/src/cmds/openapi/reduce.ts @@ -140,7 +140,7 @@ export default class OpenAPIReduceCommand extends Command { Command.debug( `options being supplied to the reducer: ${JSON.stringify({ tags: promptResults.tags, - paths: promptResults.tags, + paths: promptResults.paths, methods: promptResults.methods, })}` ); From 5f3ff11bc889a9fa46c23cd9da2584075fbfbaa3 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 11:37:48 -0600 Subject: [PATCH 2/8] feat(openapi/reduce): ability to pass in opts --- __tests__/cmds/openapi/reduce.test.ts | 76 +++++++++++++++++++++++++++ src/cmds/openapi/reduce.ts | 40 ++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/__tests__/cmds/openapi/reduce.test.ts b/__tests__/cmds/openapi/reduce.test.ts index e003e4e72..4f60733ac 100644 --- a/__tests__/cmds/openapi/reduce.test.ts +++ b/__tests__/cmds/openapi/reduce.test.ts @@ -85,6 +85,30 @@ describe('rdme openapi:reduce', () => { expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/user']); }); + + it('should reduce with no prompts via opts', async () => { + const spec = 'petstore.json'; + + let reducedSpec; + fs.writeFileSync = jest.fn((fileName, data) => { + reducedSpec = JSON.parse(data as string); + }); + + await expect( + reducer.run({ + workingDirectory: './__tests__/__fixtures__/relative-ref-oas', + tag: ['user'], + out: 'output.json', + }) + ).resolves.toBe(successfulReduction()); + + expect(console.info).toHaveBeenCalledTimes(1); + + const output = getCommandOutput(); + expect(output).toBe(chalk.yellow(`ℹ️ We found ${spec} and are attempting to reduce it.`)); + + expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/user']); + }); }); describe('by path', () => { @@ -115,6 +139,34 @@ describe('rdme openapi:reduce', () => { expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']); expect(Object.keys(reducedSpec.paths['/pet/findByStatus'])).toStrictEqual(['get']); }); + + it('should reduce with no prompts via opts', async () => { + const spec = 'petstore.json'; + + let reducedSpec; + fs.writeFileSync = jest.fn((fileName, data) => { + reducedSpec = JSON.parse(data as string); + }); + + await expect( + reducer.run({ + workingDirectory: './__tests__/__fixtures__/relative-ref-oas', + path: ['/pet', '/pet/{petId}'], + method: ['get', 'post'], + out: 'output.json', + }) + ).resolves.toBe(successfulReduction()); + + expect(console.info).toHaveBeenCalledTimes(1); + + const output = getCommandOutput(); + expect(output).toBe(chalk.yellow(`ℹ️ We found ${spec} and are attempting to reduce it.`)); + + expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String)); + expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet', '/pet/{petId}']); + expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']); + expect(Object.keys(reducedSpec.paths['/pet/{petId}'])).toStrictEqual(['get']); + }); }); }); @@ -142,5 +194,29 @@ describe('rdme openapi:reduce', () => { new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?') ); }); + + it('should fail if you attempt to pass both tags and paths as opts', async () => { + const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); + + await expect( + reducer.run({ + spec, + tag: ['tag1', 'tag2'], + path: ['/path'], + }) + ).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.')); + }); + + it('should fail if you attempt to pass both tags and methods as opts', async () => { + const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); + + await expect( + reducer.run({ + spec, + tag: ['tag1', 'tag2'], + path: ['/path'], + }) + ).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.')); + }); }); }); diff --git a/src/cmds/openapi/reduce.ts b/src/cmds/openapi/reduce.ts index 02d7d3ec0..3f99b062f 100644 --- a/src/cmds/openapi/reduce.ts +++ b/src/cmds/openapi/reduce.ts @@ -7,6 +7,7 @@ import chalk from 'chalk'; import jsonpath from 'jsonpath'; import oasReducer from 'oas/dist/lib/reducer'; import ora from 'ora'; +import prompts from 'prompts'; import Command, { CommandCategories } from '../../lib/baseCommand'; import { checkFilePath } from '../../lib/checkFile'; @@ -16,6 +17,10 @@ import promptTerminal from '../../lib/promptWrapper'; export type Options = { spec?: string; + tag?: string[]; + path?: string[]; + method?: string[]; + out?: string; workingDirectory?: string; }; @@ -36,6 +41,29 @@ export default class OpenAPIReduceCommand extends Command { type: String, defaultOption: true, }, + { + name: 'tag', + type: String, + multiple: true, + description: 'Tags to reduce by', + }, + { + name: 'path', + type: String, + multiple: true, + description: 'Paths to reduce by', + }, + { + name: 'method', + type: String, + multiple: true, + description: 'Methods to reduce by (can only be used alongside the `path` option)', + }, + { + name: 'out', + type: String, + description: 'Output file path to write reduced file to', + }, { name: 'workingDirectory', type: String, @@ -60,6 +88,18 @@ export default class OpenAPIReduceCommand extends Command { throw new Error('Sorry, this reducer feature in rdme only supports OpenAPI 3.0+ definitions.'); } + if ((opts.path?.length || opts.method?.length) && opts.tag?.length) { + throw new Error('You can pass in either tags or paths/methods, but not both.'); + } + + prompts.override({ + reduceBy: opts.tag?.length ? 'tags' : opts.path?.length ? 'paths' : undefined, + tags: opts.tag, + paths: opts.path, + methods: opts.method, + outputPath: opts.out, + }); + const promptResults = await promptTerminal([ { type: 'select', From b7faf4299d6f069e41794dc962e190626e8598b5 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 11:38:47 -0600 Subject: [PATCH 3/8] test: fix test --- __tests__/cmds/openapi/reduce.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/cmds/openapi/reduce.test.ts b/__tests__/cmds/openapi/reduce.test.ts index 4f60733ac..c77bfd041 100644 --- a/__tests__/cmds/openapi/reduce.test.ts +++ b/__tests__/cmds/openapi/reduce.test.ts @@ -214,7 +214,7 @@ describe('rdme openapi:reduce', () => { reducer.run({ spec, tag: ['tag1', 'tag2'], - path: ['/path'], + method: ['get'], }) ).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.')); }); From 14cd4f86e3eb9d62d27a084e5a2f1877202b90f2 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 11:55:18 -0600 Subject: [PATCH 4/8] fix: fallback value when parsing through bad paths --- __tests__/cmds/openapi/reduce.test.ts | 16 +++++++++++++++- src/cmds/openapi/reduce.ts | 2 +- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/__tests__/cmds/openapi/reduce.test.ts b/__tests__/cmds/openapi/reduce.test.ts index c77bfd041..4980358bc 100644 --- a/__tests__/cmds/openapi/reduce.test.ts +++ b/__tests__/cmds/openapi/reduce.test.ts @@ -181,7 +181,7 @@ describe('rdme openapi:reduce', () => { ).rejects.toStrictEqual(new Error('Sorry, this reducer feature in rdme only supports OpenAPI 3.0+ definitions.')); }); - it('should fail if you attempt to reduce a spec to nothing', async () => { + it('should fail if you attempt to reduce a spec to nothing via tags', async () => { const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); prompts.inject(['tags', ['unknown-tag'], 'output.json']); @@ -195,6 +195,20 @@ describe('rdme openapi:reduce', () => { ); }); + it('should fail if you attempt to reduce a spec to nothin via paths', async () => { + const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); + + prompts.inject(['paths', ['unknown-path'], 'output.json']); + + await expect( + reducer.run({ + spec, + }) + ).rejects.toStrictEqual( + new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?') + ); + }); + it('should fail if you attempt to pass both tags and paths as opts', async () => { const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); diff --git a/src/cmds/openapi/reduce.ts b/src/cmds/openapi/reduce.ts index 3f99b062f..d7e7991ef 100644 --- a/src/cmds/openapi/reduce.ts +++ b/src/cmds/openapi/reduce.ts @@ -151,7 +151,7 @@ export default class OpenAPIReduceCommand extends Command { choices: (prev, values) => { const paths: string[] = values.paths; let methods = paths - .map((p: string) => Object.keys(parsedBundledSpec.paths[p])) + .map((p: string) => Object.keys(parsedBundledSpec.paths[p] || {})) .flat() .filter((method: string) => method.toLowerCase() !== 'parameters'); From d952630195a56a9b672c771d34589c72f1cbebca Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 12:59:32 -0600 Subject: [PATCH 5/8] fix: another edge case --- __tests__/cmds/openapi/reduce.test.ts | 13 +++++++++++++ src/cmds/openapi/reduce.ts | 7 +++++++ 2 files changed, 20 insertions(+) diff --git a/__tests__/cmds/openapi/reduce.test.ts b/__tests__/cmds/openapi/reduce.test.ts index 4980358bc..e0eb6940a 100644 --- a/__tests__/cmds/openapi/reduce.test.ts +++ b/__tests__/cmds/openapi/reduce.test.ts @@ -232,5 +232,18 @@ describe('rdme openapi:reduce', () => { }) ).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.')); }); + + it('should fail if you attempt to pass non-existent path and no method', async () => { + const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); + + await expect( + reducer.run({ + spec, + path: ['unknown-path'], + }) + ).rejects.toStrictEqual( + new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?') + ); + }); }); }); diff --git a/src/cmds/openapi/reduce.ts b/src/cmds/openapi/reduce.ts index d7e7991ef..a4a192d99 100644 --- a/src/cmds/openapi/reduce.ts +++ b/src/cmds/openapi/reduce.ts @@ -155,6 +155,13 @@ export default class OpenAPIReduceCommand extends Command { .flat() .filter((method: string) => method.toLowerCase() !== 'parameters'); + // We have to catch this case so prompt doesn't crash + if (!methods.length && !opts.method?.length) { + throw new Error( + 'All paths in the API definition were removed. Did you supply the right path name to reduce by?' + ); + } + methods = [...new Set(methods)]; methods.sort(); From a121e5d55b964806b93b8fa44a61593dc4bb1601 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 13:16:12 -0600 Subject: [PATCH 6/8] docs: add language on this --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c041e1ddc..05147af88 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,19 @@ We also offer a tool that allows you to reduce a large API definition down to a rdme openapi:reduce [path-to-file.json] ``` -The command will ask you a couple questions about how you wish to reduce the file and then do so. And as with the `openapi` command, you can also [omit the file path](#omitting-the-file-path). +The command will ask you a couple questions about how you wish to reduce the file and then do so. If you wish to automate this command, you can pass in CLI arguments to bypass the prompts. Here's an example use case: + +- The input API definition is called `petstore.json` +- The file is reduced by the `/pet/{id}` path and the `GET` and `PUT` methods +- The output file is called `petstore-reduced.json` + +Here's what the resulting command looks like: + +``` +rdme openapi:reduce petstore.json --path /pet/{id} --method get --method put --out petstore-reduced.json +``` + +As with the `openapi` command, you can also [omit the file path](#omitting-the-file-path). ### Docs (a.k.a. Guides) 📖 From ccd34db65a7b5c8f7783c4f6a7a838d79e6c6511 Mon Sep 17 00:00:00 2001 From: Kanad Gupta <8854718+kanadgupta@users.noreply.github.com> Date: Wed, 30 Nov 2022 13:21:09 -0600 Subject: [PATCH 7/8] Update README.md Co-authored-by: Jon Ursenbach --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05147af88..d35e64524 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,7 @@ rdme openapi:reduce [path-to-file.json] The command will ask you a couple questions about how you wish to reduce the file and then do so. If you wish to automate this command, you can pass in CLI arguments to bypass the prompts. Here's an example use case: - The input API definition is called `petstore.json` -- The file is reduced by the `/pet/{id}` path and the `GET` and `PUT` methods +- The file is reduced to only the `/pet/{id}` path and the `GET` and `PUT` methods - The output file is called `petstore-reduced.json` Here's what the resulting command looks like: From 48d25c3b32d90aa4576400c3e811cbac3a872650 Mon Sep 17 00:00:00 2001 From: Kanad Gupta Date: Wed, 30 Nov 2022 13:21:31 -0600 Subject: [PATCH 8/8] test: typo --- __tests__/cmds/openapi/reduce.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/cmds/openapi/reduce.test.ts b/__tests__/cmds/openapi/reduce.test.ts index e0eb6940a..df7107c5b 100644 --- a/__tests__/cmds/openapi/reduce.test.ts +++ b/__tests__/cmds/openapi/reduce.test.ts @@ -195,7 +195,7 @@ describe('rdme openapi:reduce', () => { ); }); - it('should fail if you attempt to reduce a spec to nothin via paths', async () => { + it('should fail if you attempt to reduce a spec to nothing via paths', async () => { const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json'); prompts.inject(['paths', ['unknown-path'], 'output.json']);