@sleeksky/alt-swagger 2.5.0 → 3.0.0
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.
- package/README.md +505 -14
- package/package.json +15 -6
- package/src/index.ts +274 -0
- package/src/utils.ts +125 -0
- package/src/index.js +0 -171
- package/src/utils.js +0 -101
package/README.md
CHANGED
|
@@ -1,24 +1,515 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Alt Swagger
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A fluent, programmatic API for generating OpenAPI 3.0 specifications. Quickly define your REST API endpoints with request/response schemas, parameters, security, and more using a simple, chainable syntax.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
```JavaScript
|
|
7
|
-
let refRequest = docs.ref.schema('example','{id:i:1,name:s:foo,label:{id:i:2,name:?s:bar},arr:[{a:b:false}]}');
|
|
5
|
+
Built on top of [@sleeksky/alt-schema](https://github.com/sleeksky-dev/alt-schema) for intuitive JSON schema definitions.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Fluent API**: Chain methods to define endpoints declaratively
|
|
10
|
+
- 📝 **Schema-first**: Define request/response schemas using simple string syntax
|
|
11
|
+
- 🔒 **Security**: Built-in support for authentication schemes
|
|
12
|
+
- 🏷️ **Rich Metadata**: Tags, summaries, descriptions, deprecation
|
|
13
|
+
- 🔄 **Reusable Schemas**: Define and reference schemas across endpoints
|
|
14
|
+
- 🎯 **TypeScript Support**: Full TypeScript definitions included
|
|
15
|
+
- 📊 **OpenAPI 3.0**: Generates valid OpenAPI 3.0 specifications
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @sleeksky/alt-swagger
|
|
21
|
+
# or
|
|
22
|
+
yarn add @sleeksky/alt-swagger
|
|
23
|
+
# or
|
|
24
|
+
pnpm add @sleeksky/alt-swagger
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
31
|
+
|
|
32
|
+
// Define your API
|
|
33
|
+
swagger.server('https://api.example.com', 'Production API');
|
|
34
|
+
|
|
35
|
+
// Define endpoints
|
|
36
|
+
swagger.get('/users')
|
|
37
|
+
.summary('Get users')
|
|
38
|
+
.query('limit:i:10,offset:i:0')
|
|
39
|
+
.res(200, '[{id:i,name:s,email:s}]');
|
|
40
|
+
|
|
41
|
+
swagger.post('/users')
|
|
42
|
+
.summary('Create user')
|
|
43
|
+
.req('{name:s,email:s}')
|
|
44
|
+
.res(201, '{id:i,name:s,email:s}')
|
|
45
|
+
.res(400, '{error:s}');
|
|
46
|
+
|
|
47
|
+
// Generate OpenAPI spec
|
|
48
|
+
const spec = swagger.swaggerDoc('User API', '1.0.0');
|
|
49
|
+
console.log(JSON.stringify(spec, null, 2));
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Schema Syntax
|
|
53
|
+
|
|
54
|
+
Alt Swagger uses a compact string syntax for defining JSON schemas, powered by alt-schema:
|
|
55
|
+
|
|
56
|
+
### Basic Types
|
|
57
|
+
- `s` - string
|
|
58
|
+
- `i` - integer
|
|
59
|
+
- `n` - number
|
|
60
|
+
- `b` - boolean
|
|
61
|
+
- `o` - object
|
|
62
|
+
- `a` - array
|
|
63
|
+
|
|
64
|
+
### Examples
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
// Simple object
|
|
68
|
+
'{name:s, age:i}'
|
|
69
|
+
|
|
70
|
+
// With default values
|
|
71
|
+
'{name:s:John, age:i:25}'
|
|
72
|
+
|
|
73
|
+
// Optional fields (prefixed with ?)
|
|
74
|
+
'{name:s, age:?i, email:?s}'
|
|
75
|
+
|
|
76
|
+
// Nested objects
|
|
77
|
+
'{user:{name:s, age:i}, active:b}'
|
|
78
|
+
|
|
79
|
+
// Arrays
|
|
80
|
+
'[{id:i, name:s}]'
|
|
81
|
+
|
|
82
|
+
// Complex nested structure
|
|
83
|
+
'{id:i, profile:{name:s, settings:{theme:s:dark, notifications:b:true}}}'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## API Reference
|
|
87
|
+
|
|
88
|
+
### Core Methods
|
|
89
|
+
|
|
90
|
+
#### `swagger.server(url, description?)`
|
|
91
|
+
Define API servers.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
swagger.server('https://api.example.com', 'Production');
|
|
95
|
+
swagger.server('https://staging.api.example.com', 'Staging');
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### `swagger.tag(name, description?)`
|
|
99
|
+
Define tags for grouping endpoints.
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
swagger.tag('Users', 'User management endpoints');
|
|
103
|
+
swagger.tag('Posts', 'Blog post endpoints');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### HTTP Methods
|
|
107
|
+
|
|
108
|
+
All HTTP methods return a fluent API for chaining:
|
|
109
|
+
|
|
110
|
+
#### `swagger.get(path)`
|
|
111
|
+
#### `swagger.post(path)`
|
|
112
|
+
#### `swagger.put(path)`
|
|
113
|
+
#### `swagger.patch(path)`
|
|
114
|
+
#### `swagger.del(path)` (or `swagger.delete(path)`)
|
|
115
|
+
|
|
116
|
+
```javascript
|
|
117
|
+
swagger.get('/users/{id}')
|
|
118
|
+
.summary('Get user by ID')
|
|
119
|
+
.res(200, '{id:i, name:s, email:s}')
|
|
120
|
+
.res(404, '{error:s}');
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Path Parameters
|
|
124
|
+
|
|
125
|
+
Parameters in curly braces are automatically detected:
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
// /users/{id} automatically creates path parameter 'id'
|
|
129
|
+
swagger.get('/users/{id}')
|
|
130
|
+
.res(200, '{id:i, name:s}');
|
|
131
|
+
|
|
132
|
+
// With default values
|
|
133
|
+
swagger.get('/users/{id:123}')
|
|
134
|
+
.res(200, '{id:i, name:s}');
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Fluent API Methods
|
|
138
|
+
|
|
139
|
+
#### `.req(schema)`
|
|
140
|
+
Define request body schema.
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
swagger.post('/users')
|
|
144
|
+
.req('{name:s, email:s, age:?i}')
|
|
145
|
+
.res(201, '{id:i, name:s, email:s}');
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `.res(statusCode, schema)`
|
|
149
|
+
Define response schema for a status code.
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
swagger.get('/users')
|
|
153
|
+
.res(200, '[{id:i, name:s}]')
|
|
154
|
+
.res(404, '{error:s}')
|
|
155
|
+
.res(500, '{error:s, code:i}');
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### `.query(params)`
|
|
159
|
+
Add query parameters. Accepts string or array.
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
// Single parameter
|
|
163
|
+
swagger.get('/users').query('limit:i:10');
|
|
164
|
+
|
|
165
|
+
// Multiple parameters (comma-separated)
|
|
166
|
+
swagger.get('/users').query('limit:i:10,offset:i:0');
|
|
167
|
+
|
|
168
|
+
// Array format
|
|
169
|
+
swagger.get('/users').query(['limit:i:10', 'offset:i:0']);
|
|
170
|
+
|
|
171
|
+
// Optional parameters
|
|
172
|
+
swagger.get('/users').query('search:?s');
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### `.header(params)`
|
|
176
|
+
Add header parameters.
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
swagger.get('/users')
|
|
180
|
+
.header('authorization')
|
|
181
|
+
.header('x-api-key:?');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `.tag(name)`
|
|
185
|
+
Add tags to the endpoint.
|
|
186
|
+
|
|
187
|
+
```javascript
|
|
188
|
+
swagger.get('/users')
|
|
189
|
+
.tag('Users')
|
|
190
|
+
.tag('Public');
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
#### `.summary(text)`
|
|
194
|
+
Add summary to the endpoint.
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
swagger.get('/users')
|
|
198
|
+
.summary('Retrieve a list of users');
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `.desc(text)` or `.description(text)`
|
|
202
|
+
Add description to the endpoint.
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
swagger.get('/users')
|
|
206
|
+
.desc('Returns a paginated list of users with optional filtering');
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
#### `.security(name)`
|
|
210
|
+
Apply security scheme to the endpoint.
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
swagger.get('/users')
|
|
214
|
+
.security('bearerAuth');
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### `.deprecate()`
|
|
218
|
+
Mark endpoint as deprecated.
|
|
219
|
+
|
|
220
|
+
```javascript
|
|
221
|
+
swagger.get('/old-endpoint')
|
|
222
|
+
.deprecate()
|
|
223
|
+
.res(200, '{message:s}');
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Schema Management
|
|
227
|
+
|
|
228
|
+
#### `swagger.ref.schema(name, schema)`
|
|
229
|
+
Define reusable schemas.
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
const userSchema = swagger.ref.schema('User', '{id:i, name:s, email:s}');
|
|
233
|
+
const errorSchema = swagger.ref.schema('Error', '{error:s, code:i}');
|
|
234
|
+
|
|
235
|
+
swagger.get('/users')
|
|
236
|
+
.res(200, userSchema)
|
|
237
|
+
.res(500, errorSchema);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Security
|
|
241
|
+
|
|
242
|
+
#### `swagger.security(name, options?)`
|
|
243
|
+
Define security schemes.
|
|
244
|
+
|
|
245
|
+
```javascript
|
|
246
|
+
// Bearer token
|
|
247
|
+
swagger.security('bearerAuth');
|
|
248
|
+
|
|
249
|
+
// API Key
|
|
250
|
+
swagger.security('apiKey', {
|
|
251
|
+
type: 'apiKey',
|
|
252
|
+
in: 'header',
|
|
253
|
+
name: 'X-API-Key'
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Basic auth
|
|
257
|
+
swagger.security('basicAuth', {
|
|
258
|
+
type: 'http',
|
|
259
|
+
scheme: 'basic'
|
|
260
|
+
});
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### Document Generation
|
|
264
|
+
|
|
265
|
+
#### `swagger.swaggerDoc(title?, version?)`
|
|
266
|
+
Generate the complete OpenAPI specification.
|
|
267
|
+
|
|
268
|
+
```javascript
|
|
269
|
+
const spec = swagger.swaggerDoc('My API', '1.0.0');
|
|
270
|
+
// Returns OpenAPI 3.0 compliant object
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Utility Methods
|
|
274
|
+
|
|
275
|
+
#### `swagger.reset()`
|
|
276
|
+
Clear all defined specifications.
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
swagger.reset(); // Start fresh
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
#### `swagger.remove(options)`
|
|
283
|
+
Remove endpoints.
|
|
284
|
+
|
|
285
|
+
```javascript
|
|
286
|
+
// Remove specific path
|
|
287
|
+
swagger.remove({ path: '/users' });
|
|
288
|
+
|
|
289
|
+
// Remove by tag
|
|
290
|
+
swagger.remove({ tag: 'Deprecated' });
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Complete Examples
|
|
294
|
+
|
|
295
|
+
### Basic CRUD API
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
299
|
+
|
|
300
|
+
swagger.server('https://api.example.com', 'User Management API');
|
|
301
|
+
swagger.tag('Users', 'User operations');
|
|
302
|
+
|
|
303
|
+
// Define schemas
|
|
304
|
+
const userSchema = swagger.ref.schema('User', '{id:i, name:s, email:s, created_at:s}');
|
|
305
|
+
const createUserSchema = swagger.ref.schema('CreateUser', '{name:s, email:s}');
|
|
306
|
+
const errorSchema = swagger.ref.schema('Error', '{error:s, code:i}');
|
|
307
|
+
|
|
308
|
+
// Security
|
|
309
|
+
swagger.security('bearerAuth');
|
|
310
|
+
|
|
311
|
+
// Endpoints
|
|
312
|
+
swagger.get('/users')
|
|
313
|
+
.tag('Users')
|
|
314
|
+
.summary('List users')
|
|
315
|
+
.security('bearerAuth')
|
|
316
|
+
.query('limit:?i:10,offset:?i:0')
|
|
317
|
+
.res(200, `[${userSchema}]`)
|
|
318
|
+
.res(401, errorSchema);
|
|
319
|
+
|
|
320
|
+
swagger.post('/users')
|
|
321
|
+
.tag('Users')
|
|
322
|
+
.summary('Create user')
|
|
323
|
+
.req(createUserSchema)
|
|
324
|
+
.res(201, userSchema)
|
|
325
|
+
.res(400, errorSchema);
|
|
326
|
+
|
|
327
|
+
swagger.get('/users/{id}')
|
|
328
|
+
.tag('Users')
|
|
329
|
+
.summary('Get user by ID')
|
|
330
|
+
.security('bearerAuth')
|
|
331
|
+
.res(200, userSchema)
|
|
332
|
+
.res(404, errorSchema);
|
|
333
|
+
|
|
334
|
+
swagger.put('/users/{id}')
|
|
335
|
+
.tag('Users')
|
|
336
|
+
.summary('Update user')
|
|
337
|
+
.security('bearerAuth')
|
|
338
|
+
.req(createUserSchema)
|
|
339
|
+
.res(200, userSchema)
|
|
340
|
+
.res(400, errorSchema)
|
|
341
|
+
.res(404, errorSchema);
|
|
342
|
+
|
|
343
|
+
swagger.del('/users/{id}')
|
|
344
|
+
.tag('Users')
|
|
345
|
+
.summary('Delete user')
|
|
346
|
+
.security('bearerAuth')
|
|
347
|
+
.res(204)
|
|
348
|
+
.res(404, errorSchema);
|
|
349
|
+
|
|
350
|
+
const spec = swagger.swaggerDoc('User API', '1.0.0');
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Express Integration
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
const express = require('express');
|
|
357
|
+
const swaggerUi = require('swagger-ui-express');
|
|
358
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
359
|
+
|
|
360
|
+
const app = express();
|
|
361
|
+
|
|
362
|
+
// Define API spec
|
|
363
|
+
swagger.server('http://localhost:3000', 'Local API');
|
|
364
|
+
|
|
365
|
+
swagger.get('/health')
|
|
366
|
+
.summary('Health check')
|
|
367
|
+
.res(200, '{status:s, timestamp:s}');
|
|
368
|
+
|
|
369
|
+
swagger.get('/users/{id}')
|
|
370
|
+
.summary('Get user')
|
|
371
|
+
.res(200, '{id:i, name:s}');
|
|
372
|
+
|
|
373
|
+
// Serve Swagger UI
|
|
374
|
+
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swagger.swaggerDoc('My API')));
|
|
375
|
+
|
|
376
|
+
// API routes
|
|
377
|
+
app.get('/health', (req, res) => {
|
|
378
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
app.get('/users/:id', (req, res) => {
|
|
382
|
+
const user = { id: parseInt(req.params.id), name: 'John Doe' };
|
|
383
|
+
res.json(user);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
app.listen(3000, () => {
|
|
387
|
+
console.log('Server running on http://localhost:3000');
|
|
388
|
+
console.log('API docs at http://localhost:3000/api-docs');
|
|
389
|
+
});
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### Advanced Schema Examples
|
|
393
|
+
|
|
394
|
+
```javascript
|
|
395
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
396
|
+
|
|
397
|
+
// Complex nested schemas
|
|
398
|
+
const profileSchema = swagger.ref.schema('Profile', `
|
|
399
|
+
{
|
|
400
|
+
personal: {
|
|
401
|
+
firstName: s,
|
|
402
|
+
lastName: s,
|
|
403
|
+
age: ?i,
|
|
404
|
+
email: s
|
|
405
|
+
},
|
|
406
|
+
preferences: {
|
|
407
|
+
theme: s:light,
|
|
408
|
+
notifications: {
|
|
409
|
+
email: b:true,
|
|
410
|
+
push: b:false
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
tags: [s]
|
|
414
|
+
}
|
|
415
|
+
`);
|
|
416
|
+
|
|
417
|
+
// Array responses
|
|
418
|
+
swagger.get('/posts')
|
|
419
|
+
.summary('Get blog posts')
|
|
420
|
+
.query('category:?s,tag:?s,published:?b:true')
|
|
421
|
+
.res(200, `
|
|
422
|
+
[{
|
|
423
|
+
id: i,
|
|
424
|
+
title: s,
|
|
425
|
+
content: s,
|
|
426
|
+
author: {id:i, name:s},
|
|
427
|
+
tags: [s],
|
|
428
|
+
published: b,
|
|
429
|
+
created_at: s
|
|
430
|
+
}]
|
|
431
|
+
`);
|
|
432
|
+
|
|
433
|
+
// Union-like responses (use oneOf in OpenAPI)
|
|
434
|
+
swagger.post('/upload')
|
|
435
|
+
.summary('Upload file')
|
|
436
|
+
.req('{file:s, type:s}') // In practice, use multipart/form-data
|
|
437
|
+
.res(200, '{id:s, url:s, size:i}')
|
|
438
|
+
.res(400, '{error:s, code:s}');
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
## TypeScript Support
|
|
442
|
+
|
|
443
|
+
Alt Swagger includes full TypeScript definitions:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
import * as swagger from '@sleeksky/alt-swagger';
|
|
447
|
+
|
|
448
|
+
swagger.server('https://api.example.com');
|
|
449
|
+
swagger.get('/users').res(200, '[{id:number, name:string}]');
|
|
450
|
+
|
|
451
|
+
const spec: swagger.SwaggerDoc = swagger.swaggerDoc('API');
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Integration with Frameworks
|
|
455
|
+
|
|
456
|
+
### Fastify
|
|
457
|
+
|
|
458
|
+
```javascript
|
|
459
|
+
const fastify = require('fastify')();
|
|
460
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
461
|
+
|
|
462
|
+
// Define spec
|
|
463
|
+
swagger.server('http://localhost:3000');
|
|
464
|
+
swagger.get('/users').res(200, '[{id:i, name:s}]');
|
|
465
|
+
|
|
466
|
+
// Register Swagger
|
|
467
|
+
fastify.register(require('@fastify/swagger'), {
|
|
468
|
+
specification: {
|
|
469
|
+
document: swagger.swaggerDoc('Fastify API')
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Hapi
|
|
475
|
+
|
|
476
|
+
```javascript
|
|
477
|
+
const Hapi = require('@hapi/hapi');
|
|
478
|
+
const swagger = require('@sleeksky/alt-swagger');
|
|
479
|
+
|
|
480
|
+
const server = Hapi.server({ port: 3000 });
|
|
481
|
+
|
|
482
|
+
// Define spec
|
|
483
|
+
swagger.server('http://localhost:3000');
|
|
484
|
+
swagger.get('/users').res(200, '[{id:i, name:s}]');
|
|
485
|
+
|
|
486
|
+
// Use hapi-swagger
|
|
487
|
+
server.register([
|
|
488
|
+
require('hapi-swagger'),
|
|
489
|
+
{
|
|
490
|
+
options: {
|
|
491
|
+
documentationPage: true,
|
|
492
|
+
swaggerOptions: {
|
|
493
|
+
spec: swagger.swaggerDoc('Hapi API')
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
]);
|
|
10
498
|
```
|
|
11
|
-

|
|
12
|
-
# Installation
|
|
13
499
|
|
|
14
|
-
|
|
500
|
+
## Best Practices
|
|
15
501
|
|
|
16
|
-
|
|
502
|
+
1. **Define schemas first**: Use `swagger.ref.schema()` for reusable schemas
|
|
503
|
+
2. **Use meaningful tags**: Group related endpoints with tags
|
|
504
|
+
3. **Document thoroughly**: Always include summaries and descriptions
|
|
505
|
+
4. **Handle errors**: Define error responses for all endpoints
|
|
506
|
+
5. **Version your API**: Use semantic versioning in `swaggerDoc()`
|
|
507
|
+
6. **Validate schemas**: Test your generated specs with OpenAPI validators
|
|
17
508
|
|
|
18
|
-
|
|
509
|
+
## Contributing
|
|
19
510
|
|
|
20
|
-
|
|
511
|
+
Contributions are welcome! Please feel free to submit issues and pull requests.
|
|
21
512
|
|
|
22
|
-
|
|
513
|
+
## License
|
|
23
514
|
|
|
24
|
-
MIT © SleekSky
|
|
515
|
+
MIT © [SleekSky](https://sleeksky.com)
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sleeksky/alt-swagger",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "./
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "A fluent, programmatic API for generating OpenAPI 3.0 specifications with TypeScript support. Define REST API endpoints, schemas, parameters, and security using a simple, chainable syntax.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "mocha --require ts-node/register test/index.ts",
|
|
8
10
|
"cover": "nyc --check-coverage npm run test",
|
|
9
|
-
"prepublish": "npm run test"
|
|
11
|
+
"prepublish": "npm run build && npm run test"
|
|
10
12
|
},
|
|
11
13
|
"files": [
|
|
12
14
|
"src"
|
|
@@ -29,12 +31,19 @@
|
|
|
29
31
|
},
|
|
30
32
|
"homepage": "https://github.com/sleeksky-dev/alt-swagger#readme",
|
|
31
33
|
"devDependencies": {
|
|
34
|
+
"@types/chai": "^5.2.3",
|
|
35
|
+
"@types/express": "^5.0.6",
|
|
36
|
+
"@types/lodash": "^4.17.21",
|
|
37
|
+
"@types/mocha": "^10.0.10",
|
|
38
|
+
"@types/node": "^25.0.3",
|
|
32
39
|
"chai": "^4.1.2",
|
|
33
40
|
"cross-env": "^5.1.3",
|
|
34
41
|
"express": "^4.17.1",
|
|
35
42
|
"mocha": "^6.1.3",
|
|
36
43
|
"nyc": "^13.3.0",
|
|
37
|
-
"swagger-ui-express": "^4.1.6"
|
|
44
|
+
"swagger-ui-express": "^4.1.6",
|
|
45
|
+
"ts-node": "^10.9.2",
|
|
46
|
+
"typescript": "^5.9.3"
|
|
38
47
|
},
|
|
39
48
|
"dependencies": {
|
|
40
49
|
"@sleeksky/alt-schema": "^2.1.0",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Author: Yusuf Bhabhrawala
|
|
3
|
+
*/
|
|
4
|
+
import "./utils";
|
|
5
|
+
import * as _ from "lodash";
|
|
6
|
+
import { toSwaggerSchema, pathParameters, pathClean, toParameter, SwaggerSchema, PathParameter, Parameter } from "./utils";
|
|
7
|
+
|
|
8
|
+
interface Server {
|
|
9
|
+
url: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Tag {
|
|
14
|
+
name: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface SecurityScheme {
|
|
19
|
+
type: string;
|
|
20
|
+
scheme?: string;
|
|
21
|
+
bearerFormat?: string | null;
|
|
22
|
+
in?: string;
|
|
23
|
+
required?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface Components {
|
|
27
|
+
securitySchemes?: { [key: string]: SecurityScheme };
|
|
28
|
+
schemas?: { [key: string]: SwaggerSchema };
|
|
29
|
+
[key: string]: any;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface ResponseSpec {
|
|
33
|
+
content?: { [key: string]: { schema: SwaggerSchema } };
|
|
34
|
+
description: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface PathSpec {
|
|
38
|
+
parameters: (PathParameter | Parameter)[];
|
|
39
|
+
responses: { [key: string]: ResponseSpec };
|
|
40
|
+
description: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
summary?: string;
|
|
43
|
+
security?: { [key: string]: string[] }[];
|
|
44
|
+
deprecated?: boolean;
|
|
45
|
+
requestBody?: {
|
|
46
|
+
content: { [key: string]: { schema: SwaggerSchema } };
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Paths {
|
|
51
|
+
[key: string]: {
|
|
52
|
+
[method: string]: PathSpec;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const paths: Paths = {};
|
|
57
|
+
const components: Components = {};
|
|
58
|
+
const tags: Tag[] = [];
|
|
59
|
+
const servers: Server[] = [];
|
|
60
|
+
|
|
61
|
+
function reset(): void {
|
|
62
|
+
Object.keys(paths).forEach((key) => delete paths[key]);
|
|
63
|
+
Object.keys(components).forEach((key) => delete components[key]);
|
|
64
|
+
tags.splice(0, tags.length);
|
|
65
|
+
servers.splice(0, servers.length);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function server(url: string, description?: string): void {
|
|
69
|
+
servers.push({ url, description });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
interface ApiOptions {
|
|
73
|
+
path: string;
|
|
74
|
+
method: string;
|
|
75
|
+
tag?: string;
|
|
76
|
+
desc?: string;
|
|
77
|
+
summary?: string;
|
|
78
|
+
req?: string;
|
|
79
|
+
header?: string | string[];
|
|
80
|
+
query?: string | string[];
|
|
81
|
+
security?: string;
|
|
82
|
+
deprecated?: boolean;
|
|
83
|
+
[key: string]: any; // for numeric response codes
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
interface ApiExtension {
|
|
87
|
+
req: (flatSch: string) => ApiExtension;
|
|
88
|
+
res: (code: string | number, flatSch: string) => ApiExtension;
|
|
89
|
+
query: (strArr: string | string[]) => ApiExtension;
|
|
90
|
+
header: (strArr: string | string[]) => ApiExtension;
|
|
91
|
+
tag: (str: string) => ApiExtension;
|
|
92
|
+
summary: (str: string) => ApiExtension;
|
|
93
|
+
desc: (str: string) => ApiExtension;
|
|
94
|
+
security: (str: string) => ApiExtension;
|
|
95
|
+
deprecate: () => ApiExtension;
|
|
96
|
+
remove: () => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function api(opt: ApiOptions): ApiExtension {
|
|
100
|
+
let path = `${pathClean(opt.path)}.${opt.method}`;
|
|
101
|
+
let spec: PathSpec = _.get(paths as any, path, null) as PathSpec;
|
|
102
|
+
if (!spec) {
|
|
103
|
+
spec = { parameters: [], responses: {}, description: "" };
|
|
104
|
+
const pathParams = pathParameters(opt.path);
|
|
105
|
+
if (pathParams.length > 0) spec.parameters = pathParams;
|
|
106
|
+
_.set(paths, path, spec);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let ext: ApiExtension = {} as ApiExtension;
|
|
110
|
+
|
|
111
|
+
const req = (flatSch: string): ApiExtension => {
|
|
112
|
+
let schema = flatSch.match(/^#/) ? { $ref: flatSch } : toSwaggerSchema(flatSch);
|
|
113
|
+
spec.requestBody = {
|
|
114
|
+
content: { "application/json": { schema } },
|
|
115
|
+
};
|
|
116
|
+
return ext;
|
|
117
|
+
};
|
|
118
|
+
const res = (code: string | number, flatSch: string): ApiExtension => {
|
|
119
|
+
let schema = flatSch.match(/^#/) ? { $ref: flatSch } : toSwaggerSchema(flatSch);
|
|
120
|
+
spec.responses[code] = {
|
|
121
|
+
content: { [schema.type === "string" ? "text/plain" : "application/json"]: { schema } },
|
|
122
|
+
description: "",
|
|
123
|
+
};
|
|
124
|
+
return ext;
|
|
125
|
+
};
|
|
126
|
+
const query = (strArr: string | string[]): ApiExtension => {
|
|
127
|
+
if (!_.isArray(strArr)) strArr = (strArr as string).split(",");
|
|
128
|
+
strArr.forEach((str) => {
|
|
129
|
+
spec.parameters.push(toParameter('query', str));
|
|
130
|
+
});
|
|
131
|
+
return ext;
|
|
132
|
+
};
|
|
133
|
+
const header = (strArr: string | string[]): ApiExtension => {
|
|
134
|
+
if (!_.isArray(strArr)) strArr = (strArr as string).split(",");
|
|
135
|
+
strArr.forEach((str) => {
|
|
136
|
+
spec.parameters.push(toParameter('header', str));
|
|
137
|
+
});
|
|
138
|
+
return ext;
|
|
139
|
+
};
|
|
140
|
+
const tag = (str: string): ApiExtension => { spec.tags = [str]; return ext; };
|
|
141
|
+
const summary = (str: string): ApiExtension => { spec.summary = str; return ext; };
|
|
142
|
+
const desc = (str: string): ApiExtension => { spec.description = str; return ext; };
|
|
143
|
+
const security = (str: string): ApiExtension => { spec.security = [ { [str]: []}]; return ext; }
|
|
144
|
+
const deprecate = (): ApiExtension => { spec.deprecated = true; return ext; };
|
|
145
|
+
const remove = (): void => { _.unset(paths, `${pathClean(opt.path)}.${opt.method}`); };
|
|
146
|
+
|
|
147
|
+
Object.assign(ext, { req, res, query, header, tag, summary, desc, deprecate, remove, security });
|
|
148
|
+
|
|
149
|
+
// support all ext in parameters
|
|
150
|
+
Object.keys(ext).forEach(k => {
|
|
151
|
+
if (opt[k]) (ext as any)[k](opt[k]);
|
|
152
|
+
});
|
|
153
|
+
Object.keys(opt).filter(k => k.match(/^[0-9]+$/)).forEach(k => ext.res(parseInt(k), opt[k]));
|
|
154
|
+
|
|
155
|
+
return ext;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function get(path: string = "", opt: Partial<ApiOptions> = {}): ApiExtension {
|
|
159
|
+
opt.method = "get";
|
|
160
|
+
opt.path = path;
|
|
161
|
+
return api(opt as ApiOptions);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function post(path: string = "", opt: Partial<ApiOptions> = {}): ApiExtension {
|
|
165
|
+
opt.method = "post";
|
|
166
|
+
opt.path = path;
|
|
167
|
+
return api(opt as ApiOptions);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function put(path: string = "", opt: Partial<ApiOptions> = {}): ApiExtension {
|
|
171
|
+
opt.method = "put";
|
|
172
|
+
opt.path = path;
|
|
173
|
+
return api(opt as ApiOptions);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function patch(path: string = "", opt: Partial<ApiOptions> = {}): ApiExtension {
|
|
177
|
+
opt.method = "patch";
|
|
178
|
+
opt.path = path;
|
|
179
|
+
return api(opt as ApiOptions);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function del(path: string = "", opt: Partial<ApiOptions> = {}): ApiExtension {
|
|
183
|
+
opt.method = "delete";
|
|
184
|
+
opt.path = path;
|
|
185
|
+
return api(opt as ApiOptions);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
interface RemoveOptions {
|
|
189
|
+
path?: string;
|
|
190
|
+
tag?: string;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function remove({path, tag}: RemoveOptions): void {
|
|
194
|
+
if (path) _.unset(paths, `${pathClean(path)}`);
|
|
195
|
+
if (tag) {
|
|
196
|
+
Object.keys(paths).forEach(p => {
|
|
197
|
+
Object.keys(paths[p]).forEach(method => {
|
|
198
|
+
if (paths[p][method].tags && paths[p][method].tags!.includes(tag)) _.unset(paths[p], method);
|
|
199
|
+
})
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
interface SecurityOptions {
|
|
205
|
+
type?: string;
|
|
206
|
+
schema?: string;
|
|
207
|
+
bearerFormat?: string | null;
|
|
208
|
+
required?: boolean;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function security(name: string, {type = "http", schema = "bearer", bearerFormat = null, required = true}: SecurityOptions = {}): string {
|
|
212
|
+
components.securitySchemes = components.securitySchemes || {};
|
|
213
|
+
components.securitySchemes[name] = { type, scheme: schema, bearerFormat, "in": "header", required };
|
|
214
|
+
return `#/components/securitySchemes/${name}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
interface RefOptions {
|
|
218
|
+
type: string;
|
|
219
|
+
name: string;
|
|
220
|
+
def: SwaggerSchema;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function _ref({ type, name, def }: RefOptions): string {
|
|
224
|
+
components[type] = components[type] || {};
|
|
225
|
+
(components[type] as any)[name] = def;
|
|
226
|
+
return `#/components/${type}/${name}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const ref = {
|
|
230
|
+
schema(name: string, flatSch: string): string {
|
|
231
|
+
return _ref({ type: "schemas", name, def: toSwaggerSchema(flatSch) });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function tag(name: string, description?: string): void {
|
|
236
|
+
tags.push({ name, description });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
interface SwaggerDoc {
|
|
240
|
+
info: { title: string; version: string };
|
|
241
|
+
openapi: string;
|
|
242
|
+
servers: Server[];
|
|
243
|
+
tags: Tag[];
|
|
244
|
+
paths: Paths;
|
|
245
|
+
components: Components;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function swaggerDoc(title?: string): SwaggerDoc {
|
|
249
|
+
return {
|
|
250
|
+
info: { title: title || "", version: "1.0.0" },
|
|
251
|
+
openapi: "3.0.0",
|
|
252
|
+
servers,
|
|
253
|
+
tags,
|
|
254
|
+
paths,
|
|
255
|
+
components,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export {
|
|
260
|
+
tag,
|
|
261
|
+
server,
|
|
262
|
+
ref,
|
|
263
|
+
get,
|
|
264
|
+
post,
|
|
265
|
+
put,
|
|
266
|
+
patch,
|
|
267
|
+
del,
|
|
268
|
+
security,
|
|
269
|
+
swaggerDoc,
|
|
270
|
+
reset,
|
|
271
|
+
remove,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export type { ApiOptions, ApiExtension, SwaggerDoc };
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
Author: Yusuf Bhabhrawala
|
|
3
|
+
*/
|
|
4
|
+
import * as _ from "lodash";
|
|
5
|
+
// @ts-ignore
|
|
6
|
+
import { typeShape } from "@sleeksky/alt-schema";
|
|
7
|
+
|
|
8
|
+
const TYPES: { [key: string]: string } = { i: "integer", s: "string", b: "boolean", n: "number", o: "object", a: "array" };
|
|
9
|
+
|
|
10
|
+
const RX_BRACE = /\{([^\}]+)\}/g;
|
|
11
|
+
const RX_COLON = /\:([^\/]+)/g;
|
|
12
|
+
|
|
13
|
+
interface SwaggerSchema {
|
|
14
|
+
type?: string;
|
|
15
|
+
items?: SwaggerSchema;
|
|
16
|
+
properties?: { [key: string]: SwaggerSchema };
|
|
17
|
+
example?: any;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
$ref?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toSwaggerSchema(str: string): SwaggerSchema {
|
|
23
|
+
function traverse(schema: SwaggerSchema, obj: any): void {
|
|
24
|
+
if (_.isArray(obj)) {
|
|
25
|
+
schema.type = "array";
|
|
26
|
+
schema.items = {};
|
|
27
|
+
traverse(schema.items, obj[0]);
|
|
28
|
+
} else if (_.isObject(obj)) {
|
|
29
|
+
schema.type = "object";
|
|
30
|
+
schema.properties = {};
|
|
31
|
+
_.forOwn(obj, (v, k) => {
|
|
32
|
+
schema.properties![k] = {};
|
|
33
|
+
traverse(schema.properties![k], v);
|
|
34
|
+
});
|
|
35
|
+
} else {
|
|
36
|
+
let [optional, type, def] = (obj as string).split(":");
|
|
37
|
+
const isOptional = optional === '?';
|
|
38
|
+
if (isOptional) {
|
|
39
|
+
type = type || 'string';
|
|
40
|
+
}
|
|
41
|
+
if (['string','number','boolean','integer','array','object'].indexOf(type) < 0) type = "string";
|
|
42
|
+
schema.type = type;
|
|
43
|
+
if (def !== undefined && def !== '') {
|
|
44
|
+
if (type === 'number' || type === 'integer') {
|
|
45
|
+
schema.example = parseFloat(def);
|
|
46
|
+
} else if (type === 'boolean') {
|
|
47
|
+
schema.example = ['true','1'].indexOf(def) > -1;
|
|
48
|
+
} else {
|
|
49
|
+
schema.example = def;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (!isOptional) schema.required = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const obj = typeShape(str);
|
|
58
|
+
const schema: SwaggerSchema = {};
|
|
59
|
+
traverse(schema, obj);
|
|
60
|
+
return schema;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(`Malformed schema: ${str}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
interface PathParameter {
|
|
67
|
+
in: string;
|
|
68
|
+
name: string;
|
|
69
|
+
schema: { type: string; example?: string };
|
|
70
|
+
required: boolean;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function pathParameters(str: string): PathParameter[] {
|
|
74
|
+
let params: PathParameter[] = [];
|
|
75
|
+
let matches = str.match(RX_BRACE);
|
|
76
|
+
if (!matches) matches = str.match(RX_COLON);
|
|
77
|
+
if (matches) {
|
|
78
|
+
params = matches.map(m => {
|
|
79
|
+
let [p, def] = m.replace(/^[\{\:]/,"").replace(/[\}]$/,"").split(":");
|
|
80
|
+
if (!def) def = "";
|
|
81
|
+
return { in: "path", name: p, schema: { type: "string", example: def }, required: true };
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return params;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function pathClean(path: string): string {
|
|
88
|
+
if (path.match(RX_BRACE)) return path.replace(RX_BRACE, m => `${m.split(/(\:.+)?\}/)[0]}}`)
|
|
89
|
+
return path;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
interface Parameter {
|
|
93
|
+
name: string;
|
|
94
|
+
in: string;
|
|
95
|
+
required: boolean;
|
|
96
|
+
schema: { type: string; example?: string };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// header:?token
|
|
100
|
+
function toParameter(inType: string, str: string | Parameter): Parameter {
|
|
101
|
+
if (!_.isString(str)) return str as Parameter;
|
|
102
|
+
const cleanStr = str.replace(/ /g, "");
|
|
103
|
+
let [name, type, def] = cleanStr.split(":");
|
|
104
|
+
let required = true;
|
|
105
|
+
if (type && type.match(/^\?/)) {
|
|
106
|
+
type = type.replace(/^\?/, "");
|
|
107
|
+
required = false;
|
|
108
|
+
}
|
|
109
|
+
if (type && TYPES[type]) type = TYPES[type];
|
|
110
|
+
else type = "string";
|
|
111
|
+
const schema: { type: string; example?: string } = { type };
|
|
112
|
+
if (def) schema.example = def;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
name,
|
|
116
|
+
in: inType,
|
|
117
|
+
required,
|
|
118
|
+
schema
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export { toSwaggerSchema, pathParameters, pathClean, toParameter };
|
|
123
|
+
export type { SwaggerSchema, PathParameter, Parameter };
|
|
124
|
+
|
|
125
|
+
module.exports = {toSwaggerSchema, pathParameters, pathClean, toParameter};
|
package/src/index.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Author: Yusuf Bhabhrawala
|
|
3
|
-
*/
|
|
4
|
-
require("./utils");
|
|
5
|
-
const _ = require("lodash");
|
|
6
|
-
const { toSwaggerSchema, pathParameters, pathClean, toParameter} = require("./utils")
|
|
7
|
-
|
|
8
|
-
const paths = {};
|
|
9
|
-
const components = {};
|
|
10
|
-
const tags = [];
|
|
11
|
-
const servers = [];
|
|
12
|
-
|
|
13
|
-
function reset() {
|
|
14
|
-
Object.keys(paths).forEach((key) => delete paths[key]);
|
|
15
|
-
Object.keys(components).forEach((key) => delete paths[key]);
|
|
16
|
-
tags.splice(0, tags.length);
|
|
17
|
-
servers.splice(0, servers.length);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function server(url, description) {
|
|
21
|
-
servers.push({ url, description });
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function api(opt) {
|
|
25
|
-
let path = `${pathClean(opt.path)}.${opt.method}`;
|
|
26
|
-
let spec = _.get(paths, path, false);
|
|
27
|
-
if (!spec) {
|
|
28
|
-
spec = { parameters: [], responses: {}, description: "" };
|
|
29
|
-
const pathParams = pathParameters(opt.path);
|
|
30
|
-
if (pathParams.length > 0) spec.parameters = pathParams;
|
|
31
|
-
_.set(paths, path, spec);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let ext = {};
|
|
35
|
-
|
|
36
|
-
const req = (flatSch) => {
|
|
37
|
-
let schema = flatSch.match(/^#/) ? { $ref: flatSch } : toSwaggerSchema(flatSch);
|
|
38
|
-
spec.requestBody = {
|
|
39
|
-
content: { "application/json": { schema } },
|
|
40
|
-
};
|
|
41
|
-
return ext;
|
|
42
|
-
};
|
|
43
|
-
const res = (code, flatSch) => {
|
|
44
|
-
let schema = flatSch.match(/^#/) ? { $ref: flatSch } : toSwaggerSchema(flatSch);
|
|
45
|
-
spec.responses[code] = {
|
|
46
|
-
content: { [schema.type === "string" ? "text/plain" : "application/json"]: { schema } },
|
|
47
|
-
description: "",
|
|
48
|
-
};
|
|
49
|
-
return ext;
|
|
50
|
-
};
|
|
51
|
-
const query = (strArr) => {
|
|
52
|
-
if (!_.isArray(strArr)) strArr = strArr.split(",");
|
|
53
|
-
strArr.forEach((str) => {
|
|
54
|
-
spec.parameters.push(toParameter('query',str));
|
|
55
|
-
});
|
|
56
|
-
return ext;
|
|
57
|
-
};
|
|
58
|
-
const header = (strArr) => {
|
|
59
|
-
if (!_.isArray(strArr)) strArr = strArr.split(",");
|
|
60
|
-
strArr.forEach((str) => {
|
|
61
|
-
spec.parameters.push(toParameter('header',str));
|
|
62
|
-
});
|
|
63
|
-
return ext;
|
|
64
|
-
};
|
|
65
|
-
const tag = (str) => { spec.tags = [str]; return ext; };
|
|
66
|
-
const summary = (str) => { spec.summary = str; return ext; };
|
|
67
|
-
const desc = (str) => { spec.description = str; return ext; };
|
|
68
|
-
const security = (str) => { spec.security = [ { [str]: []}]; return ext; }
|
|
69
|
-
const deprecate = () => { spec.deprecated = true; return ext; };
|
|
70
|
-
const remove = () => { _.unset(paths, `${pathClean(opt.path)}.${opt.method}`); };
|
|
71
|
-
|
|
72
|
-
Object.assign(ext, { req, res, query, header, tag, summary, desc, deprecate, remove, security });
|
|
73
|
-
|
|
74
|
-
// support all ext in parameters
|
|
75
|
-
Object.keys(ext).forEach(k => {
|
|
76
|
-
if (opt[k]) ext[k](opt[k]);
|
|
77
|
-
});
|
|
78
|
-
Object.keys(opt).filter(k => k.match(/^[0-9]+$/)).forEach(k => ext.res(k, opt[k]));
|
|
79
|
-
|
|
80
|
-
return ext;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function get(path = "", opt = {}) {
|
|
84
|
-
opt.method = "get";
|
|
85
|
-
opt.path = path;
|
|
86
|
-
return api(opt);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function post(path = "", opt = {}) {
|
|
90
|
-
opt.method = "post";
|
|
91
|
-
opt.path = path;
|
|
92
|
-
return api(opt);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function put(path = "", opt = {}) {
|
|
96
|
-
opt.method = "put";
|
|
97
|
-
opt.path = path;
|
|
98
|
-
return api(opt);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function patch(path = "", opt = {}) {
|
|
102
|
-
opt.method = "patch";
|
|
103
|
-
opt.path = path;
|
|
104
|
-
return api(opt);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function del(path = "", opt = {}) {
|
|
108
|
-
opt.method = "delete";
|
|
109
|
-
opt.path = path;
|
|
110
|
-
return api(opt);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function remove({path=null, tag=null}) {
|
|
114
|
-
if (path) _.unset(paths, `${pathClean(path)}`);
|
|
115
|
-
if (tag) {
|
|
116
|
-
// remote all the paths with this tag
|
|
117
|
-
Object.keys(paths).forEach(p => {
|
|
118
|
-
Object.keys(paths[p]).forEach(method => {
|
|
119
|
-
if (paths[p][method].tags && paths[p][method].tags.includes(tag)) _.unset(paths[p], method);
|
|
120
|
-
})
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function security(name, {type = "http", schema = "bearer", bearerFormat = null, required = true}) {
|
|
126
|
-
components.securitySchemes = components.securitySchemes || {};
|
|
127
|
-
components.securitySchemes[name] = { type, scheme: schema, bearerFormat, "in": "header", required };
|
|
128
|
-
return `#/components/securitySchemes/${name}`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function _ref({ type, name, def }) {
|
|
132
|
-
components[type] = components[type] || {};
|
|
133
|
-
components[type][name] = def;
|
|
134
|
-
return `#/components/${type}/${name}`;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const ref = {
|
|
138
|
-
schema(name, flatSch) {
|
|
139
|
-
return _ref({ type: "schemas", name, def: toSwaggerSchema(flatSch) });
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function tag(name, description) {
|
|
144
|
-
tags.push({ name, description });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function swaggerDoc(title) {
|
|
148
|
-
return {
|
|
149
|
-
info: { title: title || "", version: "1.0.0" },
|
|
150
|
-
openapi: "3.0.0",
|
|
151
|
-
servers,
|
|
152
|
-
tags,
|
|
153
|
-
paths,
|
|
154
|
-
components,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
module.exports = {
|
|
159
|
-
tag,
|
|
160
|
-
server,
|
|
161
|
-
ref,
|
|
162
|
-
get,
|
|
163
|
-
post,
|
|
164
|
-
put,
|
|
165
|
-
patch,
|
|
166
|
-
del,
|
|
167
|
-
security,
|
|
168
|
-
swaggerDoc,
|
|
169
|
-
reset,
|
|
170
|
-
remove,
|
|
171
|
-
};
|
package/src/utils.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
Author: Yusuf Bhabhrawala
|
|
3
|
-
*/
|
|
4
|
-
const _ = require("lodash");
|
|
5
|
-
|
|
6
|
-
const {typeShape} = require("@sleeksky/alt-schema");
|
|
7
|
-
|
|
8
|
-
const TYPES = {i: "integer", s: "string", b: "boolean", n: "number", o: "object", a: "array"};
|
|
9
|
-
const RX_OBJ = /(\{[^\{\}\[\]]+\})/;
|
|
10
|
-
const RX_ARR = /(\[[^\{\}\[\]]+\])/;
|
|
11
|
-
const RX_NESTED = /^\:?(\$[0-9]+)$/;
|
|
12
|
-
const RX_FLAT_ARR = /^\[([^\{\}\[\]]+)\]$/;
|
|
13
|
-
const RX_FLAT_OBJ = /^\{([^\{\}\[\]]+)\}$/;
|
|
14
|
-
|
|
15
|
-
const RX_BRACE = /\{([^\}]+)\}/g;
|
|
16
|
-
const RX_COLON = /\:([^\/]+)/g;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function toSwaggerSchema(str) {
|
|
20
|
-
|
|
21
|
-
function traverse(schema, obj) {
|
|
22
|
-
if (_.isArray(obj)) {
|
|
23
|
-
schema.type = "array";
|
|
24
|
-
schema.items = {};
|
|
25
|
-
traverse(schema.items, obj[0]);
|
|
26
|
-
} else if (_.isObject(obj)) {
|
|
27
|
-
schema.type = "object";
|
|
28
|
-
schema.properties = {};
|
|
29
|
-
_.forOwn(obj, (v, k) => {
|
|
30
|
-
schema.properties[k] = {};
|
|
31
|
-
traverse(schema.properties[k], v);
|
|
32
|
-
});
|
|
33
|
-
} else {
|
|
34
|
-
let [optional, type, def] = obj.split(":");
|
|
35
|
-
optional = (optional === '?') ? true : false;
|
|
36
|
-
if (['string','number','boolean','integer','array','object'].indexOf(type) < 0) type = "string";
|
|
37
|
-
schema.type = type;
|
|
38
|
-
if (def !== '') {
|
|
39
|
-
if (type === 'number' || type === 'integer') def = def*1;
|
|
40
|
-
if (type === 'boolean') def = ['true','1'].indexOf(def) > -1 ? true : false;
|
|
41
|
-
schema.example = def;
|
|
42
|
-
} else if (optional) {
|
|
43
|
-
// schema.default = null;
|
|
44
|
-
}
|
|
45
|
-
if (!optional) schema.required = true;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
let obj = typeShape(str);
|
|
51
|
-
let schema = {};
|
|
52
|
-
traverse(schema, obj);
|
|
53
|
-
return schema;
|
|
54
|
-
} catch (error) {
|
|
55
|
-
throw new Error(`Malformed schema: ${str}`);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function pathParameters(str) {
|
|
60
|
-
let params = [];
|
|
61
|
-
let matches = str.match(RX_BRACE);
|
|
62
|
-
if (!matches) matches = str.match(RX_COLON);
|
|
63
|
-
if (matches) {
|
|
64
|
-
params = matches.map(m => {
|
|
65
|
-
let [p, def] = m.replace(/^[\{\:]/,"").replace(/[\}]$/,"").split(":");
|
|
66
|
-
if (!def) def = "";
|
|
67
|
-
return { in: "path", name: p, schema: { type: "string", example: def }, required: true };
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
return params;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function pathClean(path) {
|
|
74
|
-
if (path.match(RX_BRACE)) return path.replace(RX_BRACE, m => `${m.split(/(\:.+)?\}/)[0]}}`)
|
|
75
|
-
return path;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// header:?token
|
|
79
|
-
function toParameter(inType, str) {
|
|
80
|
-
if (!_.isString(str)) return str;
|
|
81
|
-
str = str.replace(/ /g, "");
|
|
82
|
-
let [name, type, def] = str.split(":");
|
|
83
|
-
let required = true;
|
|
84
|
-
if (type && type.match(/^\?/)) {
|
|
85
|
-
type = type.replace(/^\?/, "");
|
|
86
|
-
required = false;
|
|
87
|
-
}
|
|
88
|
-
if (type && TYPES[type]) type = TYPES[type];
|
|
89
|
-
else type = "string";
|
|
90
|
-
let schema = { type };
|
|
91
|
-
if (def) schema.example = def;
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
name,
|
|
95
|
-
in: inType,
|
|
96
|
-
required,
|
|
97
|
-
schema
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
module.exports = {toSwaggerSchema, pathParameters, pathClean, toParameter};
|