Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(openapi:reduce): pass opts #684

Merged
merged 8 commits into from
Nov 30, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 by the `/pet/{id}` path and the `GET` and `PUT` methods
kanadgupta marked this conversation as resolved.
Show resolved Hide resolved
- 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 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');

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops

paths: promptResults.paths,
methods: promptResults.methods,
})}`
);
Expand Down