openapi-jsonrpc-jsdoc 1.4.1 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +4 -6
  2. package/index.mjs +166 -67
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -8,7 +8,7 @@ npm i openapi-jsonrpc-jsdoc
8
8
 
9
9
  ## Examples
10
10
 
11
- ### Create JSON-RPC Method
11
+ ### Usage Example
12
12
 
13
13
  ```js
14
14
  // api/api-v1.js
@@ -28,11 +28,9 @@ module.exports = (parameters) => {
28
28
  }
29
29
  ```
30
30
 
31
- ### Run package
31
+ ### Generate OpenAPI JSON
32
32
 
33
33
  ```js
34
- // index.js
35
- const fs = require('fs');
36
34
  const openapiJSONRpcJSDoc = require('openapi-jsonrpc-jsdoc');
37
35
  openapiJSONRpcJSDoc({
38
36
  api: '/',
@@ -44,7 +42,7 @@ openapiJSONRpcJSDoc({
44
42
  packageUrl: './package.json',
45
43
  files: './api/*.js',
46
44
  }).then(data => {
47
- fs.writeFileSync('openapi.json', JSON.stringify(data, null, 2));
45
+ JSON.stringify(data, null, 2);// openapi.json
48
46
  });
49
47
  ```
50
48
 
@@ -54,7 +52,7 @@ openapiJSONRpcJSDoc({
54
52
  ```json
55
53
  {
56
54
  "x-send-defaults": true,
57
- "openapi": "3.0.0",
55
+ "openapi": "3.1.0",
58
56
  "x-api-id": "json-rpc-example",
59
57
  "x-headers": [],
60
58
  "x-explorer-enabled": true,
package/index.mjs CHANGED
@@ -1,6 +1,139 @@
1
1
  import jsdoc from 'jsdoc-api';
2
2
 
3
- export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {}, packageUrl, servers, api = '/' }) {
3
+ function extractName(name) {
4
+ try {
5
+ return name.match(/\.(.+)/i)[1]; //eslint-disable-line
6
+ } catch {
7
+ return name;
8
+ }
9
+ }
10
+
11
+ function extractTypeName(names = []) {
12
+ let typeName;
13
+ if (names.length === 0) {
14
+ typeName = 'null';
15
+ } else if (names.length === 1) {
16
+ [typeName] = names;
17
+ } else {
18
+ typeName = 'enum';
19
+ }
20
+ return typeName;
21
+ }
22
+
23
+ function resolveSchemaFromTypeNames(names) {
24
+ let type;
25
+ let format;
26
+ let items;
27
+ let nullable;
28
+ let constant;
29
+ let enumData;
30
+
31
+ switch (extractTypeName(names)) {
32
+ case 'Array.<string>': {
33
+ type = 'array';
34
+ items = {type: 'string'};
35
+ break;
36
+ }
37
+ case 'Array.<number>': {
38
+ type = 'array';
39
+ items = {type: 'number'};
40
+ break;
41
+ }
42
+ case 'URL': {
43
+ type = 'string';
44
+ format = 'url';
45
+ break;
46
+ }
47
+ case 'String':
48
+ case 'string': {
49
+ type = 'string';
50
+ break;
51
+ }
52
+ case 'Number':
53
+ case 'number': {
54
+ type = 'number';
55
+ break;
56
+ }
57
+ case 'Boolean':
58
+ case 'boolean': {
59
+ type = 'boolean';
60
+ break;
61
+ }
62
+ case 'Date': {
63
+ type = 'string';
64
+ format = 'date-time';
65
+ break;
66
+ }
67
+ case 'enum': {
68
+ enumData = names;
69
+ if (enumData.includes('null')) {
70
+ nullable = true;
71
+ enumData = enumData.filter(n => n !== 'null');
72
+ }
73
+ enumData = enumData.map((n) => {
74
+ if (!Number.isNaN(Number(n))) {
75
+ return Number(n);
76
+ }
77
+ return n;
78
+ });
79
+
80
+ if (enumData.every(n => Number.isInteger(Number(n)))) {
81
+ type = 'integer';
82
+ } else if (enumData.every(n => !Number.isNaN(Number(n)))) {
83
+ type = 'number';
84
+ } else if (enumData.every(n => n?.toLowerCase() === 'boolean')) {
85
+ type = 'boolean';
86
+ } else if (enumData.every(n => n?.toLowerCase() === 'number')) {
87
+ type = 'number';
88
+ } else if (enumData.every(n => n?.toLowerCase() === 'string')) {
89
+ type = 'string';
90
+ } else if (enumData.every(n => n?.toLowerCase() === 'date')) {
91
+ type = 'string';
92
+ format = 'date-time';
93
+ } else if (enumData.every(n => n === 'URL')) {
94
+ type = 'string';
95
+ format = 'url';
96
+ } else if (enumData.every(n => n?.toLowerCase() === 'true')) {
97
+ type = 'boolean';
98
+ constant = true;
99
+ } else if (enumData.every(n => n?.toLowerCase() === 'false')) {
100
+ type = 'boolean';
101
+ constant = false;
102
+ } else {
103
+ type = 'string';
104
+ }
105
+
106
+ if (enumData?.length === 1) {
107
+ if (!format) {
108
+ [format] = enumData;
109
+ }
110
+ enumData = undefined;
111
+ }
112
+ break;
113
+ }
114
+ default: {
115
+ break;
116
+ }
117
+ }
118
+
119
+ return {
120
+ type,
121
+ format,
122
+ nullable,
123
+ items,
124
+ constant,
125
+ enumData,
126
+ };
127
+ }
128
+
129
+ export default async function openapiJsonrpcJsdoc({
130
+ openapi = '3.1.0',
131
+ files,
132
+ securitySchemes = {},
133
+ packageUrl,
134
+ servers,
135
+ api = '/',
136
+ }) {
4
137
  const allData = await jsdoc.explain({
5
138
  files: Array.isArray(files) ? files : [files],
6
139
  package: packageUrl,
@@ -9,12 +142,13 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
9
142
  undocumented: false,
10
143
  allowUnknownTags: true,
11
144
  dictionaries: ['jsdoc'],
145
+ cache: true,
12
146
  });
13
147
  const package_ = allData.find(item => item.kind === 'package');
14
148
  const documents = allData.filter(item => item.kind !== 'package');
15
149
  const temporaryDocument = {
150
+ 'openapi': openapi,
16
151
  'x-send-defaults': true,
17
- 'openapi': '3.0.0',
18
152
  'x-api-id': 'json-rpc-example',
19
153
  'x-headers': [],
20
154
  'x-explorer-enabled': true,
@@ -52,11 +186,7 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
52
186
  },
53
187
  }
54
188
  },
55
- 'security': [
56
- {
57
- BasicAuth: [],
58
- },
59
- ],
189
+ 'security': Object.keys(securitySchemes).map(val => ({ [val]: [] })),
60
190
  'tags': [],
61
191
  };
62
192
  const requiredSchema = ['method', 'jsonrpc'];
@@ -110,6 +240,7 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
110
240
  }
111
241
  },
112
242
  requestBody: {
243
+ required: true,
113
244
  content: {
114
245
  'application/json': {
115
246
  schema: {
@@ -122,9 +253,7 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
122
253
  description: `API method ${apiName}`,
123
254
  },
124
255
  id: {
125
- type: 'integer',
126
- default: 1,
127
- format: 'int32',
256
+ type: ['string'],
128
257
  description: 'Request ID',
129
258
  },
130
259
  jsonrpc: {
@@ -157,61 +286,38 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
157
286
  if (parameter.type.names[0] === 'object') {
158
287
  return accumulator;
159
288
  }
160
- let type;
161
- if (parameter.type.names.length === 0) {
162
- type = 'null';
163
- } else if (parameter.type.names.length === 1) {
164
- type = parameter.type.names[0];
165
- } else {
166
- type = 'enum';
167
- }
168
- let items;
169
- let enumData;
170
- let oneOf;
171
289
 
172
- switch (type) {
173
- case 'Array.<string>': {
174
- type = 'array';
175
- items = { type: 'string' };
176
- break;
177
- }
178
- case 'Array.<number>': {
179
- type = 'array';
180
- items = { type: 'number' };
181
- break;
182
- }
183
- case 'enum': {
184
- enumData = parameter.type.names;
185
- oneOf = parameter.type.names.map((n) => {
186
- if (!Number.isNaN(Number(n))) {
187
- return Number(n);
188
- }
189
- return n;
190
- });
191
- if (parameter.type.names.every(n => Number.isInteger(Number(n)))) {
192
- type = 'integer';
193
- } else if (parameter.type.names.every(n => !Number.isNaN(Number(n)))) {
194
- type = 'number';
195
- } else {
196
- type = 'string';
197
- }
198
- break;
199
- }
200
- default: {
201
- break;
202
- }
203
- }
290
+ const name = extractName(parameter.name);
291
+ accumulator.properties[name] = accumulator.properties[name] ?? {};
204
292
  const description = parameter.description;
205
- let name;
206
- try {
207
- name = parameter.name.match(/\.(.+)/i)[1]; //eslint-disable-line
208
- } catch {
209
- name = parameter.name;
210
- }
293
+ const defaultValue = parameter.defaultvalue;
294
+
295
+ const {
296
+ items,
297
+ constant,
298
+ enumData,
299
+ type,
300
+ format,
301
+ nullable,
302
+ } = resolveSchemaFromTypeNames(parameter.type.names)
211
303
  if (!parameter.optional) {
212
304
  accumulator.required.push(name);
213
305
  }
214
- accumulator.properties[name] = accumulator.properties[name] ?? {};
306
+ if (nullable) {
307
+ accumulator.properties[name].nullable = nullable;
308
+ }
309
+ if (defaultValue) {
310
+ accumulator.properties[name].default = defaultValue;
311
+ }
312
+ if (constant) {
313
+ accumulator.properties[name].const = constant;
314
+ }
315
+ if (format) {
316
+ accumulator.properties[name].format = format;
317
+ }
318
+ if (enumData) {
319
+ accumulator.properties[name].enum = enumData;
320
+ }
215
321
  if (type) {
216
322
  accumulator.properties[name].type = type;
217
323
  }
@@ -221,13 +327,6 @@ export default async function openapiJsonrpcJsdoc({ files, securitySchemes = {},
221
327
  if (items) {
222
328
  accumulator.properties[name].items = items;
223
329
  }
224
- if (enumData) {
225
- accumulator.properties[name].enum = enumData;
226
- }
227
- if (oneOf) {
228
- accumulator.properties[name].oneOf = oneOf;
229
- }
230
-
231
330
  return accumulator;
232
331
  },
233
332
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openapi-jsonrpc-jsdoc",
3
- "version": "1.4.1",
3
+ "version": "1.4.3",
4
4
  "description": "Transform JSDoc-annotated JSON-RPC 2.0 methods into OpenAPI specifications.",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -33,8 +33,7 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "ava": "~6.4.1",
36
- "express": "~5.2.1",
37
- "express-openapi-validator": "~5.6.2"
36
+ "express": "~5.2.1"
38
37
  },
39
38
  "engines": {
40
39
  "node": ">= 22"