Skip to content

Commit 5fc4cee

Browse files
kanadguptaerunion
andauthored
feat(openapi:reduce): pass opts (#684)
* 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>
1 parent 22ff103 commit 5fc4cee

File tree

3 files changed

+166
-4
lines changed

3 files changed

+166
-4
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,19 @@ We also offer a tool that allows you to reduce a large API definition down to a
246246
rdme openapi:reduce [path-to-file.json]
247247
```
248248

249-
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).
249+
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:
250+
251+
- The input API definition is called `petstore.json`
252+
- The file is reduced to only the `/pet/{id}` path and the `GET` and `PUT` methods
253+
- The output file is called `petstore-reduced.json`
254+
255+
Here's what the resulting command looks like:
256+
257+
```
258+
rdme openapi:reduce petstore.json --path /pet/{id} --method get --method put --out petstore-reduced.json
259+
```
260+
261+
As with the `openapi` command, you can also [omit the file path](#omitting-the-file-path).
250262

251263
### Docs (a.k.a. Guides) 📖
252264

__tests__/cmds/openapi/reduce.test.ts

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,30 @@ describe('rdme openapi:reduce', () => {
8585

8686
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/user']);
8787
});
88+
89+
it('should reduce with no prompts via opts', async () => {
90+
const spec = 'petstore.json';
91+
92+
let reducedSpec;
93+
fs.writeFileSync = jest.fn((fileName, data) => {
94+
reducedSpec = JSON.parse(data as string);
95+
});
96+
97+
await expect(
98+
reducer.run({
99+
workingDirectory: './__tests__/__fixtures__/relative-ref-oas',
100+
tag: ['user'],
101+
out: 'output.json',
102+
})
103+
).resolves.toBe(successfulReduction());
104+
105+
expect(console.info).toHaveBeenCalledTimes(1);
106+
107+
const output = getCommandOutput();
108+
expect(output).toBe(chalk.yellow(`ℹ️ We found ${spec} and are attempting to reduce it.`));
109+
110+
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/user']);
111+
});
88112
});
89113

90114
describe('by path', () => {
@@ -115,6 +139,34 @@ describe('rdme openapi:reduce', () => {
115139
expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']);
116140
expect(Object.keys(reducedSpec.paths['/pet/findByStatus'])).toStrictEqual(['get']);
117141
});
142+
143+
it('should reduce with no prompts via opts', async () => {
144+
const spec = 'petstore.json';
145+
146+
let reducedSpec;
147+
fs.writeFileSync = jest.fn((fileName, data) => {
148+
reducedSpec = JSON.parse(data as string);
149+
});
150+
151+
await expect(
152+
reducer.run({
153+
workingDirectory: './__tests__/__fixtures__/relative-ref-oas',
154+
path: ['/pet', '/pet/{petId}'],
155+
method: ['get', 'post'],
156+
out: 'output.json',
157+
})
158+
).resolves.toBe(successfulReduction());
159+
160+
expect(console.info).toHaveBeenCalledTimes(1);
161+
162+
const output = getCommandOutput();
163+
expect(output).toBe(chalk.yellow(`ℹ️ We found ${spec} and are attempting to reduce it.`));
164+
165+
expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String));
166+
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet', '/pet/{petId}']);
167+
expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']);
168+
expect(Object.keys(reducedSpec.paths['/pet/{petId}'])).toStrictEqual(['get']);
169+
});
118170
});
119171
});
120172

@@ -129,7 +181,7 @@ describe('rdme openapi:reduce', () => {
129181
).rejects.toStrictEqual(new Error('Sorry, this reducer feature in rdme only supports OpenAPI 3.0+ definitions.'));
130182
});
131183

132-
it('should fail if you attempt to reduce a spec to nothing', async () => {
184+
it('should fail if you attempt to reduce a spec to nothing via tags', async () => {
133185
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
134186

135187
prompts.inject(['tags', ['unknown-tag'], 'output.json']);
@@ -142,5 +194,56 @@ describe('rdme openapi:reduce', () => {
142194
new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?')
143195
);
144196
});
197+
198+
it('should fail if you attempt to reduce a spec to nothing via paths', async () => {
199+
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
200+
201+
prompts.inject(['paths', ['unknown-path'], 'output.json']);
202+
203+
await expect(
204+
reducer.run({
205+
spec,
206+
})
207+
).rejects.toStrictEqual(
208+
new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?')
209+
);
210+
});
211+
212+
it('should fail if you attempt to pass both tags and paths as opts', async () => {
213+
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
214+
215+
await expect(
216+
reducer.run({
217+
spec,
218+
tag: ['tag1', 'tag2'],
219+
path: ['/path'],
220+
})
221+
).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.'));
222+
});
223+
224+
it('should fail if you attempt to pass both tags and methods as opts', async () => {
225+
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
226+
227+
await expect(
228+
reducer.run({
229+
spec,
230+
tag: ['tag1', 'tag2'],
231+
method: ['get'],
232+
})
233+
).rejects.toStrictEqual(new Error('You can pass in either tags or paths/methods, but not both.'));
234+
});
235+
236+
it('should fail if you attempt to pass non-existent path and no method', async () => {
237+
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
238+
239+
await expect(
240+
reducer.run({
241+
spec,
242+
path: ['unknown-path'],
243+
})
244+
).rejects.toStrictEqual(
245+
new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?')
246+
);
247+
});
145248
});
146249
});

src/cmds/openapi/reduce.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import chalk from 'chalk';
77
import jsonpath from 'jsonpath';
88
import oasReducer from 'oas/dist/lib/reducer';
99
import ora from 'ora';
10+
import prompts from 'prompts';
1011

1112
import Command, { CommandCategories } from '../../lib/baseCommand';
1213
import { checkFilePath } from '../../lib/checkFile';
@@ -16,6 +17,10 @@ import promptTerminal from '../../lib/promptWrapper';
1617

1718
export type Options = {
1819
spec?: string;
20+
tag?: string[];
21+
path?: string[];
22+
method?: string[];
23+
out?: string;
1924
workingDirectory?: string;
2025
};
2126

@@ -36,6 +41,29 @@ export default class OpenAPIReduceCommand extends Command {
3641
type: String,
3742
defaultOption: true,
3843
},
44+
{
45+
name: 'tag',
46+
type: String,
47+
multiple: true,
48+
description: 'Tags to reduce by',
49+
},
50+
{
51+
name: 'path',
52+
type: String,
53+
multiple: true,
54+
description: 'Paths to reduce by',
55+
},
56+
{
57+
name: 'method',
58+
type: String,
59+
multiple: true,
60+
description: 'Methods to reduce by (can only be used alongside the `path` option)',
61+
},
62+
{
63+
name: 'out',
64+
type: String,
65+
description: 'Output file path to write reduced file to',
66+
},
3967
{
4068
name: 'workingDirectory',
4169
type: String,
@@ -60,6 +88,18 @@ export default class OpenAPIReduceCommand extends Command {
6088
throw new Error('Sorry, this reducer feature in rdme only supports OpenAPI 3.0+ definitions.');
6189
}
6290

91+
if ((opts.path?.length || opts.method?.length) && opts.tag?.length) {
92+
throw new Error('You can pass in either tags or paths/methods, but not both.');
93+
}
94+
95+
prompts.override({
96+
reduceBy: opts.tag?.length ? 'tags' : opts.path?.length ? 'paths' : undefined,
97+
tags: opts.tag,
98+
paths: opts.path,
99+
methods: opts.method,
100+
outputPath: opts.out,
101+
});
102+
63103
const promptResults = await promptTerminal([
64104
{
65105
type: 'select',
@@ -111,10 +151,17 @@ export default class OpenAPIReduceCommand extends Command {
111151
choices: (prev, values) => {
112152
const paths: string[] = values.paths;
113153
let methods = paths
114-
.map((p: string) => Object.keys(parsedBundledSpec.paths[p]))
154+
.map((p: string) => Object.keys(parsedBundledSpec.paths[p] || {}))
115155
.flat()
116156
.filter((method: string) => method.toLowerCase() !== 'parameters');
117157

158+
// We have to catch this case so prompt doesn't crash
159+
if (!methods.length && !opts.method?.length) {
160+
throw new Error(
161+
'All paths in the API definition were removed. Did you supply the right path name to reduce by?'
162+
);
163+
}
164+
118165
methods = [...new Set(methods)];
119166
methods.sort();
120167

@@ -140,7 +187,7 @@ export default class OpenAPIReduceCommand extends Command {
140187
Command.debug(
141188
`options being supplied to the reducer: ${JSON.stringify({
142189
tags: promptResults.tags,
143-
paths: promptResults.tags,
190+
paths: promptResults.paths,
144191
methods: promptResults.methods,
145192
})}`
146193
);

0 commit comments

Comments
 (0)