Skip to content

Commit 39d9eb4

Browse files
authored
Merge pull request #222 from JaredCE/better-specification-extensions-support
Better specification extensions support
2 parents cdcbec0 + 68b2a20 commit 39d9eb4

File tree

4 files changed

+148
-27
lines changed

4 files changed

+148
-27
lines changed

README.md

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Originally based off of: https://github.com/temando/serverless-openapi-documenta
1818

1919
## Install
2020

21-
This plugin works for Serverless 2.x and up and only supports node.js 14 and up.
21+
This plugin works for Serverless (2.x, 3.x and 4.x) and only supports node.js 14 and up.
2222

2323
To add this plugin to your package.json:
2424

@@ -79,26 +79,30 @@ Options:
7979

8080
| OpenAPI field | Serverless field |
8181
| --------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
82-
| info.title | custom.documentation.title OR service |
83-
| info.description | custom.documentation.description OR blank string |
84-
| info.version | custom.documentation.version OR random v4 uuid if not provided |
85-
| info.termsOfService | custom.documentation.termsOfService |
82+
| info.title | `custom.documentation.title` OR service |
83+
| info.description | `custom.documentation.description` OR blank string |
84+
| info.version | `custom.documentation.version` OR random v4 uuid if not provided |
85+
| info.termsOfService | `custom.documentation.termsOfService` |
8686
| info.contact | custom.documentation.contact |
87-
| info.contact.name | custom.documentation.contact.name OR blank string |
88-
| info.contact.url | custom.documentation.contact.url if provided |
87+
| info.contact.name | `custom.documentation.contact.name` OR blank string |
88+
| info.contact.url | `custom.documentation.contact.url` if provided |
89+
| info.contact.x- | `custom.documentation.contact.x-` extended specifications provided |
8990
| info.license | custom.documentation.license |
90-
| info.license.name | custom.documentation.license.name OR blank string |
91-
| info.license.url | custom.documentation.license.url if provided |
92-
| externalDocs.description | custom.documentation.externalDocumentation.description |
93-
| externalDocs.url | custom.documentation.externalDocumentation.url |
94-
| security | custom.documentation.security |
91+
| info.license.name | `custom.documentation.license.name` OR blank string |
92+
| info.license.url | `custom.documentation.license.url` if provided |
93+
| info.license.x- | `custom.documentation.license.x-` if extended specifications provided provided |
94+
| externalDocs.description | `custom.documentation.externalDocumentation.description ` |
95+
| externalDocs.url | `custom.documentation.externalDocumentation.url` |
96+
| x-tagGroups | `custom.documentation.x-tagGroups` if provided |
97+
| security | `custom.documentation.security` |
9598
| servers[].description | custom.documentation.servers.description |
9699
| servers[].url | custom.documentation.servers.url |
97100
| servers[].variables | custom.documentation.servers.variables |
98-
| tags[].name | custom.documentation.tags.name |
99-
| tags[].description | custom.documentation.tags.description |
100-
| tags[].externalDocs.url | custom.documentation.tags.externalDocumentation.url |
101-
| tags[].externalDocs.description | custom.documentation.tags.externalDocumentation.description |
101+
| tags[].name | `custom.documentation.tags.name` |
102+
| tags[].description | `custom.documentation.tags.description` |
103+
| tags[].externalDocs.url | `custom.documentation.tags.externalDocumentation.url` |
104+
| tags[].externalDocs.description | `custom.documentation.tags.externalDocumentation.description` |
105+
| tags[].externalDocs.x- | `custom.documentation.tags.externalDocumentation.x-` if extended specifications provided |
102106
| path[path] | functions.functions.events.[http OR httpApi].path |
103107
| path[path].servers[].description | functions.functions.servers.description |
104108
| path[path].servers[].url | functions.functions.servers.url |
@@ -189,7 +193,9 @@ custom:
189193
190194
```
191195

192-
These fields are optional, though `url` and `email` need to be in the format of an email address (ed: what that might be, i'm not 100% sure... go read the email RFC(s)) and a url.
196+
These fields are optional, though `url` needs to in the form of a URL and `email` needs to be in the format of an email address (ed: what that might be, I'm not 100% sure... go read the email RFC(s)).
197+
198+
This can be extended using the `^x-` specification extension.
193199

194200
#### License
195201

@@ -205,6 +211,8 @@ custom:
205211

206212
Name is required but `url` is optional and must be in the format of a url.
207213

214+
This can be extended using the `^x-` specification extension.
215+
208216
#### Extended Fields
209217

210218
You can also add extended fields to the documentation object:
@@ -225,6 +233,58 @@ custom:
225233

226234
`other-field` here will not make it to the generated OpenAPI schema.
227235

236+
Currently extended specification fields defined under the `documentation` tag will sit under the OpenAPI `info` object e.g.
237+
238+
```yml
239+
custom:
240+
documentation:
241+
title: myService
242+
x-other-field: This is an extended field
243+
```
244+
245+
converts to:
246+
247+
```json
248+
{
249+
"info": {
250+
"title": "myService",
251+
"x-other-field": "This is an extended field"
252+
}
253+
}
254+
```
255+
256+
An exception to this is Redocly `x-tagGroups`. If defined, they will sit at the root level of the OpenAPI specification, e.g.
257+
258+
```yml
259+
custom:
260+
documentation:
261+
title: myService
262+
x-other-field: This is an extended field
263+
x-tagGroups:
264+
- name: Customers
265+
tags:
266+
- Customers
267+
```
268+
269+
converts to:
270+
271+
```json
272+
{
273+
"info": {
274+
"title": "myService",
275+
"x-other-field": "This is an extended field"
276+
},
277+
"x-tagGroups": [
278+
{
279+
"name": "Customers",
280+
"tags": ["Customers"]
281+
}
282+
]
283+
}
284+
```
285+
286+
#### Moving documentation to a separate file
287+
228288
These configurations can be quite verbose; you can separate it out into it's own file, such as `serverless.doc.yml` as below:
229289

230290
```yml

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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "serverless-openapi-documenter",
3-
"version": "0.0.103",
3+
"version": "0.0.104",
44
"description": "Generate OpenAPI v3 documentation and Postman Collections from your Serverless Config",
55
"main": "index.js",
66
"keywords": [
@@ -9,12 +9,16 @@
99
"Serverless 2",
1010
"serverless3",
1111
"Serverless 3",
12+
"Serverless4",
13+
"Serverless 4",
1214
"serverless framework",
1315
"serverless framework plugin",
1416
"serverless plugin",
17+
"swagger",
1518
"openAPI",
1619
"openAPIv3",
1720
"openAPI3",
21+
"Postman",
1822
"PostmanCollections",
1923
"Postman-Collections",
2024
"Postman Collections",

src/definitionGenerator.js

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ class DefinitionGenerator {
154154
if (documentation.contact.url) contactObj.url = documentation.contact.url;
155155

156156
contactObj.email = documentation.contact.email || "";
157+
158+
const extendedSpec = this.extendSpecification(documentation.contact);
159+
160+
if (Object.keys(extendedSpec).length) {
161+
Object.assign(contactObj, extendedSpec);
162+
}
163+
157164
Object.assign(info, { contact: contactObj });
158165
}
159166

@@ -164,14 +171,22 @@ class DefinitionGenerator {
164171
if (documentation.license.url)
165172
licenseObj.url = documentation.license.url || "";
166173

174+
const extendedSpec = this.extendSpecification(documentation.license);
175+
176+
if (Object.keys(extendedSpec).length) {
177+
Object.assign(licenseObj, extendedSpec);
178+
}
179+
167180
Object.assign(info, { license: licenseObj });
168181
}
169182

170-
// for (const key of Object.keys(documentation)) {
171-
// if (/^[x\-]/i.test(key)) {
172-
// Object.assign(info, { [key]: documentation[key] });
173-
// }
174-
// }
183+
if (documentation["x-tagGroups"]) {
184+
Object.assign(this.openAPI, {
185+
"x-tagGroups": documentation["x-tagGroups"],
186+
});
187+
188+
delete documentation["x-tagGroups"];
189+
}
175190

176191
const extendedSpec = this.extendSpecification(documentation);
177192

@@ -237,6 +252,29 @@ class DefinitionGenerator {
237252
const serverDoc = servers;
238253
const newServers = [];
239254

255+
const variableManipulation = (variables) => {
256+
for (const key of Object.keys(variables)) {
257+
if (variables[key].enum) {
258+
const strEnum = variables[key].enum.map((enumVar) =>
259+
enumVar.toString()
260+
);
261+
262+
variables[key].enum = strEnum;
263+
}
264+
265+
if (variables[key].default)
266+
variables[key].default = variables[key].default.toString();
267+
268+
const extendedSpec = this.extendSpecification(variables[key]);
269+
270+
if (Object.keys(extendedSpec).length) {
271+
Object.assign(variables[key], extendedSpec);
272+
}
273+
}
274+
275+
return variables;
276+
};
277+
240278
if (Array.isArray(serverDoc)) {
241279
for (const server of serverDoc) {
242280
const obj = {
@@ -248,7 +286,13 @@ class DefinitionGenerator {
248286
}
249287

250288
if (server.variables) {
251-
obj.variables = server.variables;
289+
obj.variables = variableManipulation(server.variables);
290+
}
291+
292+
const extendedSpec = this.extendSpecification(server);
293+
294+
if (Object.keys(extendedSpec).length) {
295+
Object.assign(obj, extendedSpec);
252296
}
253297

254298
newServers.push(obj);
@@ -263,7 +307,13 @@ class DefinitionGenerator {
263307
}
264308

265309
if (servers.variables) {
266-
obj.variables = servers.variables;
310+
obj.variables = variableManipulation(servers.variables);
311+
}
312+
313+
const extendedSpec = this.extendSpecification(servers);
314+
315+
if (Object.keys(extendedSpec).length) {
316+
Object.assign(obj, extendedSpec);
267317
}
268318

269319
newServers.push(obj);
@@ -297,6 +347,13 @@ class DefinitionGenerator {
297347
tag.externalDocumentation
298348
);
299349
}
350+
351+
const extendedSpec = this.extendSpecification(tag);
352+
353+
if (Object.keys(extendedSpec).length) {
354+
Object.assign(obj, extendedSpec);
355+
}
356+
300357
tags.push(obj);
301358
}
302359
Object.assign(this.openAPI, { tags: tags });

0 commit comments

Comments
 (0)