Skip to content

Commit

Permalink
feat: creation of new openapi:reduce command to reduce large OpenAP…
Browse files Browse the repository at this point in the history
…I 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 <[email protected]>

* Update src/cmds/openapi/reduce.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/cmds/openapi/reduce.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/cmds/openapi/reduce.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/cmds/openapi/reduce.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/cmds/openapi/reduce.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/lib/prepareOas.ts

Co-authored-by: Kanad Gupta <[email protected]>

* Update src/lib/prepareOas.ts

Co-authored-by: Kanad Gupta <[email protected]>

* 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 <[email protected]>

Co-authored-by: Kanad Gupta <[email protected]>
  • Loading branch information
erunion and kanadgupta authored Aug 19, 2022
1 parent 3c9f4bf commit 074c13e
Show file tree
Hide file tree
Showing 14 changed files with 1,968 additions and 215 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ rdme validate [path-to-file.json]

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

#### Reducing an API Definition

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.

```sh
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).

### Docs

#### Syncing a Folder of Markdown Docs to ReadMe
Expand Down
24 changes: 24 additions & 0 deletions __tests__/__fixtures__/relative-ref-oas/petstore.json
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,30 @@
}
]
}
},
"/user": {
"post": {
"tags": ["user"],
"summary": "Create user",
"description": "This can only be done by the logged in user.",
"operationId": "createUser",
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "components/external-components.json#/schemas/User"
}
}
},
"description": "Created user object",
"required": true
},
"responses": {
"default": {
"description": "successful operation"
}
}
}
}
},
"components": {
Expand Down
15 changes: 9 additions & 6 deletions __tests__/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ Options
Related commands
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
"
`;

Expand All @@ -49,8 +50,9 @@ Options
Related commands
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
"
`;

Expand All @@ -76,8 +78,9 @@ Options
Related commands
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
$ rdme validate Validate your OpenAPI/Swagger definition.
$ rdme openapi:reduce Reduce an OpenAPI definition into a smaller subset.
$ rdme swagger Alias for \`rdme openapi\`. [deprecated]
"
`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,64 @@ Object {
],
},
},
"/user": Object {
"post": Object {
"description": "This can only be done by the logged in user.",
"operationId": "createUser",
"requestBody": Object {
"content": Object {
"application/json": Object {
"schema": Object {
"properties": Object {
"email": Object {
"type": "string",
},
"firstName": Object {
"type": "string",
},
"id": Object {
"format": "int64",
"type": "integer",
},
"lastName": Object {
"type": "string",
},
"password": Object {
"type": "string",
},
"phone": Object {
"type": "string",
},
"userStatus": Object {
"description": "User Status",
"format": "int32",
"type": "integer",
},
"username": Object {
"type": "string",
},
},
"type": "object",
"xml": Object {
"name": "User",
},
},
},
},
"description": "Created user object",
"required": true,
},
"responses": Object {
"default": Object {
"description": "successful operation",
},
},
"summary": "Create user",
"tags": Array [
"user",
],
},
},
},
"servers": Array [
Object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import config from 'config';
import nock from 'nock';
import prompts from 'prompts';

import OpenAPICommand from '../../src/cmds/openapi';
import SwaggerCommand from '../../src/cmds/swagger';
import APIError from '../../src/lib/apiError';
import getAPIMock from '../helpers/get-api-mock';
import OpenAPICommand from '../../../src/cmds/openapi';
import SwaggerCommand from '../../../src/cmds/swagger';
import APIError from '../../../src/lib/apiError';
import getAPIMock from '../../helpers/get-api-mock';

const openapi = new OpenAPICommand();
const swagger = new SwaggerCommand();
Expand Down
151 changes: 151 additions & 0 deletions __tests__/cmds/openapi/reduce.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/* eslint-disable no-console */
import fs from 'fs';

import chalk from 'chalk';
import prompts from 'prompts';

import OpenAPIReduceCommand from '../../../src/cmds/openapi/reduce';

const reducer = new OpenAPIReduceCommand();

const successfulReduction = () => 'Your reduced API definition has been saved to output.json! 🤏';

const testWorkingDir = process.cwd();

let consoleInfoSpy;
const getCommandOutput = () => consoleInfoSpy.mock.calls.join('\n\n');

describe('rdme openapi:reduce', () => {
beforeEach(() => {
jest.mock('fs');

consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation();
});

afterEach(() => {
consoleInfoSpy.mockRestore();

process.chdir(testWorkingDir);

jest.clearAllMocks();
});

describe('reducing', () => {
describe('by tag', () => {
it.each([
['OpenAPI 3.0', 'json', '3.0'],
['OpenAPI 3.0', 'yaml', '3.0'],
['OpenAPI 3.1', 'json', '3.1'],
['OpenAPI 3.1', 'yaml', '3.1'],
])('should support reducing a %s definition (format: %s)', async (_, format, specVersion) => {
const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);

let reducedSpec;
fs.writeFileSync = jest.fn((f, d) => {
reducedSpec = JSON.parse(d as string);
return true;
});

prompts.inject(['tags', ['pet'], 'output.json']);

await expect(
reducer.run({
spec,
})
).resolves.toBe(successfulReduction());

expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String));
expect(reducedSpec.tags).toHaveLength(1);
expect(Object.keys(reducedSpec.paths)).toStrictEqual([
'/pet',
'/pet/findByStatus',
'/pet/findByTags',
'/pet/{petId}',
'/pet/{petId}/uploadImage',
]);
});

it('should discover and upload an API definition if none is provided', async () => {
const spec = 'petstore.json';

let reducedSpec;
fs.writeFileSync = jest.fn((f, d) => {
reducedSpec = JSON.parse(d as string);
return true;
});

prompts.inject(['tags', ['user'], 'output.json']);

await expect(
reducer.run({
workingDirectory: './__tests__/__fixtures__/relative-ref-oas',
})
).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', () => {
it.each([
['OpenAPI 3.0', 'json', '3.0'],
['OpenAPI 3.0', 'yaml', '3.0'],
['OpenAPI 3.1', 'json', '3.1'],
['OpenAPI 3.1', 'yaml', '3.1'],
])('should support reducing a %s definition (format: %s)', async (_, format, specVersion) => {
const spec = require.resolve(`@readme/oas-examples/${specVersion}/${format}/petstore.${format}`);

let reducedSpec;
fs.writeFileSync = jest.fn((f, d) => {
reducedSpec = JSON.parse(d as string);
return true;
});

prompts.inject(['paths', ['/pet', '/pet/findByStatus'], ['get', 'post'], 'output.json']);

await expect(
reducer.run({
spec,
})
).resolves.toBe(successfulReduction());

expect(fs.writeFileSync).toHaveBeenCalledWith('output.json', expect.any(String));
expect(reducedSpec.tags).toHaveLength(1);
expect(Object.keys(reducedSpec.paths)).toStrictEqual(['/pet', '/pet/findByStatus']);
expect(Object.keys(reducedSpec.paths['/pet'])).toStrictEqual(['post']);
expect(Object.keys(reducedSpec.paths['/pet/findByStatus'])).toStrictEqual(['get']);
});
});
});

describe('error handling', () => {
it.each([['json'], ['yaml']])('should fail if given a Swagger 2.0 definition (format: %s)', async format => {
const spec = require.resolve(`@readme/oas-examples/2.0/${format}/petstore.${format}`);

await expect(
reducer.run({
spec,
})
).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 () => {
const spec = require.resolve('@readme/oas-examples/3.0/json/petstore.json');

prompts.inject(['tags', ['unknown-tag'], '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?')
);
});
});
});
7 changes: 6 additions & 1 deletion __tests__/lib/__snapshots__/commands.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,15 @@ Object {
"name": "openapi",
"position": 1,
},
Object {
"description": "Reduce an OpenAPI definition into a smaller subset.",
"name": "openapi:reduce",
"position": 3,
},
Object {
"description": "Alias for \`rdme openapi\`. [deprecated]",
"name": "swagger",
"position": 2,
"position": 4,
},
Object {
"description": "Validate your OpenAPI/Swagger definition.",
Expand Down
Loading

0 comments on commit 074c13e

Please sign in to comment.