Skip to content

Commit 60d9c29

Browse files
author
Abilio Henrique
committed
Updated document generation stuff
1 parent dd9b28c commit 60d9c29

File tree

5 files changed

+253
-50
lines changed

5 files changed

+253
-50
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules
44

55
.directory
66
.DS_STORE*
7+
*.log

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
"typescript": "^2.2.2"
3131
},
3232
"dependencies": {
33+
"@jdw/jst": "^1.0.0-beta.10",
3334
"@types/bluebird": "^3.5.2",
3435
"@types/fs-promise": "^1.0.1",
3536
"@types/js-yaml": "^3.5.29",
3637
"bluebird": "^3.5.0",
3738
"fs-promise": "^2.0.2",
3839
"js-yaml": "^3.8.2",
39-
"lutils": "^1.2.5"
40+
"lutils": "^1.2.5",
41+
"uuid": "^3.0.1"
4042
}
4143
}

src/generate.ts

Lines changed: 152 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import { merge } from 'lutils';
1+
import { dereference } from '@jdw/jst';
2+
import { clone, merge } from 'lutils';
3+
import uuid from 'uuid';
24

35
type ServiceDescriptionType = {
6+
title: string,
47
description: string,
58
version: string,
6-
summary: string,
79
termsOfService?: string,
810
contact?: {
911
name?: string,
@@ -14,68 +16,178 @@ type ServiceDescriptionType = {
1416
name: string,
1517
url: string,
1618
},
19+
models?: any[],
1720
};
1821

1922
export default class DocumentGenerator {
20-
private config: ServiceDescriptionType = {
23+
private config = {
2124
description: '',
2225
version: '0.0.0',
23-
summary: '',
26+
title: '',
27+
paths: {},
28+
components: {
29+
schemas: {},
30+
},
2431
};
2532

2633
constructor(serviceDescriptor: ServiceDescriptionType) {
27-
merge([this.config, serviceDescriptor], { depth: 32 });
34+
this.config = this.process(clone(serviceDescriptor, { depth: 100 }));
35+
// merge([this.config, serviceDescriptor], { depth: 32 });
36+
// console.log(this.config);
2837
}
2938

3039
public generate() {
31-
return {
32-
openapi: '3.0.0-RC0',
33-
servers: this.getServers(),
34-
info: {
35-
description: this.getDescription(),
36-
version: this.getVersion(),
37-
title: this.getTitle(),
38-
termsOfService: this.getTermsOfService(),
39-
},
40-
tags: this.getTags(),
41-
paths: {},
42-
components: {
43-
schemas: this.getSchemas(),
44-
requestBodies: {},
45-
responses: {},
46-
parameters: {},
47-
examples: {},
48-
securitySchemes: {},
49-
},
50-
};
40+
return this.config;
5141
}
5242

53-
private getDescription() {
54-
return this.config.description;
43+
public addPathsFromFunctionConfig(config) {
44+
// loop through function configurations
45+
for (const funcConfig of config) {
46+
// loop through http events
47+
for (const httpEvent of this.getHttpEvents(funcConfig.events)) {
48+
const httpEventConfig = httpEvent.http;
49+
if (httpEventConfig.documentation) {
50+
const documentationConfig = httpEventConfig.documentation;
51+
// console.log(documentationConfig);
52+
const pathConfig = {
53+
[`/${httpEventConfig.path}`]: {
54+
[httpEventConfig.method]: {
55+
operationId: funcConfig._functionName,
56+
summary: documentationConfig.summary || '',
57+
description: documentationConfig.description || '',
58+
responses: this.getResponsesFromConfig(documentationConfig),
59+
parameters: this.getParametersFromConfig(documentationConfig),
60+
requestBody: this.getRequestBodiesFromConfig(documentationConfig),
61+
},
62+
},
63+
};
64+
merge([this.config.paths, pathConfig], { depth: 100 });
65+
}
66+
}
67+
}
68+
// console.log(JSON.stringify(this.config.paths, null, 2));
5569
}
5670

57-
private getSchemas() {
58-
return {};
59-
}
71+
private getParametersFromConfig(documentationConfig) {
72+
const parameters = [];
73+
// Path Parameters
74+
if (documentationConfig.pathParams) {
75+
for (const parameter of documentationConfig.pathParams) {
76+
parameters.push({
77+
name: parameter.name,
78+
in: 'path',
79+
description: parameter.description,
80+
required: true, // Note: all path parameters must be required
81+
schema: parameter.schema || {},
82+
example: parameter.example || null,
83+
});
84+
}
85+
}
86+
// Query Parameters
87+
if (documentationConfig.queryParams) {
88+
for (const parameter of documentationConfig.queryParams) {
89+
parameters.push({
90+
name: parameter.name,
91+
in: 'query',
92+
description: parameter.description,
93+
required: parameter.required || false, // Note: all path parameters must be required
94+
schema: parameter.schema || {},
95+
example: parameter.example || null,
96+
});
97+
}
98+
}
99+
100+
// Request Header Parameters
101+
if (documentationConfig.requestHeaders) {
102+
for (const parameter of documentationConfig.requestHeaders) {
103+
parameters.push({
104+
name: parameter.name,
105+
in: 'header',
106+
description: parameter.description,
107+
required: parameter.required || false, // Note: all path parameters must be required
108+
schema: parameter.schema || {},
109+
example: parameter.example || null,
110+
});
111+
}
112+
}
60113

61-
private getServers() {
62-
return [{ url: 'nothing' }];
114+
return parameters;
63115
}
64116

65-
private getTermsOfService() {
66-
return this.config.termsOfService;
117+
private getRequestBodiesFromConfig(documentationConfig) {
118+
const requestBodies = {};
119+
if (documentationConfig.requestModels) {
120+
for (const requestModelType of Object.keys(documentationConfig.requestModels)) {
121+
merge([requestBodies, {
122+
[requestModelType]: {
123+
schema: {
124+
$ref: `#/components/schemas/${documentationConfig.requestModels[requestModelType]}`,
125+
},
126+
},
127+
}], { depth: 100 });
128+
}
129+
}
130+
131+
return requestBodies;
67132
}
68133

69-
private getTags() {
70-
return [];
134+
private getResponsesFromConfig(documentationConfig) {
135+
const responses = {};
136+
if (documentationConfig.methodResponses) {
137+
for (const response of documentationConfig.methodResponses) {
138+
merge([responses, {
139+
[response.statusCode]: {
140+
description: response.description || `Status ${response.statusCode} Response`,
141+
content: this.getResponseContent(response.responseModels),
142+
},
143+
}], { depth: 100 });
144+
}
145+
}
146+
147+
return responses;
71148
}
72149

73-
private getTitle() {
74-
return this.config.summary;
150+
private getResponseContent(response) {
151+
const content = {};
152+
for (const responseKey of Object.keys(response)) {
153+
merge(content, { [responseKey] : {
154+
schema: {
155+
$ref: `#/components/schemas/${response[responseKey]}`,
156+
},
157+
} });
158+
}
159+
// console.log(content);
160+
return content;
75161
}
76162

77-
private getVersion() {
78-
return this.config.version;
163+
private getHttpEvents(funcConfig) {
164+
return funcConfig.filter((event) => event.http ? true : false);
79165
}
80166

167+
private process(serviceDescriptor): any {
168+
const configObject = {
169+
openapi: '3.0.0-RC0',
170+
servers: [],
171+
info: {
172+
title: serviceDescriptor.summary || '',
173+
description: serviceDescriptor.description || '',
174+
version: serviceDescriptor.version || uuid.v4(),
175+
},
176+
paths: {},
177+
components: {
178+
schemas: {},
179+
requestBodies: {},
180+
responses: {},
181+
parameters: {},
182+
examples: {},
183+
securitySchemes: {},
184+
},
185+
};
186+
187+
for (const model of serviceDescriptor.models) {
188+
configObject.components.schemas[model.name] = dereference(model.schema);
189+
}
190+
191+
return configObject;
192+
}
81193
}

src/index.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import * as DocumentGenerator from './generate';
1+
import * as fs from 'fs';
2+
import * as YAML from 'js-yaml';
3+
import { merge } from 'lutils';
4+
import DocumentGenerator from './generate';
25

36
class ServerlessOpenAPIDocumentation {
47
public hooks;
8+
public commands;
59
private serverless;
610
private options;
711
private provider;
@@ -19,22 +23,79 @@ class ServerlessOpenAPIDocumentation {
1923
this.getMethodLogicalId = naming.getMethodLogicalId.bind(naming);
2024
this.normalizePath = naming.normalizePath.bind(naming);
2125

26+
this.commands = {
27+
openapi: {
28+
commands: {
29+
generate: {
30+
lifecycleEvents: [
31+
'serverless',
32+
],
33+
usage: 'Generate OpenAPI v3 Documentation',
34+
options: {
35+
output: {
36+
usage: 'Output file location [default: openapi.yml|json]',
37+
shortcut: 'o',
38+
},
39+
format: {
40+
usage: 'OpenAPI file format (yml|json) [default: yml]',
41+
shortcut: 'f',
42+
},
43+
indent: {
44+
usage: 'File indentation in spaces[default: 2]',
45+
shortcut: 'i',
46+
},
47+
},
48+
},
49+
},
50+
},
51+
};
52+
2253
this.hooks = {
23-
'before:deploy:deploy': this.beforeDeploy.bind(this),
24-
'after:deploy:deploy': this.afterDeploy.bind(this),
54+
// 'before:deploy:deploy': this.beforeDeploy.bind(this),
55+
// 'after:deploy:deploy': this.afterDeploy.bind(this),
56+
'openapi:generate:serverless': this.beforeDeploy.bind(this),
2557
};
2658
}
2759

28-
private beforeDeploy() {
60+
private beforeDeploy(e) {
61+
const indent = this.serverless.processedInput.options.indent || 2;
62+
const outputFormat = this.serverless.processedInput.options.format || 'yaml';
63+
64+
if (['yaml', 'json'].indexOf(outputFormat.toLowerCase()) < 0) {
65+
throw new Error('Invalid Output Format Specified - must be one of "yaml" or "json"');
66+
}
67+
68+
let outputFile = this.serverless.processedInput.options.output;
69+
2970
const dg = new DocumentGenerator(this.customVars.documentation);
3071

3172
const funcConfigs = this.serverless.service.getAllFunctions().map((functionName) => {
3273
const func = this.serverless.service.getFunction(functionName);
33-
console.log(func.events);
34-
return func;
74+
return merge([{ _functionName: functionName }, func], { depth: 100 });
3575
});
3676

37-
return Promise.reject(new Error('fuck'));
77+
dg.addPathsFromFunctionConfig(funcConfigs);
78+
79+
const outputObject = dg.generate();
80+
81+
let outputContent = '';
82+
switch (outputFormat.toLowerCase()) {
83+
case 'json':
84+
if (!outputFile) {
85+
outputFile = 'openapi.json';
86+
}
87+
outputContent = JSON.stringify(outputObject, null, indent);
88+
break;
89+
case 'yaml':
90+
default:
91+
if (!outputFile) {
92+
outputFile = 'openapi.yml';
93+
}
94+
outputContent = YAML.safeDump(outputObject, { indent });
95+
break;
96+
}
97+
98+
fs.writeFileSync(outputFile, outputContent);
3899
}
39100

40101
private afterDeploy() {

0 commit comments

Comments
 (0)