Skip to content

Commit

Permalink
feat: generate models from components in OpenAPI (#1984)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonaslagoni authored May 6, 2024
1 parent 9deacc7 commit 43b46b7
Show file tree
Hide file tree
Showing 14 changed files with 282 additions and 47 deletions.
1 change: 1 addition & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ There are one way to generate models from an OpenAPI document

- [Generate from OpenAPI 3.0 JS object](../examples/openapi-from-object)
- [Generate from OpenAPI 3.1 JS object](../examples/openapi-v3_1-from-object)
- [Generate from OpenAPI components](../examples/openapi-include-components)

The OpenAPI input processor expects that the property `openapi` is defined in order to know it should be processed.

Expand Down
3 changes: 3 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ These examples show a specific input and how they can be used:
- [json-schema-single-enum-as-const](./json-schema-single-enum-as-const) - An advanced example that shows how to change how `enum` are interpreted when containing a single value.
- [json-schema-additional-properties-representation](./json-schema-additional-properties-representation) - An advanced example that shows how to change the property name for additional properties
- [swagger2.0-from-object](./swagger2.0-from-object) - A basic example where a Swagger 2.0 JS object is used to generate models.
- [openapi-from-object](./openapi-from-object) - A basic example where an OpenAPI v3.0 JS object is used to generate models.
- [openapi-include-components](./openapi-include-components) - A basic example where an OpenAPI document without paths is used to generate models by iterating the components.
- [openapi-v3_1-from-object](./openapi-v3_1-from-object) - A basic example where an OpenAPI v3.1 JS object is used to generate models.
- [meta-model](./meta-model) - A basic example how to provide a meta model for the generator

## General examples
Expand Down
17 changes: 17 additions & 0 deletions examples/openapi-include-components/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OpenAPI include components

A basic example of how to use Modelina with an OpenAPI document that iterates the components section to determine models.

## How to run this example

Run this example using:

```sh
npm i && npm run start
```

If you are on Windows, use the `start:windows` script instead:

```sh
npm i && npm run start:windows
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Should be able to process a OpenAPI document that has no paths and should log expected output to console 1`] = `
Array [
"class TestSchema {
private _email?: string;
constructor(input: {
email?: string,
}) {
this._email = input.email;
}
get email(): string | undefined { return this._email; }
set email(email: string | undefined) { this._email = email; }
}",
]
`;
14 changes: 14 additions & 0 deletions examples/openapi-include-components/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const spy = jest.spyOn(global.console, 'log').mockImplementation(() => {
return;
});
import { generate } from './index';
describe('Should be able to process a OpenAPI document that has no paths', () => {
afterAll(() => {
jest.restoreAllMocks();
});
test('and should log expected output to console', async () => {
await generate();
expect(spy.mock.calls.length).toEqual(1);
expect(spy.mock.calls[0]).toMatchSnapshot();
});
});
39 changes: 39 additions & 0 deletions examples/openapi-include-components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { TypeScriptGenerator } from '../../src';

const generator = new TypeScriptGenerator({
processorOptions: { openapi: { includeComponentSchemas: true } }
});
const swaggerDocument = {
openapi: '3.0.3',
info: {
version: '0.1',
title: 'Simple basic api'
},
paths: {},
components: {
schemas: {
TestSchema: {
$schema: 'http://json-schema.org/draft-07/schema#',
type: 'object',
additionalProperties: false,
properties: {
email: {
type: 'string',
format: 'email'
}
}
}
}
}
};

export async function generate(): Promise<void> {
const models = await generator.generate(swaggerDocument);
for (const model of models) {
console.log(model.result);
}
}

if (require.main === module) {
generate();
}
10 changes: 10 additions & 0 deletions examples/openapi-include-components/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions examples/openapi-include-components/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"config" : { "example_name" : "openapi-include-components" },
"scripts": {
"install": "cd ../.. && npm i",
"start": "../../node_modules/.bin/ts-node --cwd ../../ ./examples/$npm_package_config_example_name/index.ts",
"start:windows": "..\\..\\node_modules\\.bin\\ts-node --cwd ..\\..\\ .\\examples\\%npm_package_config_example_name%\\index.ts",
"test": "../../node_modules/.bin/jest --config=../../jest.config.js ./examples/$npm_package_config_example_name/index.spec.ts",
"test:windows": "..\\..\\node_modules\\.bin\\jest --config=..\\..\\jest.config.js examples/%npm_package_config_example_name%/index.spec.ts"
}
}
2 changes: 2 additions & 0 deletions src/models/ProcessorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ import { ParseOptions } from '@asyncapi/parser';
import { InterpreterOptions } from '../interpreter/Interpreter';
import {
JsonSchemaProcessorOptions,
OpenAPIInputProcessorOptions,
TypeScriptInputProcessorOptions
} from '../processors/index';

export interface ProcessorOptions {
asyncapi?: ParseOptions;
openapi?: OpenAPIInputProcessorOptions;
typescript?: TypeScriptInputProcessorOptions;
jsonSchema?: JsonSchemaProcessorOptions;
/**
Expand Down
22 changes: 19 additions & 3 deletions src/processors/OpenAPIInputProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import { InputMetaModel, OpenapiV3Schema, ProcessorOptions } from '../models';
import { Logger } from '../utils';
import SwaggerParser from '@apidevtools/swagger-parser';
import { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
export interface OpenAPIInputProcessorOptions {
includeComponentSchemas?: boolean;
}

/**
* Class for processing OpenAPI V3.0 inputs
* Class for processing OpenAPI inputs
*/
export class OpenAPIInputProcessor extends AbstractInputProcessor {
static supportedVersions = ['3.0.0', '3.0.1', '3.0.2', '3.0.3', '3.1.0'];

/**
* Process the input as a OpenAPI V3.0 document
* Process the input as a OpenAPI document
*
* @param input
*/
Expand All @@ -32,12 +35,25 @@ export class OpenAPIInputProcessor extends AbstractInputProcessor {
const api = (await SwaggerParser.dereference(input as any)) as unknown as
| OpenAPIV3.Document
| OpenAPIV3_1.Document;

if (api && api.paths) {
for (const [path, pathObject] of Object.entries(api.paths)) {
this.processPath(pathObject, path, inputModel, options);
}
}
if (options?.openapi?.includeComponentSchemas) {
for (const [schemaName, schemaObject] of Object.entries(
api.components?.schemas ?? {}
)) {
if ((schemaObject as any)['$ref'] === undefined) {
this.includeSchema(
schemaObject as OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject,
schemaName,
inputModel,
options
);
}
}
}
return inputModel;
}

Expand Down
16 changes: 15 additions & 1 deletion test/processors/OpenAPIInputProcessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ const basicDoc = JSON.parse(
'utf8'
)
);
const noPathsDoc = JSON.parse(
fs.readFileSync(
path.resolve(__dirname, './OpenAPIInputProcessor/no_paths.json'),
'utf8'
)
);
const circularDoc = JSON.parse(
fs.readFileSync(
path.resolve(__dirname, './OpenAPIInputProcessor/references_circular.json'),
Expand All @@ -20,7 +26,7 @@ const processorSpy = jest.spyOn(
'convertToInternalSchema'
);
const mockedReturnModels = [new CommonModel()];
const mockedMetaModel = new AnyModel('', undefined);
const mockedMetaModel = new AnyModel('', undefined, {});
jest.mock('../../src/helpers/CommonModelToMetaModel', () => {
return {
convertToMetaModel: jest.fn().mockImplementation(() => {
Expand Down Expand Up @@ -100,6 +106,14 @@ describe('OpenAPIInputProcessor', () => {
expect(commonInputModel).toMatchSnapshot();
expect(processorSpy.mock.calls).toMatchSnapshot();
});
test('should process the OpenAPI document with no paths', async () => {
const processor = new OpenAPIInputProcessor();
const commonInputModel = await processor.process(noPathsDoc, {
openapi: { includeComponentSchemas: true }
});
expect(commonInputModel).toMatchSnapshot();
expect(processorSpy.mock.calls).toMatchSnapshot();
});
test('should include schema for parameters', async () => {
const doc = {
openapi: '3.0.3',
Expand Down
Loading

0 comments on commit 43b46b7

Please sign in to comment.