Skip to content

Commit 074c13e

Browse files
erunionkanadgupta
andauthored
feat: creation of new openapi:reduce command to reduce large OpenAPI definitions (#572)
* feat: porting over the `oas-reducer` CLI into a new `openapi:reduce` command * fix: requiring at least one method + adding docs * fix: alex * Update src/lib/prepareOas.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/lib/prepareOas.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * Update src/lib/prepareOas.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> * feat: adding a dynamic initial outputPath * feat: wrapping the reducer process in a spinner * fix: filter out non-tags from being handled as tags in the multiselect * Update src/cmds/openapi/reduce.ts Co-authored-by: Kanad Gupta <kgupta@umn.edu> Co-authored-by: Kanad Gupta <kgupta@umn.edu>
1 parent 3c9f4bf commit 074c13e

File tree

14 files changed

+1968
-215
lines changed

14 files changed

+1968
-215
lines changed

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,16 @@ rdme validate [path-to-file.json]
168168

169169
Similar to the `openapi` command, you can also [omit the file path](#omitting-the-file-path).
170170

171+
#### Reducing an API Definition
172+
173+
We also offer a tool that allows you to reduce a large API definition down to a specific set of tags or paths. This can be useful if you're debugging a problematic schema somewhere, or if you have a file that is too big to maintain.
174+
175+
```sh
176+
rdme openapi:reduce [path-to-file.json]
177+
```
178+
179+
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).
180+
171181
### Docs
172182

173183
#### Syncing a Folder of Markdown Docs to ReadMe

__tests__/__fixtures__/relative-ref-oas/petstore.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,30 @@
106106
}
107107
]
108108
}
109+
},
110+
"/user": {
111+
"post": {
112+
"tags": ["user"],
113+
"summary": "Create user",
114+
"description": "This can only be done by the logged in user.",
115+
"operationId": "createUser",
116+
"requestBody": {
117+
"content": {
118+
"application/json": {
119+
"schema": {
120+
"$ref": "components/external-components.json#/schemas/User"
121+
}
122+
}
123+
},
124+
"description": "Created user object",
125+
"required": true
126+
},
127+
"responses": {
128+
"default": {
129+
"description": "successful operation"
130+
}
131+
}
132+
}
109133
}
110134
},
111135
"components": {

__tests__/__snapshots__/index.test.ts.snap

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ Options
2222
2323
Related commands
2424
25-
$ rdme validate Validate your OpenAPI/Swagger definition.
26-
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
25+
$ rdme validate Validate your OpenAPI/Swagger definition.
26+
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
27+
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
2728
"
2829
`;
2930

@@ -49,8 +50,9 @@ Options
4950
5051
Related commands
5152
52-
$ rdme validate Validate your OpenAPI/Swagger definition.
53-
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
53+
$ rdme validate Validate your OpenAPI/Swagger definition.
54+
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
55+
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
5456
"
5557
`;
5658

@@ -76,8 +78,9 @@ Options
7678
7779
Related commands
7880
79-
$ rdme validate Validate your OpenAPI/Swagger definition.
80-
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
81+
$ rdme validate Validate your OpenAPI/Swagger definition.
82+
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
83+
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
8184
"
8285
`;
8386

__tests__/cmds/__snapshots__/openapi.test.ts.snap renamed to __tests__/cmds/openapi/__snapshots__/index.test.ts.snap

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,64 @@ Object {
479479
],
480480
},
481481
},
482+
"/user": Object {
483+
"post": Object {
484+
"description": "This can only be done by the logged in user.",
485+
"operationId": "createUser",
486+
"requestBody": Object {
487+
"content": Object {
488+
"application/json": Object {
489+
"schema": Object {
490+
"properties": Object {
491+
"email": Object {
492+
"type": "string",
493+
},
494+
"firstName": Object {
495+
"type": "string",
496+
},
497+
"id": Object {
498+
"format": "int64",
499+
"type": "integer",
500+
},
501+
"lastName": Object {
502+
"type": "string",
503+
},
504+
"password": Object {
505+
"type": "string",
506+
},
507+
"phone": Object {
508+
"type": "string",
509+
},
510+
"userStatus": Object {
511+
"description": "User Status",
512+
"format": "int32",
513+
"type": "integer",
514+
},
515+
"username": Object {
516+
"type": "string",
517+
},
518+
},
519+
"type": "object",
520+
"xml": Object {
521+
"name": "User",
522+
},
523+
},
524+
},
525+
},
526+
"description": "Created user object",
527+
"required": true,
528+
},
529+
"responses": Object {
530+
"default": Object {
531+
"description": "successful operation",
532+
},
533+
},
534+
"summary": "Create user",
535+
"tags": Array [
536+
"user",
537+
],
538+
},
539+
},
482540
},
483541
"servers": Array [
484542
Object {

__tests__/cmds/openapi.test.ts renamed to __tests__/cmds/openapi/index.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import config from 'config';
44
import nock from 'nock';
55
import prompts from 'prompts';
66

7-
import OpenAPICommand from '../../src/cmds/openapi';
8-
import SwaggerCommand from '../../src/cmds/swagger';
9-
import APIError from '../../src/lib/apiError';
10-
import getAPIMock from '../helpers/get-api-mock';
7+
import OpenAPICommand from '../../../src/cmds/openapi';
8+
import SwaggerCommand from '../../../src/cmds/swagger';
9+
import APIError from '../../../src/lib/apiError';
10+
import getAPIMock from '../../helpers/get-api-mock';
1111

1212
const openapi = new OpenAPICommand();
1313
const swagger = new SwaggerCommand();

__tests__/cmds/openapi/reduce.test.ts

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
/* eslint-disable no-console */
2+
import fs from 'fs';
3+
4+
import chalk from 'chalk';
5+
import prompts from 'prompts';
6+
7+
import OpenAPIReduceCommand from '../../../src/cmds/openapi/reduce';
8+
9+
const reducer = new OpenAPIReduceCommand();
10+
11+
const successfulReduction = () => 'Your reduced API definition has been saved to output.json! 🤏';
12+
13+
const testWorkingDir = process.cwd();
14+
15+
let consoleInfoSpy;
16+
const getCommandOutput = () => consoleInfoSpy.mock.calls.join('\n\n');
17+
18+
describe('rdme openapi:reduce', () => {
19+
beforeEach(() => {
20+
jest.mock('fs');
21+
22+
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
23+
});
24+
25+
afterEach(() => {
26+
consoleInfoSpy.mockRestore();
27+
28+
process.chdir(testWorkingDir);
29+
30+
jest.clearAllMocks();
31+
});
32+
33+
describe('reducing', () => {
34+
describe('by tag', () => {
35+
it.each([
36+
['OpenAPI 3.0', 'json', '3.0'],
37+
['OpenAPI 3.0', 'yaml', '3.0'],
38+
['OpenAPI 3.1', 'json', '3.1'],
39+
['OpenAPI 3.1', 'yaml', '3.1'],
40+
])('should support reducing a %s definition (format: %s)', async (_, format, specVersion) => {
41+
const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);
42+
43+
let reducedSpec;
44+
fs.writeFileSync = jest.fn((f, d) => {
45+
reducedSpec = JSON.parse(d as string);
46+
return true;
47+
});
48+
49+
prompts.inject(['tags', ['pet'], 'output.json']);
50+
51+
await expect(
52+
reducer.run({
53+
spec,
54+
})
55+
).resolves.toBe(successfulReduction());
56+
57+
expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String));
58+
expect(reducedSpec.tags).toHaveLength(1);
59+
expect(Object.keys(reducedSpec.paths)).toStrictEqual([
60+
'/pet',
61+
'/pet/findByStatus',
62+
'/pet/findByTags',
63+
'/pet/{petId}',
64+
'/pet/{petId}/uploadImage',
65+
]);
66+
});
67+
68+
it('should discover and upload an API definition if none is provided', async () => {
69+
const spec = 'petstore.json';
70+
71+
let reducedSpec;
72+
fs.writeFileSync = jest.fn((f, d) => {
73+
reducedSpec = JSON.parse(d as string);
74+
return true;
75+
});
76+
77+
prompts.inject(['tags', ['user'], 'output.json']);
78+
79+
await expect(
80+
reducer.run({
81+
workingDirectory: './__tests__/__fixtures__/relative-ref-oas',
82+
})
83+
).resolves.toBe(successfulReduction());
84+
85+
expect(console.info).toHaveBeenCalledTimes(1);
86+
87+
const output = getCommandOutput();
88+
expect(output).toBe(chalk.yellow(`ℹ️ We found ${spec} and are attempting to reduce it.`));
89+
90+
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/user']);
91+
});
92+
});
93+
94+
describe('by path', () => {
95+
it.each([
96+
['OpenAPI 3.0', 'json', '3.0'],
97+
['OpenAPI 3.0', 'yaml', '3.0'],
98+
['OpenAPI 3.1', 'json', '3.1'],
99+
['OpenAPI 3.1', 'yaml', '3.1'],
100+
])('should support reducing a %s definition (format: %s)', async (_, format, specVersion) => {
101+
const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);
102+
103+
let reducedSpec;
104+
fs.writeFileSync = jest.fn((f, d) => {
105+
reducedSpec = JSON.parse(d as string);
106+
return true;
107+
});
108+
109+
prompts.inject(['paths', ['/pet', '/pet/findByStatus'], ['get', 'post'], 'output.json']);
110+
111+
await expect(
112+
reducer.run({
113+
spec,
114+
})
115+
).resolves.toBe(successfulReduction());
116+
117+
expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String));
118+
expect(reducedSpec.tags).toHaveLength(1);
119+
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet', '/pet/findByStatus']);
120+
expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']);
121+
expect(Object.keys(reducedSpec.paths['/pet/findByStatus'])).toStrictEqual(['get']);
122+
});
123+
});
124+
});
125+
126+
describe('error handling', () => {
127+
it.each([['json'], ['yaml']])('should fail if given a Swagger 2.0 definition (format: %s)', async format => {
128+
const spec = require.resolve(`@readme/oas-examples/2.0/${format}/petstore.${format}`);
129+
130+
await expect(
131+
reducer.run({
132+
spec,
133+
})
134+
).rejects.toStrictEqual(new Error('Sorry, this reducer feature in rdme only supports OpenAPI 3.0+ definitions.'));
135+
});
136+
137+
it('should fail if you attempt to reduce a spec to nothing', async () => {
138+
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');
139+
140+
prompts.inject(['tags', ['unknown-tag'], 'output.json']);
141+
142+
await expect(
143+
reducer.run({
144+
spec,
145+
})
146+
).rejects.toStrictEqual(
147+
new Error('All paths in the API definition were removed. Did you supply the right path name to reduce by?')
148+
);
149+
});
150+
});
151+
});

__tests__/lib/__snapshots__/commands.test.ts.snap

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,15 @@ Object {
2929
"name": "openapi",
3030
"position": 1,
3131
},
32+
Object {
33+
"description": "Reduce an OpenAPI definition into a smaller subset.",
34+
"name": "openapi:reduce",
35+
"position": 3,
36+
},
3237
Object {
3338
"description": "Alias for \`rdme openapi\`. [deprecated]",
3439
"name": "swagger",
35-
"position": 2,
40+
"position": 4,
3641
},
3742
Object {
3843
"description": "Validate your OpenAPI/Swagger definition.",

0 commit comments

Comments
 (0)