Skip to content
This repository was archived by the owner on Dec 10, 2021. It is now read-only.

Commit 2f671fb

Browse files
authored
Merge pull request #7 from conqa/add-file-ref-support
Add file ref support
2 parents 4b1bf36 + 8f11f74 commit 2f671fb

19 files changed

+5817
-3684
lines changed

Diff for: package-lock.json

+5,432-2,829
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@conqa/serverless-openapi-documentation",
3-
"version": "1.0.4",
3+
"version": "1.1.0-alpha",
44
"description": "Serverless 1.0 plugin to generate OpenAPI V3 documentation from serverless configuration",
55
"main": "index.js",
66
"engines": {
@@ -42,14 +42,17 @@
4242
"@types/chalk": "^0.4.31",
4343
"@types/fs-extra": "^4.0.0",
4444
"@types/jest": "^20.0.2",
45-
"@types/js-yaml": "^3.5.31",
45+
"@types/js-yaml": "^3.12.1",
4646
"@types/json-schema": "^7.0.3",
47-
"@types/node": "^8.0.7",
48-
"@types/uuid": "^3.0.0",
47+
"@types/lodash": "^4.14.123",
48+
"@types/node": "^8.10.48",
49+
"@types/serverless": "^1.18.2",
50+
"@types/uuid": "^3.4.4",
4951
"changelog-verify": "^1.0.4",
5052
"jest": "^24.8.0",
51-
"serverless": "^1.16.1",
53+
"serverless": "^1.41.1",
5254
"ts-jest": "^24.0.2",
55+
"openapi-types": "^1.3.4",
5356
"ts-node": "^3.1.0",
5457
"tslint": "^5.4.3",
5558
"tslint-config-temando": "^1.1.4",
@@ -61,7 +64,8 @@
6164
"chalk": "^2.0.1",
6265
"fs-extra": "^4.0.1",
6366
"js-yaml": "^3.8.4",
64-
"lutils": "^2.4.0",
67+
"json-schema-ref-parser": "^6.1.0",
68+
"lodash": "^4.17.11",
6569
"swagger2openapi": "^2.5.0",
6670
"uuid": "^3.1.0"
6771
}

Diff for: src/DefinitionGenerator.ts

+19-72
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { JSONSchema7 } from 'json-schema';
1+
import _ = require('lodash');
22
// tslint:disable-next-line no-submodule-imports
33
import { validateSync as openApiValidatorSync } from 'swagger2openapi/validate';
44
import * as uuid from 'uuid';
55

6+
import { parseModels } from './parse';
67
import { IDefinition, IDefinitionConfig, IOperation, IParameterConfig, IServerlessFunctionConfig } from './types';
7-
import { clone, isIterable, merge, omit } from './utils';
8+
import { cleanSchema } from './utils';
89

910
export class DefinitionGenerator {
1011
// The OpenAPI version we currently validate against
@@ -18,23 +19,25 @@ export class DefinitionGenerator {
1819

1920
public config: IDefinitionConfig;
2021

22+
private root: string;
23+
2124
/**
2225
* Constructor
23-
* @param serviceDescriptor IServiceDescription
2426
*/
25-
constructor (config: IDefinitionConfig) {
26-
this.config = clone(config);
27+
constructor (config: IDefinitionConfig, root: string) {
28+
this.config = _.cloneDeep(config);
29+
this.root = root;
2730
}
2831

29-
public parse () {
32+
public async parse () {
3033
const {
3134
title = '',
3235
description = '',
3336
version = uuid.v4(),
3437
models,
3538
} = this.config;
3639

37-
merge(this.definition, {
40+
_.merge(this.definition, {
3841
openapi: this.version,
3942
info: { title, description, version },
4043
paths: {},
@@ -44,24 +47,7 @@ export class DefinitionGenerator {
4447
},
4548
});
4649

47-
if (isIterable(models)) {
48-
for (const model of models) {
49-
if (!model.schema) {
50-
continue;
51-
}
52-
53-
for (const definitionName of Object.keys(model.schema.definitions || {})) {
54-
const definition = model.schema.definitions[definitionName];
55-
if (typeof definition !== 'boolean') {
56-
this.definition.components.schemas[definitionName] = this.cleanSchema(this.updateReferences(definition));
57-
}
58-
}
59-
60-
const schemaWithoutDefinitions = omit(model.schema, ['definitions']);
61-
62-
this.definition.components.schemas[model.name] = this.cleanSchema(this.updateReferences(schemaWithoutDefinitions));
63-
}
64-
}
50+
this.definition.components.schemas = await parseModels(models, this.root);
6551

6652
return this;
6753
}
@@ -101,49 +87,10 @@ export class DefinitionGenerator {
10187
};
10288

10389
// merge path configuration into main configuration
104-
merge(this.definition.paths, pathConfig);
105-
}
106-
}
107-
}
108-
}
109-
110-
/**
111-
* Cleans schema objects to make them OpenAPI compatible
112-
* @param schema JSON Schema Object
113-
*/
114-
private cleanSchema (schema) {
115-
// Clone the schema for manipulation
116-
const cleanedSchema = clone(schema);
117-
118-
// Strip $schema from schemas
119-
if (cleanedSchema.$schema) {
120-
delete cleanedSchema.$schema;
121-
}
122-
123-
// Return the cleaned schema
124-
return cleanedSchema;
125-
}
126-
127-
/**
128-
* Walks through the schema object recursively and updates references to point to openapi's components
129-
* @param schema JSON Schema Object
130-
*/
131-
private updateReferences (schema: JSONSchema7): JSONSchema7 {
132-
const cloned = clone(schema);
133-
134-
if (cloned.$ref) {
135-
cloned.$ref = cloned.$ref.replace('#/definitions', '#/components/schemas');
136-
} else {
137-
for (const key of Object.getOwnPropertyNames(cloned)) {
138-
const value = cloned[key];
139-
140-
if (typeof value === 'object') {
141-
cloned[key] = this.updateReferences(value);
90+
_.merge(this.definition.paths, pathConfig);
14291
}
14392
}
14493
}
145-
146-
return cloned;
14794
}
14895

14996
/**
@@ -240,7 +187,7 @@ export class DefinitionGenerator {
240187
}
241188

242189
if (parameter.schema) {
243-
parameterConfig.schema = this.cleanSchema(parameter.schema);
190+
parameterConfig.schema = cleanSchema(parameter.schema);
244191
}
245192

246193
if (parameter.example) {
@@ -299,7 +246,7 @@ export class DefinitionGenerator {
299246
reqBodyConfig.description = documentationConfig.requestBody.description;
300247
}
301248

302-
merge(requestBodies, reqBodyConfig);
249+
_.merge(requestBodies, reqBodyConfig);
303250
}
304251
}
305252
}
@@ -309,9 +256,9 @@ export class DefinitionGenerator {
309256

310257
private attachExamples (target, config) {
311258
if (target.examples && Array.isArray(target.examples)) {
312-
merge(config, { examples: clone(target.examples) });
259+
_.merge(config, { examples: _.cloneDeep(target.examples) });
313260
} else if (target.example) {
314-
merge(config, { example: clone(target.example) });
261+
_.merge(config, { example: _.cloneDeep(target.example) });
315262
}
316263
}
317264

@@ -339,12 +286,12 @@ export class DefinitionGenerator {
339286
description: header.description || `${header.name} header`,
340287
};
341288
if (header.schema) {
342-
methodResponseConfig.headers[header.name].schema = this.cleanSchema(header.schema);
289+
methodResponseConfig.headers[header.name].schema = cleanSchema(header.schema);
343290
}
344291
}
345292
}
346293

347-
merge(responses, {
294+
_.merge(responses, {
348295
[response.statusCode]: methodResponseConfig,
349296
});
350297
}
@@ -370,7 +317,7 @@ export class DefinitionGenerator {
370317

371318
this.attachExamples(responseModel, resModelConfig);
372319

373-
merge(content, { [responseKey] : resModelConfig });
320+
_.merge(content, { [responseKey] : resModelConfig });
374321
}
375322
}
376323

Diff for: src/ServerlessOpenApiDocumentation.ts

+71-44
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,58 @@
11
import chalk from 'chalk';
22
import * as fs from 'fs';
33
import * as YAML from 'js-yaml';
4+
import _ = require('lodash');
5+
import * as Serverless from 'serverless';
46
import { inspect } from 'util';
7+
58
import { DefinitionGenerator } from './DefinitionGenerator';
6-
import { IDefinitionType, ILog } from './types';
7-
import { merge } from './utils';
9+
import { Format, IDefinitionConfig, IDefinitionType, ILog } from './types';
10+
11+
interface IOptions {
12+
indent: number;
13+
format: Format;
14+
output: string;
15+
}
16+
17+
interface IProcessedInput {
18+
options: IOptions;
19+
}
20+
21+
interface ICustomVars {
22+
documentation: IDefinitionConfig;
23+
}
24+
25+
interface IService {
26+
custom: ICustomVars;
27+
}
28+
29+
interface IVariables {
30+
service: IService;
31+
}
32+
33+
interface IFullServerless extends Serverless {
34+
variables: IVariables;
35+
processedInput: IProcessedInput;
36+
}
837

938
export class ServerlessOpenApiDocumentation {
1039
public hooks;
1140
public commands;
1241
/** Serverless Instance */
13-
private serverless;
14-
/** CLI options */
15-
// private options;
42+
private serverless: IFullServerless;
43+
1644
/** Serverless Service Custom vars */
17-
private customVars;
45+
private customVars: ICustomVars;
1846

1947
/**
2048
* Constructor
2149
* @param serverless
2250
* @param options
2351
*/
24-
constructor (serverless, options) {
52+
constructor (serverless: IFullServerless, options) {
53+
2554
// pull the serverless instance into our class vars
2655
this.serverless = serverless;
27-
// pull the CLI options into our class vars
28-
// this.options = options;
2956
// Serverless service custom variables
3057
this.customVars = this.serverless.variables.service.custom;
3158

@@ -67,51 +94,20 @@ export class ServerlessOpenApiDocumentation {
6794
process.stdout.write(str.join(' '));
6895
}
6996

70-
/**
71-
* Processes CLI input by reading the input from serverless
72-
* @returns config IConfigType
73-
*/
74-
private processCliInput (): IDefinitionType {
75-
const config: IDefinitionType = {
76-
format: 'yaml',
77-
file: 'openapi.yml',
78-
indent: 2,
79-
};
80-
81-
config.indent = this.serverless.processedInput.options.indent || 2;
82-
config.format = this.serverless.processedInput.options.format || 'yaml';
83-
84-
if (['yaml', 'json'].indexOf(config.format.toLowerCase()) < 0) {
85-
throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"');
86-
}
87-
88-
config.file = this.serverless.processedInput.options.output ||
89-
((config.format === 'yaml') ? 'openapi.yml' : 'openapi.json');
90-
91-
this.log(
92-
`${chalk.bold.green('[OPTIONS]')}`,
93-
`format: "${chalk.bold.red(config.format)}",`,
94-
`output file: "${chalk.bold.red(config.file)}",`,
95-
`indentation: "${chalk.bold.red(String(config.indent))}"\n\n`,
96-
);
97-
98-
return config;
99-
}
100-
10197
/**
10298
* Generates OpenAPI Documentation based on serverless configuration and functions
10399
*/
104-
private generate () {
100+
public async generate () {
105101
this.log(chalk.bold.underline('OpenAPI v3 Documentation Generator\n\n'));
106102
// Instantiate DocumentGenerator
107-
const generator = new DefinitionGenerator(this.customVars.documentation);
103+
const generator = new DefinitionGenerator(this.customVars.documentation, this.serverless.config.servicePath);
108104

109-
generator.parse();
105+
await generator.parse();
110106

111107
// Map function configurations
112108
const funcConfigs = this.serverless.service.getAllFunctions().map((functionName) => {
113109
const func = this.serverless.service.getFunction(functionName);
114-
return merge({ _functionName: functionName }, func);
110+
return _.merge({ _functionName: functionName }, func);
115111
});
116112

117113
// Add Paths to OpenAPI Output from Function Configuration
@@ -162,4 +158,35 @@ export class ServerlessOpenApiDocumentation {
162158

163159
this.log(`${chalk.bold.green('[OUTPUT]')} To "${chalk.bold.red(config.file)}"\n`);
164160
}
161+
162+
/**
163+
* Processes CLI input by reading the input from serverless
164+
* @returns config IConfigType
165+
*/
166+
private processCliInput (): IDefinitionType {
167+
const config: IDefinitionType = {
168+
format: Format.yaml,
169+
file: 'openapi.yml',
170+
indent: 2,
171+
};
172+
173+
config.indent = this.serverless.processedInput.options.indent || 2;
174+
config.format = this.serverless.processedInput.options.format || Format.yaml;
175+
176+
if ([Format.yaml, Format.json].indexOf(config.format) < 0) {
177+
throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"');
178+
}
179+
180+
config.file = this.serverless.processedInput.options.output ||
181+
((config.format === 'yaml') ? 'openapi.yml' : 'openapi.json');
182+
183+
this.log(
184+
`${chalk.bold.green('[OPTIONS]')}`,
185+
`format: "${chalk.bold.red(config.format)}",`,
186+
`output file: "${chalk.bold.red(config.file)}",`,
187+
`indentation: "${chalk.bold.red(String(config.indent))}"\n\n`,
188+
);
189+
190+
return config;
191+
}
165192
}

0 commit comments

Comments
 (0)