Skip to content

Commit 46210a7

Browse files
authored
Merge pull request #140 from JaredCE/best-practice-serverless
Best practice serverless
2 parents b86d113 + 4fb4382 commit 46210a7

File tree

7 files changed

+690
-218
lines changed

7 files changed

+690
-218
lines changed

README.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,8 @@ requestModels:
751751

752752
#### `methodResponses`
753753

754+
`methodResponses` is a mandatory property and should include the `responseBody` and `description` properties.
755+
754756
You can define the response schemas by defining properties for your function event.
755757

756758
For an example of a `methodResponses` configuration for an event see below:
@@ -763,6 +765,12 @@ methodResponse:
763765
responseModels:
764766
application/json: "CreateResponse"
765767
application/xml: "CreateResponseXML"
768+
links:
769+
getDataLink:
770+
operation: getData
771+
description: The id created here can be used to get Data
772+
parameters:
773+
contentId: $response.body#/id
766774
responseHeaders:
767775
X-Rate-Limit-Limit:
768776
description: The number of allowed requests in the current period
@@ -788,6 +796,53 @@ responseModels:
788796
application/xml: "CreateResponseXML"
789797
```
790798

799+
##### `links`
800+
801+
The `links` property allows you to define how operations are linked to each other:
802+
803+
```yml
804+
links:
805+
linkName:
806+
operation: getContent
807+
description: The contentId created here can be used to get content
808+
parameters:
809+
contentId: $response.body#/contentId
810+
```
811+
812+
Where we are specifying operation, this should map to the function name:
813+
814+
```yml
815+
functions:
816+
createContent:
817+
events:
818+
- httpApi:
819+
path: /
820+
method: POST
821+
documentation: ...
822+
getContent:
823+
events:
824+
- http:
825+
path: /{contentId}
826+
method: POST
827+
documentation: ...
828+
```
829+
830+
If our example link was attached to the **createContent** function, and we wanted the `contentId` that was created to be used on the **getContent** function in the `contentId` parameter, we'd specify the `operation` property as **getContent**. If however, you had specified an operationId in the documentation to override the automatically created one:
831+
832+
```yml
833+
getContent:
834+
events:
835+
- http:
836+
path: /{contentId}
837+
method: POST
838+
documentation:
839+
operationId: getMyContent
840+
```
841+
842+
You can refer to the `operationId` that you created.
843+
844+
You can read more about [links](https://swagger.io/docs/specification/links/) on the swagger.io site and in the [OpenAPI](https://spec.openapis.org/oas/v3.0.3#link-object) specification. They don't seem widely supported just yet, but perhaps they'll improve your documentation.
845+
791846
##### `responseHeaders`
792847

793848
The `responseHeaders` property allows you to define the headers expected in a HTTP Response of the function event. This should only contain a description and a schema, which must be a JSON schema (inline, file or externally hosted).
@@ -882,7 +937,7 @@ This will set the `Cache-Control` Response Header to have a value of "no-store"
882937

883938
## Example configuration
884939

885-
Please view the example [serverless.yml](test/serverless-tests/serverless%202/serverless.yml).
940+
Please view the example [serverless.yml](test/serverless-tests/best/serverless.yml).
886941

887942
## Notes on schemas
888943

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-openapi-documenter",
3-
"version": "0.0.72",
3+
"version": "0.0.80",
44
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
55
"main": "index.js",
66
"keywords": [

src/definitionGenerator.js

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ class DefinitionGenerator {
3434

3535
this.schemaHandler = new SchemaHandler(serverless, this.openAPI);
3636

37+
this.operationIdMap = {};
38+
this.functionMap = {};
39+
3740
this.operationIds = [];
3841
this.schemaIDs = [];
3942

@@ -92,6 +95,8 @@ class DefinitionGenerator {
9295
throw err;
9396
});
9497

98+
this.cleanupLinks();
99+
95100
if (this.serverless.service.custom.documentation.servers) {
96101
const servers = this.createServers(
97102
this.serverless.service.custom.documentation.servers
@@ -156,6 +161,7 @@ class DefinitionGenerator {
156161
async createPaths() {
157162
const paths = {};
158163
const httpFunctions = this.getHTTPFunctions();
164+
159165
for (const httpFunction of httpFunctions) {
160166
for (const event of httpFunction.event) {
161167
if (event?.http?.documentation || event?.httpApi?.documentation) {
@@ -164,21 +170,11 @@ class DefinitionGenerator {
164170
event?.http?.documentation || event?.httpApi?.documentation;
165171

166172
this.currentFunctionName = httpFunction.functionInfo.name;
167-
168-
let opId;
169-
if (
170-
this.operationIds.includes(httpFunction.functionInfo.name) === false
171-
) {
172-
opId = httpFunction.functionInfo.name;
173-
this.operationIds.push(opId);
174-
} else {
175-
opId = `${httpFunction.functionInfo.name}-${uuid()}`;
176-
}
173+
this.operationName = httpFunction.operationName;
177174

178175
const path = await this.createOperationObject(
179176
event?.http?.method || event?.httpApi?.method,
180-
documentation,
181-
opId
177+
documentation
182178
).catch((err) => {
183179
throw err;
184180
});
@@ -282,11 +278,25 @@ class DefinitionGenerator {
282278
Object.assign(this.openAPI, { tags: tags });
283279
}
284280

285-
async createOperationObject(method, documentation, name = uuid()) {
281+
async createOperationObject(method, documentation) {
282+
let operationId = documentation?.operationId || this.operationName;
283+
if (this.operationIds.includes(operationId)) {
284+
operationId += `-${uuid()}`;
285+
}
286+
287+
const arr = this.functionMap[this.operationName];
288+
arr.push(operationId);
289+
this.functionMap[this.operationName] = arr;
290+
291+
this.operationIds.push(operationId);
292+
Object.assign(this.operationIdMap, {
293+
[operationId]: this.operationName,
294+
});
295+
286296
const obj = {
287297
summary: documentation.summary || "",
288298
description: documentation.description || "",
289-
operationId: documentation.operationId || name,
299+
operationId: operationId,
290300
parameters: [],
291301
tags: documentation.tags || [],
292302
};
@@ -472,6 +482,10 @@ class DefinitionGenerator {
472482
}
473483
}
474484

485+
if (response.links) {
486+
obj.links = this.createLinks(response.links);
487+
}
488+
475489
Object.assign(responses, { [response.statusCode]: obj });
476490
}
477491

@@ -691,6 +705,36 @@ class DefinitionGenerator {
691705
return params;
692706
}
693707

708+
createLinks(links) {
709+
const linksObj = {};
710+
for (const link in links) {
711+
const linkObj = links[link];
712+
const obj = {};
713+
714+
obj.operationId = linkObj.operation;
715+
716+
if (linkObj.description) {
717+
obj.description = linkObj.description;
718+
}
719+
720+
if (linkObj.server) {
721+
obj.server = this.createServers(linkObj.server);
722+
}
723+
724+
if (linkObj.parameters) {
725+
obj.parameters = linkObj.parameters;
726+
}
727+
728+
if (linkObj.requestBody) {
729+
obj.requestBody = linkObj.requestBody;
730+
}
731+
732+
Object.assign(linksObj, { [link]: obj });
733+
}
734+
735+
return linksObj;
736+
}
737+
694738
addToComponents(type, schema, name) {
695739
const schemaObj = {
696740
[name]: schema,
@@ -863,6 +907,27 @@ class DefinitionGenerator {
863907
return examplesObj;
864908
}
865909

910+
cleanupLinks() {
911+
for (const path of Object.keys(this.openAPI.paths)) {
912+
for (const [name, value] of Object.entries(this.openAPI.paths[path])) {
913+
for (const [statusCode, responseObj] of Object.entries(
914+
value.responses
915+
)) {
916+
if (responseObj.links) {
917+
for (const [linkName, linkObj] of Object.entries(
918+
responseObj.links
919+
)) {
920+
const opId = linkObj.operationId;
921+
if (this.functionMap[opId]) {
922+
linkObj.operationId = this.functionMap[opId][0];
923+
}
924+
}
925+
}
926+
}
927+
}
928+
}
929+
}
930+
866931
getHTTPFunctions() {
867932
const isHttpFunction = (funcType) => {
868933
const keys = Object.keys(funcType);
@@ -883,7 +948,16 @@ class DefinitionGenerator {
883948
})
884949
.map((functionType) => {
885950
const event = functionType.events.filter(isHttpFunction);
951+
const name = functionType.name.split(
952+
`${this.serverless.service.service}-${this.serverless.service.provider.stage}-`
953+
)[1];
954+
955+
Object.assign(this.functionMap, {
956+
[name]: [],
957+
});
958+
886959
return {
960+
operationName: name,
887961
functionInfo: functionType,
888962
handler: functionType.handler,
889963
name: functionType.name,
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"name": "serverless-3",
3+
"version": "0.0.14",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"generate": "npx serverless openapi generate -o openapi.json -f json -a 3.0.3"
9+
},
10+
"keywords": [],
11+
"author": "",
12+
"license": "ISC",
13+
"devDependencies": {
14+
"serverless": "^3.21.0"
15+
}
16+
}

0 commit comments

Comments
 (0)