@necessarylion/adonis-autoswagger 0.0.1
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/LICENSE +21 -0
- package/README.md +586 -0
- package/dist/adonishelpers.d.ts +6 -0
- package/dist/adonishelpers.js +70 -0
- package/dist/autoswagger.d.ts +30 -0
- package/dist/autoswagger.js +777 -0
- package/dist/example.d.ts +73 -0
- package/dist/example.js +349 -0
- package/dist/helpers.d.ts +7 -0
- package/dist/helpers.js +54 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/parsers.d.ts +73 -0
- package/dist/parsers.js +1107 -0
- package/dist/scalarCustomCss.d.ts +1 -0
- package/dist/scalarCustomCss.js +133 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +14 -0
- package/package.json +48 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Adis Durakovic
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
Adonis AutoSwagger <br />
|
|
3
|
+
<img src="https://upload.wikimedia.org/wikipedia/commons/a/ab/Swagger-logo.png" height="50" />
|
|
4
|
+
</h1>
|
|
5
|
+
|
|
6
|
+
[]()
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
[]()
|
|
10
|
+
|
|
11
|
+
### Auto-Generate swagger docs for AdonisJS
|
|
12
|
+
|
|
13
|
+
## 💻️ Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm i adonis-autoswagger #using pnpm
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## ⭐️ Features
|
|
22
|
+
|
|
23
|
+
- Creates **paths** automatically based on `routes.ts`
|
|
24
|
+
- Creates **schemas** automatically based on `app/Models/*`
|
|
25
|
+
- Creates **schemas** automatically based on `app/Interfaces/*`
|
|
26
|
+
- Creates **schemas** automatically based on `app/Validators/*` (only for adonisJS v6)
|
|
27
|
+
- Creates **schemas** automatically based on `app/Types/*` (only for adonisJS v6)
|
|
28
|
+
- **Rich configuration** via comments
|
|
29
|
+
- Works also in **production** mode
|
|
30
|
+
- `node ace docs:generate` command
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## ✌️Usage
|
|
35
|
+
|
|
36
|
+
Create a file `/config/swagger.ts`
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// for AdonisJS v6
|
|
40
|
+
import path from "node:path";
|
|
41
|
+
import url from "node:url";
|
|
42
|
+
// ---
|
|
43
|
+
|
|
44
|
+
export default {
|
|
45
|
+
// path: __dirname + "/../", for AdonisJS v5
|
|
46
|
+
path: path.dirname(url.fileURLToPath(import.meta.url)) + "/../", // for AdonisJS v6
|
|
47
|
+
title: "Foo", // use info instead
|
|
48
|
+
version: "1.0.0", // use info instead
|
|
49
|
+
description: "", // use info instead
|
|
50
|
+
tagIndex: 2,
|
|
51
|
+
productionEnv: "production", // optional
|
|
52
|
+
info: {
|
|
53
|
+
title: "title",
|
|
54
|
+
version: "1.0.0",
|
|
55
|
+
description: "",
|
|
56
|
+
},
|
|
57
|
+
snakeCase: true,
|
|
58
|
+
|
|
59
|
+
debug: false, // set to true, to get some useful debug output
|
|
60
|
+
ignore: ["/swagger", "/docs"],
|
|
61
|
+
preferredPutPatch: "PUT", // if PUT/PATCH are provided for the same route, prefer PUT
|
|
62
|
+
common: {
|
|
63
|
+
parameters: {}, // OpenAPI conform parameters that are commonly used
|
|
64
|
+
headers: {}, // OpenAPI conform headers that are commonly used
|
|
65
|
+
},
|
|
66
|
+
securitySchemes: {}, // optional
|
|
67
|
+
authMiddlewares: ["auth", "auth:api"], // optional
|
|
68
|
+
defaultSecurityScheme: "BearerAuth", // optional
|
|
69
|
+
persistAuthorization: true, // persist authorization between reloads on the swagger page
|
|
70
|
+
showFullPath: false, // the path displayed after endpoint summary
|
|
71
|
+
};
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
In your `routes.ts`
|
|
75
|
+
|
|
76
|
+
## 6️⃣ for AdonisJS v6
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
import AutoSwagger from "adonis-autoswagger";
|
|
80
|
+
import swagger from "#config/swagger";
|
|
81
|
+
// returns swagger in YAML
|
|
82
|
+
router.get("/swagger", async () => {
|
|
83
|
+
return AutoSwagger.default.docs(router.toJSON(), swagger);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Renders Swagger-UI and passes YAML-output of /swagger
|
|
87
|
+
router.get("/docs", async () => {
|
|
88
|
+
return AutoSwagger.default.ui("/swagger", swagger);
|
|
89
|
+
// return AutoSwagger.default.scalar("/swagger"); to use Scalar instead. If you want, you can pass proxy url as second argument here.
|
|
90
|
+
// return AutoSwagger.default.rapidoc("/swagger", "view"); to use RapiDoc instead (pass "view" default, or "read" to change the render-style)
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 5️⃣ for AdonisJS v5
|
|
95
|
+
|
|
96
|
+
```js
|
|
97
|
+
import AutoSwagger from "adonis-autoswagger";
|
|
98
|
+
import swagger from "Config/swagger";
|
|
99
|
+
// returns swagger in YAML
|
|
100
|
+
Route.get("/swagger", async () => {
|
|
101
|
+
return AutoSwagger.docs(Route.toJSON(), swagger);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Renders Swagger-UI and passes YAML-output of /swagger
|
|
105
|
+
Route.get("/docs", async () => {
|
|
106
|
+
return AutoSwagger.ui("/swagger", swagger);
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 👍️ Done
|
|
111
|
+
|
|
112
|
+
Visit `http://localhost:3333/docs` to see AutoSwagger in action.
|
|
113
|
+
|
|
114
|
+
### Functions
|
|
115
|
+
|
|
116
|
+
- `async docs(routes, conf)`: get the specification in YAML format
|
|
117
|
+
- `async json(routes, conf)`: get the specification in JSON format
|
|
118
|
+
- `ui(path, conf)`: get default swagger UI
|
|
119
|
+
- `rapidoc(path, style)`: get rapidoc UI
|
|
120
|
+
- `scalar(path, proxyUrl)`: get scalar UI
|
|
121
|
+
- `stoplight(path, theme)`: get stoplight elements UI
|
|
122
|
+
- `jsonToYaml(json)`: can be used to convert `json()` back to yaml
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 💡 Compatibility
|
|
127
|
+
|
|
128
|
+
For controllers to get detected properly, please load them lazily.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
✅ const TestController = () => import('#controllers/test_controller')
|
|
132
|
+
❌ import TestController from '#controllers/test_controller'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 🧑💻 Advanced usage
|
|
136
|
+
|
|
137
|
+
### Additional configuration
|
|
138
|
+
|
|
139
|
+
**info**
|
|
140
|
+
See [Swagger API General Info](https://swagger.io/docs/specification/api-general-info/) for details.
|
|
141
|
+
|
|
142
|
+
**securitySchemes**
|
|
143
|
+
|
|
144
|
+
Add/Overwrite security schemes [Swagger Authentication](https://swagger.io/docs/specification/authentication/) for details.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
// example to override ApiKeyAuth
|
|
148
|
+
securitySchemes: {
|
|
149
|
+
ApiKeyAuth: {
|
|
150
|
+
type: "apiKey"
|
|
151
|
+
in: "header",
|
|
152
|
+
name: "X-API-Key"
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**defaultSecurityScheme**
|
|
158
|
+
|
|
159
|
+
Override the default security scheme.
|
|
160
|
+
|
|
161
|
+
- BearerAuth
|
|
162
|
+
- BasicAuth
|
|
163
|
+
- ApiKeyAuth
|
|
164
|
+
- your own defined under `securitySchemes`
|
|
165
|
+
|
|
166
|
+
**authMiddlewares**
|
|
167
|
+
|
|
168
|
+
If a route uses a middleware named `auth`, `auth:api`, AutoSwagger will detect it as a Swagger security method. However, you can implement other middlewares that handle authentication.
|
|
169
|
+
|
|
170
|
+
### Modify generated output
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
Route.get("/myswagger", async () => {
|
|
174
|
+
const json = await AutoSwagger.json(Route.toJSON(), swagger);
|
|
175
|
+
// modify json to your hearts content
|
|
176
|
+
return AutoSwagger.jsonToYaml(json);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
Route.get("/docs", async () => {
|
|
180
|
+
return AutoSwagger.ui("/myswagger", swagger);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Custom Paths in adonisJS v6
|
|
185
|
+
|
|
186
|
+
AutoSwagger supports the paths set in `package.json`. Interfaces are expected to be in `app/interfaces`. However, you can override this, by modifying package.json as follows.
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
//...
|
|
190
|
+
"imports": {
|
|
191
|
+
// ...
|
|
192
|
+
"#interfaces/*": "./app/custom/path/interfaces/*.js"
|
|
193
|
+
// ...
|
|
194
|
+
}
|
|
195
|
+
//...
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## 📃 Configure
|
|
202
|
+
|
|
203
|
+
### `tagIndex`
|
|
204
|
+
|
|
205
|
+
Tags endpoints automatically
|
|
206
|
+
|
|
207
|
+
- If your routes are `/api/v1/products/...` then your tagIndex should be `3`
|
|
208
|
+
- If your routes are `/v1/products/...` then your tagIndex should be `2`
|
|
209
|
+
- If your routes are `/products/...` then your tagIndex should be `1`
|
|
210
|
+
|
|
211
|
+
### `ignore`
|
|
212
|
+
|
|
213
|
+
Ignores specified paths. When used with a wildcard (\*), AutoSwagger will ignore everything matching before/after the wildcard.
|
|
214
|
+
`/test/_`will ignore everything starting with`/test/`, whereas `\*/test`will ignore everything ending with`/test`.
|
|
215
|
+
|
|
216
|
+
### `common`
|
|
217
|
+
|
|
218
|
+
Sometimes you want to use specific parameters or headers on multiple responses.
|
|
219
|
+
|
|
220
|
+
_Example:_ Some resources use the same filter parameters or return the same headers.
|
|
221
|
+
|
|
222
|
+
Here's where you can set these and use them with `@paramUse()` and `@responseHeader() @use()`. See practical example for further details.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
# 💫 Extend Controllers
|
|
227
|
+
|
|
228
|
+
## Add additional documentation to your Controller-files
|
|
229
|
+
|
|
230
|
+
**@summary** (only one)
|
|
231
|
+
A summary of what the action does
|
|
232
|
+
|
|
233
|
+
**@tag** (only one)
|
|
234
|
+
Set a custom tag for this action
|
|
235
|
+
|
|
236
|
+
**@description** (only one)
|
|
237
|
+
A detailed description of what the action does.
|
|
238
|
+
|
|
239
|
+
**@operationId** (only one)
|
|
240
|
+
An optional unique string used to identify an operation. If provided, these IDs must be unique among all operations described in your API..
|
|
241
|
+
|
|
242
|
+
**@responseBody** (multiple)
|
|
243
|
+
|
|
244
|
+
Format: `<status> - <return> - <description>`
|
|
245
|
+
|
|
246
|
+
`<return>` can be either a `<Schema>`, `<Schema[]>/` or a custom JSON `{}`
|
|
247
|
+
|
|
248
|
+
**@responseHeader** (multiple)
|
|
249
|
+
|
|
250
|
+
Format: `<status> - <name> - <description> - <meta>`
|
|
251
|
+
|
|
252
|
+
**@param`Type`** (multiple)
|
|
253
|
+
|
|
254
|
+
`Type` can be one of [Parameter Types](https://swagger.io/docs/specification/describing-parameters/) (first letter in uppercase)
|
|
255
|
+
|
|
256
|
+
**@requestBody** (only one)
|
|
257
|
+
A definition of the expected requestBody
|
|
258
|
+
|
|
259
|
+
Format: `<body>`
|
|
260
|
+
|
|
261
|
+
`<body>` can be either a `<Schema>`, `<Schema[]>/`, or a custom JSON `{}`
|
|
262
|
+
|
|
263
|
+
**@requestFormDataBody** (only one)
|
|
264
|
+
A definition of the expected requestBody that will be sent with formData format.
|
|
265
|
+
|
|
266
|
+
**Schema**
|
|
267
|
+
A model or a validator.
|
|
268
|
+
Format: `<Schema>`
|
|
269
|
+
|
|
270
|
+
**Custom format**
|
|
271
|
+
|
|
272
|
+
Format: `{"fieldname": {"type":"string", "format": "email"}}`
|
|
273
|
+
This format should be a valid openapi 3.x json.
|
|
274
|
+
|
|
275
|
+
---
|
|
276
|
+
|
|
277
|
+
# 🤘Examples
|
|
278
|
+
|
|
279
|
+
## `@responseBody` examples
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
@responseBody <status> - Lorem ipsum Dolor sit amet
|
|
283
|
+
|
|
284
|
+
@responseBody <status> // returns standard <status> message
|
|
285
|
+
|
|
286
|
+
@responseBody <status> - <Model> // returns model specification
|
|
287
|
+
|
|
288
|
+
@responseBody <status> - <Model[]> // returns model-array specification
|
|
289
|
+
|
|
290
|
+
@responseBody <status> - <Model>.with(relations, property1, property2.relations, property3.subproperty.relations) // returns a model and a defined relation
|
|
291
|
+
|
|
292
|
+
@responseBody <status> - <Model[]>.with(relations).exclude(property1, property2, property3.subproperty) // returns model specification
|
|
293
|
+
|
|
294
|
+
@responseBody <status> - <Model[]>.append("some":"valid json") // append additional properties to a Model
|
|
295
|
+
|
|
296
|
+
@responseBody <status> - <Model[]>.paginated() // helper function to return adonisJS conform structure like {"data": [], "meta": {}}
|
|
297
|
+
|
|
298
|
+
@responseBody <status> - <Model[]>.paginated(dataName, metaName) // returns a paginated model with custom keys for the data array and meta object, use `.paginated(dataName)` or `.paginated(,metaName)` if you want to override only one. Don't forget the ',' for the second parameter.
|
|
299
|
+
|
|
300
|
+
@responseBody <status> - <Model>.only(property1, property2) // pick only specific properties
|
|
301
|
+
|
|
302
|
+
@requestBody <status> <myCustomValidator> // returns a validator object
|
|
303
|
+
|
|
304
|
+
@responseBody <status> - {"foo": "bar", "baz": "<Model>"} //returns custom json object and also parses the model
|
|
305
|
+
@responseBody <status> - ["foo", "bar"] //returns custom json array
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## `@paramPath` and `@paramQuery` examples
|
|
309
|
+
|
|
310
|
+
```ts
|
|
311
|
+
// basicaly same as @response, just without a status
|
|
312
|
+
@paramPath <paramName> - Description - (meta)
|
|
313
|
+
@paramQuery <paramName> - Description - (meta)
|
|
314
|
+
|
|
315
|
+
@paramPath id - The ID of the source - @type(number) @required
|
|
316
|
+
@paramPath slug - The ID of the source - @type(string)
|
|
317
|
+
|
|
318
|
+
@paramQuery q - Search term - @type(string) @required
|
|
319
|
+
@paramQuery page - the Page number - @type(number)
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## `@requestBody` examples
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
// basicaly same as @response, just without a status
|
|
327
|
+
@requestBody <Model> // Expects model specification
|
|
328
|
+
@requestBody <myCustomValidator> // Expects validator specification
|
|
329
|
+
@requestBody <Model>.with(relations) // Expects model and its relations
|
|
330
|
+
@requestBody <Model[]>.append("some":"valid json") // append additional properties to a Model
|
|
331
|
+
@requestBody {"foo": "bar"} // Expects a specific JSON
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## `@requestFormDataBody` examples
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
// Providing a raw JSON
|
|
338
|
+
@requestFormDataBody {"name":{"type":"string"},"picture":{"type":"string","format":"binary"}} // Expects a valid OpenAPI 3.x JSON
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
// Providing a Model, and adding additional fields
|
|
343
|
+
@requestFormDataBody <Model> // Expects a valid OpenAPI 3.x JSON
|
|
344
|
+
@requestFormDataBody <Model>.exclude(property1).append("picture":{"type":"string","format":"binary"}) // Expects a valid OpenAPI 3.x JSON
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
# **Practical example**
|
|
350
|
+
|
|
351
|
+
`config/swagger.ts`
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
export default {
|
|
355
|
+
path: __dirname + "../",
|
|
356
|
+
title: "YourProject",
|
|
357
|
+
version: "1.0.0",
|
|
358
|
+
tagIndex: 2,
|
|
359
|
+
ignore: ["/swagger", "/docs", "/v1", "/", "/something/*", "*/something"],
|
|
360
|
+
common: {
|
|
361
|
+
parameters: {
|
|
362
|
+
sortable: [
|
|
363
|
+
{
|
|
364
|
+
in: "query",
|
|
365
|
+
name: "sortBy",
|
|
366
|
+
schema: { type: "string", example: "foo" },
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
in: "query",
|
|
370
|
+
name: "sortType",
|
|
371
|
+
schema: { type: "string", example: "ASC" },
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
headers: {
|
|
376
|
+
paginated: {
|
|
377
|
+
"X-Total-Pages": {
|
|
378
|
+
description: "Total amount of pages",
|
|
379
|
+
schema: { type: "integer", example: 5 },
|
|
380
|
+
},
|
|
381
|
+
"X-Total": {
|
|
382
|
+
description: "Total amount of results",
|
|
383
|
+
schema: { type: "integer", example: 100 },
|
|
384
|
+
},
|
|
385
|
+
"X-Per-Page": {
|
|
386
|
+
description: "Results per page",
|
|
387
|
+
schema: { type: "integer", example: 20 },
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
`app/Controllers/Http/SomeController.ts`
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
export default class SomeController {
|
|
399
|
+
/**
|
|
400
|
+
* @index
|
|
401
|
+
* @operationId getProducts
|
|
402
|
+
* @description Returns array of producs and it's relations
|
|
403
|
+
* @responseBody 200 - <Product[]>.with(relations)
|
|
404
|
+
* @paramUse(sortable, filterable)
|
|
405
|
+
* @responseHeader 200 - @use(paginated)
|
|
406
|
+
* @responseHeader 200 - X-pages - A description of the header - @example(test)
|
|
407
|
+
*/
|
|
408
|
+
public async index({ request, response }: HttpContextContract) {}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* @show
|
|
412
|
+
* @paramPath id - Describe the path param - @type(string) @required
|
|
413
|
+
* @paramQuery foo - Describe the query param - @type(string) @required
|
|
414
|
+
* @description Returns a product with it's relation on user and user relations
|
|
415
|
+
* @responseBody 200 - <Product>.with(user, user.relations)
|
|
416
|
+
* @responseBody 404
|
|
417
|
+
*/
|
|
418
|
+
public async show({ request, response }: HttpContextContract) {}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @update
|
|
422
|
+
* @responseBody 200
|
|
423
|
+
* @responseBody 404 - Product could not be found
|
|
424
|
+
* @requestBody <Product>
|
|
425
|
+
*/
|
|
426
|
+
public async update({ request, response }: HttpContextContract) {}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @myCustomFunction
|
|
430
|
+
* @summary Lorem ipsum dolor sit amet
|
|
431
|
+
* @paramPath provider - The login provider to be used - @enum(google, facebook, apple)
|
|
432
|
+
* @responseBody 200 - {"token": "xxxxxxx"}
|
|
433
|
+
* @requestBody {"code": "xxxxxx"}
|
|
434
|
+
*/
|
|
435
|
+
public async myCustomFunction({ request, response }: HttpContextContract) {}
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
---
|
|
440
|
+
|
|
441
|
+
## What does it do?
|
|
442
|
+
|
|
443
|
+
AutoSwagger tries to extracat as much information as possible to generate swagger-docs for you.
|
|
444
|
+
|
|
445
|
+
## Paths
|
|
446
|
+
|
|
447
|
+
Automatically generates swagger path-descriptions, based on your application routes. It also detects endpoints, protected by the auth-middlware.
|
|
448
|
+
|
|
449
|
+

|
|
450
|
+
|
|
451
|
+
### Responses and RequestBody
|
|
452
|
+
|
|
453
|
+
Generates responses and requestBody based on your simple Controller-Annotation (see Examples)
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## Schemas
|
|
458
|
+
|
|
459
|
+
### Models
|
|
460
|
+
|
|
461
|
+
Automatically generates swagger schema-descriptions based on your models
|
|
462
|
+
|
|
463
|
+

|
|
464
|
+
|
|
465
|
+
### Interfaces
|
|
466
|
+
|
|
467
|
+
Instead of using `param: any` you can now use custom interfaces `param: UserDetails`. The interfaces files need to be located at `app/Interfaces/`
|
|
468
|
+
|
|
469
|
+
### Enums
|
|
470
|
+
|
|
471
|
+
If you use enums in your models, AutoSwagger will detect them from `app/Types/` folder and add them to the schema.
|
|
472
|
+
If you want to add enum on ExampleValue, you can use `.append(enumFieldExample)`
|
|
473
|
+
|
|
474
|
+
Example:
|
|
475
|
+
|
|
476
|
+
```ts
|
|
477
|
+
@responseBody 200 - <Model>.with(relations).append(enumFieldExample)
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
## Extend Models
|
|
481
|
+
|
|
482
|
+
Add additional documentation to your Models properties.
|
|
483
|
+
|
|
484
|
+
### SoftDelete
|
|
485
|
+
|
|
486
|
+
Either use `compose(BaseModel, SoftDeletes)` or add a line `@swagger-softdeletes` to your Model.
|
|
487
|
+
|
|
488
|
+
## Attention
|
|
489
|
+
|
|
490
|
+
The below comments MUST be placed **1 line** above the property.
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
494
|
+
**@no-swagger**
|
|
495
|
+
Although, autoswagger detects `serializeAs: null` fields automatically, and does not show them. You can use @no-swagger for other fields.
|
|
496
|
+
|
|
497
|
+
**@enum(foo, bar)**
|
|
498
|
+
If a field has defined values, you can add them into an enum. This is usesfull for something like a status field.
|
|
499
|
+
|
|
500
|
+
**@format(string)**
|
|
501
|
+
Specify a format for that field, i.e. uuid, email, binary, etc...
|
|
502
|
+
|
|
503
|
+
**@example(foo bar)**
|
|
504
|
+
Use this field to provide own example values for specific fields
|
|
505
|
+
|
|
506
|
+
**@props({"minLength": 10, "foo": "bar"})**
|
|
507
|
+
Use this field to provide additional properties to a field, like minLength, maxLength, etc. Needs to bee valid JSON.
|
|
508
|
+
|
|
509
|
+
**@required**
|
|
510
|
+
Specify that the field is required
|
|
511
|
+
|
|
512
|
+
```ts
|
|
513
|
+
// SomeModel.js
|
|
514
|
+
@hasMany(() => ProductView)
|
|
515
|
+
// @no-swagger
|
|
516
|
+
public views: HasMany<typeof ProductView>
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
@column()
|
|
520
|
+
// @enum(pending, active, deleted)
|
|
521
|
+
public status: string
|
|
522
|
+
|
|
523
|
+
@column()
|
|
524
|
+
// @example(johndoe@example.com)
|
|
525
|
+
public email: string
|
|
526
|
+
|
|
527
|
+
@column()
|
|
528
|
+
// @props({"minLength": 10})
|
|
529
|
+
public age: number
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## Production environment
|
|
536
|
+
|
|
537
|
+
> [!WARNING]
|
|
538
|
+
> Make sure **NODE_ENV=production** in your production environment or whatever you set in `options.productionEnv`
|
|
539
|
+
|
|
540
|
+
To make it work in production environments, additional steps are required
|
|
541
|
+
|
|
542
|
+
- Create a new command for `docs:generate` [See official documentation](https://docs.adonisjs.com/guides/ace/creating-commands)
|
|
543
|
+
|
|
544
|
+
- This should create a new file in `commands/DocsGenerate.ts`
|
|
545
|
+
|
|
546
|
+
- Use the provided [`DocsGenerate.ts.examle`](https://github.com/necessarylion/adonis-autoswagger/blob/main/DocsGenerate.ts.example)/[`DocsGeneratev6.ts.example`](https://github.com/necessarylion/adonis-autoswagger/blob/main/DocsGeneratev6.ts.example) and put its contents into your newly created `DocsGenerate.ts`
|
|
547
|
+
|
|
548
|
+
- Modify `/start/env.ts` as follows
|
|
549
|
+
|
|
550
|
+
```ts
|
|
551
|
+
//...
|
|
552
|
+
// this is necessary to make sure that the `DocsGenerate` command will run in CI/CD pipelines without setting environment variables
|
|
553
|
+
const isNodeAce = process.argv.some(
|
|
554
|
+
(arg) => arg.endsWith("/ace") || arg === "ace"
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
export default await Env.create(
|
|
558
|
+
new URL("../", import.meta.url),
|
|
559
|
+
isNodeAce
|
|
560
|
+
? {}
|
|
561
|
+
: {
|
|
562
|
+
// leave other settings as is
|
|
563
|
+
NODE_ENV: Env.schema.enum([
|
|
564
|
+
"development",
|
|
565
|
+
"production",
|
|
566
|
+
"test",
|
|
567
|
+
] as const),
|
|
568
|
+
PORT: Env.schema.number(),
|
|
569
|
+
}
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
//...
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
- Execute the following
|
|
576
|
+
|
|
577
|
+
```bash
|
|
578
|
+
node ace docs:generate
|
|
579
|
+
node ace build --production
|
|
580
|
+
cp swagger.yml build/
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Known Issues
|
|
584
|
+
|
|
585
|
+
- Interfaces with objects are not working like `interface Test {foo: {bar: string}}`
|
|
586
|
+
- Solution, just extract the object as it's own interface
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function serializeV6Middleware(mw: any): string[];
|
|
2
|
+
export declare function serializeV6Handler(handler: any): Promise<any>;
|
|
3
|
+
export declare function parseBindingReference(binding: string | [any | any, any]): Promise<{
|
|
4
|
+
moduleNameOrPath: string;
|
|
5
|
+
method: string;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serializeV6Middleware = serializeV6Middleware;
|
|
4
|
+
exports.serializeV6Handler = serializeV6Handler;
|
|
5
|
+
exports.parseBindingReference = parseBindingReference;
|
|
6
|
+
function serializeV6Middleware(mw) {
|
|
7
|
+
return [...mw.all()].reduce((result, one) => {
|
|
8
|
+
if (typeof one === "function") {
|
|
9
|
+
result.push(one.name || "closure");
|
|
10
|
+
return result;
|
|
11
|
+
}
|
|
12
|
+
if ("name" in one && one.name) {
|
|
13
|
+
result.push(one.name);
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}, []);
|
|
17
|
+
}
|
|
18
|
+
async function serializeV6Handler(handler) {
|
|
19
|
+
/**
|
|
20
|
+
* Value is a controller reference
|
|
21
|
+
*/
|
|
22
|
+
if ("reference" in handler) {
|
|
23
|
+
return {
|
|
24
|
+
type: "controller",
|
|
25
|
+
...(await parseBindingReference(handler.reference)),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Value is an inline closure
|
|
30
|
+
*/
|
|
31
|
+
return {
|
|
32
|
+
type: "closure",
|
|
33
|
+
name: handler.name || "closure",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async function parseBindingReference(binding) {
|
|
37
|
+
const parseImports = (await import("parse-imports")).default;
|
|
38
|
+
/**
|
|
39
|
+
* The binding reference is a magic string. It might not have method
|
|
40
|
+
* name attached to it. Therefore we split the string and attempt
|
|
41
|
+
* to find the method or use the default method name "handle".
|
|
42
|
+
*/
|
|
43
|
+
if (typeof binding === "string") {
|
|
44
|
+
const tokens = binding.split(".");
|
|
45
|
+
if (tokens.length === 1) {
|
|
46
|
+
return { moduleNameOrPath: binding, method: "handle" };
|
|
47
|
+
}
|
|
48
|
+
return { method: tokens.pop(), moduleNameOrPath: tokens.join(".") };
|
|
49
|
+
}
|
|
50
|
+
const [bindingReference, method] = binding;
|
|
51
|
+
/**
|
|
52
|
+
* Parsing the binding reference for dynamic imports and using its
|
|
53
|
+
* import value.
|
|
54
|
+
*/
|
|
55
|
+
const imports = [...(await parseImports(bindingReference.toString()))];
|
|
56
|
+
const importedModule = imports.find(($import) => $import.isDynamicImport && $import.moduleSpecifier.value);
|
|
57
|
+
if (importedModule) {
|
|
58
|
+
return {
|
|
59
|
+
moduleNameOrPath: importedModule.moduleSpecifier.value,
|
|
60
|
+
method: method || "handle",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Otherwise using the name of the binding reference.
|
|
65
|
+
*/
|
|
66
|
+
return {
|
|
67
|
+
moduleNameOrPath: bindingReference.name,
|
|
68
|
+
method: method || "handle",
|
|
69
|
+
};
|
|
70
|
+
}
|