@trojs/openapi-server 3.0.0 → 3.1.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.
- package/package.json +11 -28
- package/src/api.js +63 -63
- package/src/error-status.js +14 -14
- package/src/express-callback.js +59 -59
- package/src/handlers/not-found.js +6 -6
- package/src/handlers/request-validation.js +7 -7
- package/src/handlers/response-validation.js +24 -24
- package/src/handlers/unauthorized.js +6 -6
- package/src/openapi.js +3 -3
- package/src/operation-ids.js +7 -7
- package/src/params.js +33 -33
- package/src/router.js +60 -56
- package/src/server.js +50 -50
- package/src/types.js +8 -8
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trojs/openapi-server",
|
|
3
3
|
"description": "OpenAPI Server",
|
|
4
|
-
"version": "3.
|
|
4
|
+
"version": "3.1.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pieter Wigboldus",
|
|
7
7
|
"url": "https://trojs.org/"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"lint": "eslint",
|
|
12
12
|
"lint:report": "eslint src/*.js -f json -o report.json",
|
|
13
13
|
"lint:fix": "eslint --fix",
|
|
14
|
-
"test": "
|
|
14
|
+
"test": "node --test --experimental-test-coverage --test-reporter=spec --test-reporter=lcov --test-reporter-destination=stdout --test-reporter-destination=./coverage/lcov.info",
|
|
15
15
|
"cpd": "node_modules/jscpd/bin/jscpd src",
|
|
16
16
|
"vulnerabilities": "npm audit --omit=dev"
|
|
17
17
|
},
|
|
@@ -33,22 +33,12 @@
|
|
|
33
33
|
],
|
|
34
34
|
"main": "src/server.js",
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@
|
|
37
|
-
"@stylistic/eslint-plugin": "^2.11.0",
|
|
38
|
-
"@stylistic/eslint-plugin-js": "^2.11.0",
|
|
39
|
-
"@types/express-serve-static-core": "^4.17.41",
|
|
36
|
+
"@trojs/lint": "^0.2.9",
|
|
40
37
|
"@types/node": "^22.0.0",
|
|
41
|
-
"
|
|
38
|
+
"@types/express-serve-static-core": "^5.0.0",
|
|
42
39
|
"eslint": "^9.15.0",
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"eslint-plugin-jsdoc": "^50.5.0",
|
|
46
|
-
"eslint-plugin-n": "^17.14.0",
|
|
47
|
-
"eslint-plugin-prettier": "^5.2.1",
|
|
48
|
-
"eslint-plugin-promise": "^7.1.0",
|
|
49
|
-
"eslint-plugin-sonarjs": "^2.0.4",
|
|
50
|
-
"globals": "^15.12.0",
|
|
51
|
-
"jscpd": "^4.0.0",
|
|
40
|
+
"globals": "^16.0.0",
|
|
41
|
+
"jscpd": "^4.0.5",
|
|
52
42
|
"prettier": "^3.3.3",
|
|
53
43
|
"supertest": "^7.0.0"
|
|
54
44
|
},
|
|
@@ -65,12 +55,12 @@
|
|
|
65
55
|
"express"
|
|
66
56
|
],
|
|
67
57
|
"dependencies": {
|
|
68
|
-
"@sentry/node": "^
|
|
58
|
+
"@sentry/node": "^9.0.0",
|
|
69
59
|
"ajv-formats": "^3.0.0",
|
|
70
60
|
"body-parser": "^2.0.0",
|
|
71
61
|
"compression": "^1.7.4",
|
|
72
62
|
"cors": "^2.8.5",
|
|
73
|
-
"express": "^
|
|
63
|
+
"express": "^5.0.0",
|
|
74
64
|
"helmet": "^8.0.0",
|
|
75
65
|
"openapi-backend": "^5.9.2",
|
|
76
66
|
"swagger-ui-express": "^5.0.0"
|
|
@@ -80,15 +70,8 @@
|
|
|
80
70
|
"url": "https://github.com/sponsors/w3nl"
|
|
81
71
|
},
|
|
82
72
|
"overrides": {
|
|
83
|
-
"semver": "^7.5.3",
|
|
73
|
+
"semver@<=7.5.3": "^7.5.3",
|
|
84
74
|
"send@<=0.19.0": "^0.19.0",
|
|
85
|
-
"cookie@<=0.7.0": "0.7.0"
|
|
86
|
-
"eslint-plugin-sonarjs": {
|
|
87
|
-
"eslint": "^9.15.0",
|
|
88
|
-
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
89
|
-
"@typescript-eslint/utils": "^8.0.0",
|
|
90
|
-
"eslint-plugin-import": "^2.31.0",
|
|
91
|
-
"eslint-plugin-react-hooks": "^5.0.0"
|
|
92
|
-
}
|
|
75
|
+
"cookie@<=0.7.0": "0.7.0"
|
|
93
76
|
}
|
|
94
|
-
}
|
|
77
|
+
}
|
package/src/api.js
CHANGED
|
@@ -34,72 +34,72 @@ import { setupRouter } from './router.js'
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
export class Api {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Create a new instance of the API
|
|
39
|
+
* @class
|
|
40
|
+
* @param {ApiSchema} params
|
|
41
|
+
*/
|
|
42
|
+
constructor ({
|
|
43
|
+
version,
|
|
44
|
+
specification,
|
|
45
|
+
controllers,
|
|
46
|
+
apiRoot,
|
|
47
|
+
strictSpecification,
|
|
48
|
+
errorDetails,
|
|
49
|
+
logger,
|
|
50
|
+
meta,
|
|
51
|
+
securityHandlers,
|
|
52
|
+
swagger,
|
|
53
|
+
apiDocs,
|
|
54
|
+
ajvOptions
|
|
55
|
+
}) {
|
|
56
|
+
this.version = version
|
|
57
|
+
this.specification = specification
|
|
58
|
+
this.controllers = controllers
|
|
59
|
+
this.apiRoot = apiRoot
|
|
60
|
+
this.strictSpecification = strictSpecification
|
|
61
|
+
this.errorDetails = errorDetails || false
|
|
62
|
+
this.logger = logger || console
|
|
63
|
+
this.meta = meta || {}
|
|
64
|
+
this.securityHandlers = securityHandlers || []
|
|
65
|
+
this.swagger = swagger ?? true
|
|
66
|
+
this.apiDocs = apiDocs ?? true
|
|
67
|
+
this.ajvOptions = ajvOptions ?? { allErrors: false }
|
|
68
|
+
}
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
setup () {
|
|
71
|
+
const router = express.Router()
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
73
|
+
if (this.swagger) {
|
|
74
|
+
router.use(
|
|
75
|
+
'/swagger',
|
|
76
|
+
swaggerUi.serveFiles(this.specification, {}),
|
|
77
|
+
swaggerUi.setup(this.specification)
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
if (this.apiDocs) {
|
|
81
|
+
router.get('/api-docs', (_request, response) =>
|
|
82
|
+
response.json(this.specification)
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
86
|
+
const { api } = setupRouter({
|
|
87
|
+
openAPISpecification: this.specification,
|
|
88
|
+
controllers: this.controllers,
|
|
89
|
+
apiRoot: this.apiRoot,
|
|
90
|
+
strictSpecification: this.strictSpecification,
|
|
91
|
+
errorDetails: this.errorDetails,
|
|
92
|
+
logger: this.logger,
|
|
93
|
+
meta: this.meta,
|
|
94
|
+
securityHandlers: this.securityHandlers,
|
|
95
|
+
ajvOptions: this.ajvOptions
|
|
96
|
+
})
|
|
97
|
+
api.init()
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
router.use((request, response) =>
|
|
100
|
+
api.handleRequest(request, request, response)
|
|
101
|
+
)
|
|
102
102
|
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
return router
|
|
104
|
+
}
|
|
105
105
|
}
|
package/src/error-status.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
const errorCodesStatus = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
2
|
+
{
|
|
3
|
+
type: TypeError,
|
|
4
|
+
status: 422
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
type: RangeError,
|
|
8
|
+
status: 404
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
type: Error,
|
|
12
|
+
status: 500
|
|
13
|
+
}
|
|
14
14
|
]
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -20,5 +20,5 @@ const errorCodesStatus = [
|
|
|
20
20
|
* @returns {number}
|
|
21
21
|
*/
|
|
22
22
|
export default (error) =>
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
errorCodesStatus.find((errorCode) => error instanceof errorCode.type)
|
|
24
|
+
.status
|
package/src/express-callback.js
CHANGED
|
@@ -19,8 +19,8 @@ import { parseParams } from './params.js'
|
|
|
19
19
|
* @param {boolean=} params.mock
|
|
20
20
|
* @returns {Function}
|
|
21
21
|
*/
|
|
22
|
-
export const makeExpressCallback
|
|
23
|
-
({ controller, specification, errorDetails, logger, meta, mock }) =>
|
|
22
|
+
export const makeExpressCallback
|
|
23
|
+
= ({ controller, specification, errorDetails, logger, meta, mock }) =>
|
|
24
24
|
/**
|
|
25
25
|
* Handle controller
|
|
26
26
|
* @async
|
|
@@ -29,68 +29,68 @@ export const makeExpressCallback =
|
|
|
29
29
|
* @param {Response} response
|
|
30
30
|
* @returns {Promise<any>}
|
|
31
31
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
async (context, request, response) => {
|
|
33
|
+
try {
|
|
34
|
+
const allParameters = {
|
|
35
|
+
...(context.request?.params || {}),
|
|
36
|
+
...(context.request?.query || {})
|
|
37
|
+
}
|
|
38
|
+
const parameters = parseParams({
|
|
39
|
+
query: allParameters,
|
|
40
|
+
spec: context.operation.parameters,
|
|
41
|
+
mock
|
|
42
|
+
})
|
|
43
|
+
const url = `${request.protocol}://${request.get('Host')}${request.originalUrl}`
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
const responseBody = await controller({
|
|
46
|
+
context,
|
|
47
|
+
request,
|
|
48
|
+
response,
|
|
49
|
+
parameters,
|
|
50
|
+
specification,
|
|
51
|
+
post: request.body,
|
|
52
|
+
url,
|
|
53
|
+
logger,
|
|
54
|
+
meta
|
|
55
|
+
})
|
|
56
|
+
logger.debug({
|
|
57
|
+
url,
|
|
58
|
+
parameters,
|
|
59
|
+
post: request.body,
|
|
60
|
+
response: responseBody
|
|
61
|
+
})
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
return responseBody
|
|
64
|
+
} catch (error) {
|
|
65
|
+
const errorCodeStatus = getStatusByError(error)
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
if (errorCodeStatus >= 500) {
|
|
68
|
+
logger.error(error)
|
|
69
|
+
} else {
|
|
70
|
+
logger.warn(error)
|
|
71
|
+
}
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
response.status(errorCodeStatus)
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
],
|
|
84
|
-
status: errorCodeStatus,
|
|
85
|
-
timestamp: new Date(),
|
|
86
|
-
message: error.message
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return {
|
|
91
|
-
status: errorCodeStatus,
|
|
92
|
-
timestamp: new Date(),
|
|
93
|
-
message: error.message
|
|
75
|
+
if (errorDetails) {
|
|
76
|
+
return {
|
|
77
|
+
errors: [
|
|
78
|
+
{
|
|
79
|
+
message: error.message,
|
|
80
|
+
value: error.valueOf(),
|
|
81
|
+
type: error.constructor.name
|
|
94
82
|
}
|
|
83
|
+
],
|
|
84
|
+
status: errorCodeStatus,
|
|
85
|
+
timestamp: new Date(),
|
|
86
|
+
message: error.message
|
|
95
87
|
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
status: errorCodeStatus,
|
|
92
|
+
timestamp: new Date(),
|
|
93
|
+
message: error.message
|
|
94
|
+
}
|
|
96
95
|
}
|
|
96
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const notFound = (_context, request, response) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
response.status(404)
|
|
3
|
+
return {
|
|
4
|
+
status: 404,
|
|
5
|
+
timestamp: new Date(),
|
|
6
|
+
message: 'Not found'
|
|
7
|
+
}
|
|
8
8
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const requestValidation = (context, request, response) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
response.status(400)
|
|
3
|
+
return {
|
|
4
|
+
errors: context.validation.errors,
|
|
5
|
+
status: 400,
|
|
6
|
+
timestamp: new Date(),
|
|
7
|
+
message: 'Bad Request'
|
|
8
|
+
}
|
|
9
9
|
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
export const responseValidation = (context, request, response) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
const responseDoesntNeedValidation = response.statusCode >= 400
|
|
3
|
+
if (responseDoesntNeedValidation) {
|
|
4
|
+
return response.json(context.response)
|
|
5
|
+
}
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
const valid = context.api.validateResponse(
|
|
8
|
+
context.response,
|
|
9
|
+
context.operation
|
|
10
|
+
)
|
|
11
|
+
if (valid?.errors) {
|
|
12
|
+
return response.status(502).json({
|
|
13
|
+
errors: valid.errors,
|
|
14
|
+
status: 502,
|
|
15
|
+
timestamp: new Date(),
|
|
16
|
+
message: 'Bad response'
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
if (!context.response) {
|
|
21
|
+
return response.end()
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
const contentType = request?.headers?.accept ?? 'application/json'
|
|
25
|
+
if (contentType === 'application/json') {
|
|
26
|
+
return response.json(context.response)
|
|
27
|
+
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
return response.send(context.response)
|
|
30
30
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const unauthorized = async (context, request, response) => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
response.status(401)
|
|
3
|
+
return {
|
|
4
|
+
status: 401,
|
|
5
|
+
timestamp: new Date(),
|
|
6
|
+
message: 'Unauthorized'
|
|
7
|
+
}
|
|
8
8
|
}
|
package/src/openapi.js
CHANGED
|
@@ -9,7 +9,7 @@ import { readFile } from 'node:fs/promises'
|
|
|
9
9
|
* @returns {Promise<{ openAPISpecification: object; }>}
|
|
10
10
|
*/
|
|
11
11
|
export const openAPI = async ({ file, base = import.meta.url }) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
const fileUrl = new URL(file, base)
|
|
13
|
+
const openAPISpecification = JSON.parse(await readFile(fileUrl, 'utf8'))
|
|
14
|
+
return { openAPISpecification }
|
|
15
15
|
}
|
package/src/operation-ids.js
CHANGED
|
@@ -7,10 +7,10 @@ const operations = ['get', 'put', 'patch', 'post', 'delete']
|
|
|
7
7
|
* @returns {string[]}
|
|
8
8
|
*/
|
|
9
9
|
export const operationIds = ({ specification }) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
Object.values(specification.paths)
|
|
11
|
+
.map((path) =>
|
|
12
|
+
Object.entries(path).map(([operation, data]) =>
|
|
13
|
+
operations.includes(operation) ? data.operationId : null
|
|
14
|
+
)
|
|
15
|
+
)
|
|
16
|
+
.flat()
|
package/src/params.js
CHANGED
|
@@ -9,41 +9,41 @@ import { types } from './types.js'
|
|
|
9
9
|
* @returns {object}
|
|
10
10
|
*/
|
|
11
11
|
export const parseParams = ({ query, spec, mock = false }) =>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
spec
|
|
13
|
+
.map((parameter) => {
|
|
14
|
+
const { name, schema } = parameter
|
|
15
|
+
const {
|
|
16
|
+
type,
|
|
17
|
+
default: defaultValue,
|
|
18
|
+
example: exampleValue
|
|
19
|
+
} = schema
|
|
20
|
+
const Type = types[type]
|
|
21
|
+
const paramName = query?.[name]
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
if (!paramName && defaultValue !== undefined) {
|
|
24
|
+
return { name, value: defaultValue }
|
|
25
|
+
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (!paramName && mock && exampleValue !== undefined) {
|
|
28
|
+
return { name, value: exampleValue }
|
|
29
|
+
}
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
if (!paramName) {
|
|
32
|
+
return undefined
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
if (Type === Boolean) {
|
|
36
|
+
return {
|
|
37
|
+
name,
|
|
38
|
+
value: JSON.parse(paramName.toLowerCase())
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
const value = new Type(paramName).valueOf()
|
|
43
|
+
return { name, value }
|
|
44
|
+
})
|
|
45
|
+
.filter(Boolean)
|
|
46
|
+
.reduce((acc, { name, value }) => {
|
|
47
|
+
acc[name] = value
|
|
48
|
+
return acc
|
|
49
|
+
}, {})
|
package/src/router.js
CHANGED
|
@@ -11,6 +11,7 @@ import { unauthorized } from './handlers/unauthorized.js'
|
|
|
11
11
|
* @typedef {import('./api.js').Logger} Logger
|
|
12
12
|
* @typedef {import('./api.js').SecurityHandler} SecurityHandler
|
|
13
13
|
* @typedef {import('ajv').Options} AjvOpts
|
|
14
|
+
* @typedef {import('openapi-backend').AjvCustomizer} AjvCustomizer
|
|
14
15
|
*/
|
|
15
16
|
|
|
16
17
|
/**
|
|
@@ -25,70 +26,73 @@ import { unauthorized } from './handlers/unauthorized.js'
|
|
|
25
26
|
* @param {object=} params.meta
|
|
26
27
|
* @param {SecurityHandler[]=} params.securityHandlers
|
|
27
28
|
* @param {AjvOpts=} params.ajvOptions
|
|
29
|
+
* @param {AjvCustomizer=} params.customizeAjv
|
|
28
30
|
* @param {boolean=} params.mock
|
|
29
31
|
* @returns {{ api: OpenAPIBackend<any>, openAPISpecification: object }}
|
|
30
32
|
*/
|
|
31
33
|
export const setupRouter = ({
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
openAPISpecification,
|
|
35
|
+
controllers,
|
|
36
|
+
apiRoot,
|
|
37
|
+
strictSpecification,
|
|
38
|
+
errorDetails,
|
|
39
|
+
logger,
|
|
40
|
+
meta,
|
|
41
|
+
securityHandlers = [],
|
|
42
|
+
ajvOptions = {},
|
|
43
|
+
customizeAjv,
|
|
44
|
+
mock
|
|
42
45
|
}) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
const ajvWithExtraFormats = (originalAjv) => {
|
|
47
|
+
addFormats(originalAjv)
|
|
48
|
+
return originalAjv
|
|
49
|
+
}
|
|
50
|
+
const api = new OpenAPIBackend({
|
|
51
|
+
definition: openAPISpecification,
|
|
52
|
+
apiRoot,
|
|
53
|
+
strict: strictSpecification,
|
|
54
|
+
ajvOpts: ajvOptions,
|
|
55
|
+
customizeAjv: customizeAjv || ajvWithExtraFormats
|
|
56
|
+
})
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
api.register({
|
|
59
|
+
unauthorizedHandler: unauthorized,
|
|
60
|
+
validationFail: requestValidation,
|
|
61
|
+
notFound,
|
|
62
|
+
postResponseHandler: responseValidation
|
|
63
|
+
})
|
|
60
64
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
65
|
+
operationIds({ specification: openAPISpecification }).forEach(
|
|
66
|
+
(operationId) => {
|
|
67
|
+
if (!Object.hasOwn(controllers, operationId)) {
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
api.register(
|
|
71
|
+
operationId,
|
|
72
|
+
makeExpressCallback({
|
|
73
|
+
controller: controllers[operationId],
|
|
74
|
+
specification: openAPISpecification,
|
|
75
|
+
errorDetails,
|
|
76
|
+
logger,
|
|
77
|
+
meta,
|
|
78
|
+
mock
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
79
83
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
context.api.mockResponseForOperation(context.operation.operationId)
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
api.register('notImplemented', (context) => {
|
|
85
|
+
const { mock: mockImplementation }
|
|
86
|
+
= context.api.mockResponseForOperation(context.operation.operationId)
|
|
87
|
+
return mockImplementation
|
|
88
|
+
})
|
|
85
89
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
securityHandlers.forEach((securityHandler) => {
|
|
91
|
+
api.registerSecurityHandler(
|
|
92
|
+
securityHandler.name,
|
|
93
|
+
securityHandler.handler
|
|
94
|
+
)
|
|
95
|
+
})
|
|
92
96
|
|
|
93
|
-
|
|
97
|
+
return { api, openAPISpecification }
|
|
94
98
|
}
|
package/src/server.js
CHANGED
|
@@ -15,13 +15,13 @@ import { Api } from './api.js'
|
|
|
15
15
|
* @returns {{ crossOriginResourcePolicy: { policy: string, directives: object } }}
|
|
16
16
|
*/
|
|
17
17
|
const getOriginResourcePolicy = (origin) => ({
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
18
|
+
crossOriginResourcePolicy: {
|
|
19
|
+
policy: origin === '*' ? 'cross-origin' : 'same-origin',
|
|
20
|
+
directives: {
|
|
21
|
+
// ...
|
|
22
|
+
'require-trusted-types-for': ['\'script\'']
|
|
24
23
|
}
|
|
24
|
+
}
|
|
25
25
|
})
|
|
26
26
|
|
|
27
27
|
/**
|
|
@@ -72,58 +72,58 @@ const getOriginResourcePolicy = (origin) => ({
|
|
|
72
72
|
* @returns {Promise<{ app: Express }>}
|
|
73
73
|
*/
|
|
74
74
|
export const setupServer = async ({
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
apis,
|
|
76
|
+
origin = '*',
|
|
77
|
+
staticFolder,
|
|
78
|
+
sentry,
|
|
79
|
+
poweredBy = 'TroJS',
|
|
80
|
+
version = '1.0.0',
|
|
81
|
+
middleware = [],
|
|
82
|
+
maximumBodySize = undefined
|
|
83
83
|
}) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const app = express()
|
|
84
|
+
const corsOptions = {
|
|
85
|
+
origin
|
|
86
|
+
}
|
|
89
87
|
|
|
90
|
-
|
|
91
|
-
Sentry.init({
|
|
92
|
-
dsn: sentry.dsn,
|
|
93
|
-
tracesSampleRate: sentry.tracesSampleRate || 1.0,
|
|
94
|
-
profilesSampleRate: sentry.profilesSampleRate || 1.0,
|
|
95
|
-
integrations: sentry.integrations || [],
|
|
96
|
-
release: sentry.release
|
|
97
|
-
})
|
|
98
|
-
}
|
|
88
|
+
const app = express()
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
response.setHeader('X-Powered-By', poweredBy)
|
|
108
|
-
response.setHeader('X-Version', version)
|
|
109
|
-
next()
|
|
90
|
+
if (sentry) {
|
|
91
|
+
Sentry.init({
|
|
92
|
+
dsn: sentry.dsn,
|
|
93
|
+
tracesSampleRate: sentry.tracesSampleRate || 1.0,
|
|
94
|
+
profilesSampleRate: sentry.profilesSampleRate || 1.0,
|
|
95
|
+
integrations: sentry.integrations || [],
|
|
96
|
+
release: sentry.release
|
|
110
97
|
})
|
|
98
|
+
}
|
|
111
99
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
app.use(cors(corsOptions))
|
|
101
|
+
app.use(compression())
|
|
102
|
+
app.use(helmet(getOriginResourcePolicy(origin)))
|
|
103
|
+
app.use(express.json({ limit: maximumBodySize }))
|
|
104
|
+
middleware.forEach((fn) => app.use(fn))
|
|
105
|
+
app.use(bodyParser.urlencoded({ extended: false, limit: maximumBodySize }))
|
|
106
|
+
app.use((_request, response, next) => {
|
|
107
|
+
response.setHeader('X-Powered-By', poweredBy)
|
|
108
|
+
response.setHeader('X-Version', version)
|
|
109
|
+
next()
|
|
110
|
+
})
|
|
115
111
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
app.use(`/${api.version}`, routes)
|
|
120
|
-
})
|
|
112
|
+
if (staticFolder) {
|
|
113
|
+
app.use(express.static(staticFolder))
|
|
114
|
+
}
|
|
121
115
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
116
|
+
apis.forEach((api) => {
|
|
117
|
+
const apiRoutes = new Api(api)
|
|
118
|
+
const routes = apiRoutes.setup()
|
|
119
|
+
app.use(`/${api.version}`, routes)
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
if (sentry) {
|
|
123
|
+
Sentry.setupExpressErrorHandler(app)
|
|
124
|
+
}
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
return { app }
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
export { openAPI, Api }
|
package/src/types.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const types = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
string: String,
|
|
3
|
+
array: Array,
|
|
4
|
+
object: Object,
|
|
5
|
+
number: Number,
|
|
6
|
+
integer: Number,
|
|
7
|
+
boolean: Boolean,
|
|
8
|
+
url: URL,
|
|
9
|
+
date: Date
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export { types }
|