Skip to content

Commit

Permalink
feat(openapi:reduce): pass opts (#684)
Browse files Browse the repository at this point in the history
* chore: fix debug

* feat(openapi/reduce): ability to pass in opts

* test: fix test

* fix: fallback value when parsing through bad paths

* fix: another edge case

* docs: add language on this

* Update README.md

Co-authored-by: Jon Ursenbach <erunion@users.noreply.github.com>

* test: typo

Co-authored-by: Jon Ursenbach <erunion@users.noreply.github.com>
  • Loading branch information
kanadgupta and erunion authored Nov 30, 2022
1 parent 22ff103 commit 5fc4cee
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 4 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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:

```
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) 📖

Expand Down
105 changes: 104 additions & 1 deletion __tests__/cmds/openapi/reduce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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']);
});
});
});

Expand All @@ -129,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']);
Expand All @@ -142,5 +194,56 @@ 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 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']);

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');

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'],
method: ['get'],
})
).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?')
);
});
});
});
51 changes: 49 additions & 2 deletions src/cmds/openapi/reduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -16,6 +17,10 @@ import promptTerminal from '../../lib/promptWrapper';

export type Options = {
spec?: string;
tag?: string[];
path?: string[];
method?: string[];
out?: string;
workingDirectory?: string;
};

Expand All @@ -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,
Expand All @@ -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',
Expand Down Expand Up @@ -111,10 +151,17 @@ 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');

// 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();

Expand All @@ -140,7 +187,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,
})}`
);
Expand Down

0 comments on commit 5fc4cee

Please sign in to comment.