@trojs/openapi-server 1.2.1 → 1.2.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 -5
- package/src/api.js +70 -50
- package/src/error-status.js +15 -15
- package/src/express-callback.js +74 -75
- package/src/openapi.js +5 -5
- package/src/operation-ids.js +9 -7
- package/src/params.js +27 -22
- package/src/router.js +76 -57
- package/src/server.js +75 -60
- package/src/types.js +10 -10
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trojs/openapi-server",
|
|
3
3
|
"description": "OpenAPI Server",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.3",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Pieter Wigboldus",
|
|
7
7
|
"url": "https://trojs.org/"
|
|
@@ -33,18 +33,24 @@
|
|
|
33
33
|
],
|
|
34
34
|
"main": "src/server.js",
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@hckrnews/eslint-
|
|
36
|
+
"@hckrnews/eslint-code-quality": "^0.2.1",
|
|
37
|
+
"@hckrnews/eslint-config": "^3.1.0",
|
|
37
38
|
"@types/express-serve-static-core": "^4.17.41",
|
|
38
39
|
"@types/node": "^20.14.9",
|
|
39
40
|
"c8": "^10.0.0",
|
|
40
41
|
"eslint": "^8.23.0",
|
|
41
|
-
"eslint-config-
|
|
42
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
43
|
+
"eslint-config-prettier": "^9.1.0",
|
|
44
|
+
"eslint-plugin-html": "^8.1.1",
|
|
42
45
|
"eslint-plugin-import": "^2.26.0",
|
|
43
46
|
"eslint-plugin-jsdoc": "^48.0.0",
|
|
44
|
-
"eslint-plugin-
|
|
45
|
-
"eslint-plugin-
|
|
47
|
+
"eslint-plugin-jsx-a11y": "^6.9.0",
|
|
48
|
+
"eslint-plugin-n": "^17.0.0",
|
|
49
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
50
|
+
"eslint-plugin-promise": "^7.0.0",
|
|
46
51
|
"eslint-plugin-sonarjs": "^0.25.1",
|
|
47
52
|
"jscpd": "^4.0.0",
|
|
53
|
+
"prettier": "^3.3.3",
|
|
48
54
|
"supertest": "^7.0.0"
|
|
49
55
|
},
|
|
50
56
|
"repository": {
|
package/src/api.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import express from 'express'
|
|
2
|
-
import swaggerUi from 'swagger-ui-express'
|
|
3
|
-
import { setupRouter } from './router.js'
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import swaggerUi from 'swagger-ui-express';
|
|
3
|
+
import { setupRouter } from './router.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {import('openapi-backend').Handler} Handler
|
|
@@ -31,58 +31,78 @@ import { setupRouter } from './router.js'
|
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
33
|
* Setup the server for a specific API, so every server can run multiple instances of the API, like different versions, for e.g. different clients
|
|
34
|
-
* @class
|
|
35
34
|
*/
|
|
35
|
+
|
|
36
36
|
export class Api {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Create a new instance of the API
|
|
39
|
+
* @constructor
|
|
40
|
+
* @param {ApiSchema} params
|
|
41
|
+
*/
|
|
42
|
+
constructor({
|
|
43
|
+
version,
|
|
44
|
+
specification,
|
|
45
|
+
controllers,
|
|
46
|
+
secret,
|
|
47
|
+
apiRoot,
|
|
48
|
+
strictSpecification,
|
|
49
|
+
errorDetails,
|
|
50
|
+
logger,
|
|
51
|
+
meta,
|
|
52
|
+
securityHandlers,
|
|
53
|
+
swagger,
|
|
54
|
+
apiDocs,
|
|
55
|
+
ajvOptions,
|
|
56
|
+
}) {
|
|
57
|
+
this.version = version;
|
|
58
|
+
this.specification = specification;
|
|
59
|
+
this.controllers = controllers;
|
|
60
|
+
this.secret = secret;
|
|
61
|
+
this.apiRoot = apiRoot;
|
|
62
|
+
this.strictSpecification = strictSpecification;
|
|
63
|
+
this.errorDetails = errorDetails || false;
|
|
64
|
+
this.logger = logger || console;
|
|
65
|
+
this.meta = meta || {};
|
|
66
|
+
this.securityHandlers = securityHandlers || [];
|
|
67
|
+
this.swagger = swagger ?? true;
|
|
68
|
+
this.apiDocs = apiDocs ?? true;
|
|
69
|
+
this.ajvOptions = ajvOptions ?? { allErrors: false };
|
|
70
|
+
}
|
|
55
71
|
|
|
56
|
-
|
|
57
|
-
|
|
72
|
+
setup() {
|
|
73
|
+
const router = express.Router();
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
if (this.swagger) {
|
|
76
|
+
router.use(
|
|
77
|
+
'/swagger',
|
|
78
|
+
swaggerUi.serveFiles(this.specification, {}),
|
|
79
|
+
swaggerUi.setup(this.specification)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
if (this.apiDocs) {
|
|
83
|
+
router.get('/api-docs', (_request, response) =>
|
|
84
|
+
response.json(this.specification)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
67
87
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
const { api } = setupRouter({
|
|
89
|
+
secret: this.secret,
|
|
90
|
+
openAPISpecification: this.specification,
|
|
91
|
+
controllers: this.controllers,
|
|
92
|
+
apiRoot: this.apiRoot,
|
|
93
|
+
strictSpecification: this.strictSpecification,
|
|
94
|
+
errorDetails: this.errorDetails,
|
|
95
|
+
logger: this.logger,
|
|
96
|
+
meta: this.meta,
|
|
97
|
+
securityHandlers: this.securityHandlers,
|
|
98
|
+
ajvOptions: this.ajvOptions,
|
|
99
|
+
});
|
|
100
|
+
api.init();
|
|
81
101
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
102
|
+
router.use((request, response) =>
|
|
103
|
+
api.handleRequest(request, request, response)
|
|
104
|
+
);
|
|
85
105
|
|
|
86
|
-
|
|
87
|
-
|
|
106
|
+
return router;
|
|
107
|
+
}
|
|
88
108
|
}
|
package/src/error-status.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
const errorCodesStatus = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
]
|
|
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
|
+
];
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Get a http status when you send an error.
|
|
@@ -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
|
@@ -1,95 +1,94 @@
|
|
|
1
|
-
import getStatusByError from './error-status.js'
|
|
2
|
-
import { parseParams } from './params.js'
|
|
1
|
+
import getStatusByError from './error-status.js';
|
|
2
|
+
import { parseParams } from './params.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @typedef {import('express-serve-static-core').Request} Request
|
|
6
6
|
* @typedef {import('express-serve-static-core').Response} Response
|
|
7
7
|
* @typedef {import('openapi-backend').Context} Context
|
|
8
8
|
* @typedef {import('./api.js').Logger} Logger
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Make an express callback for the controller
|
|
9
13
|
* @param {object} params
|
|
10
14
|
* @param {Function} params.controller
|
|
11
15
|
* @param {object} params.specification
|
|
12
16
|
* @param {boolean=} params.errorDetails
|
|
13
17
|
* @param {Logger=} params.logger
|
|
14
18
|
* @param {object=} params.meta
|
|
15
|
-
* @returns {
|
|
16
|
-
*/
|
|
17
|
-
export const makeExpressCallback = ({
|
|
18
|
-
controller,
|
|
19
|
-
specification,
|
|
20
|
-
errorDetails,
|
|
21
|
-
logger,
|
|
22
|
-
meta
|
|
23
|
-
}) =>
|
|
24
|
-
/**
|
|
25
|
-
* Handle controller
|
|
26
|
-
* @async
|
|
27
|
-
* @param {Context} context
|
|
28
|
-
* @param {Request} request
|
|
29
|
-
* @param {Response} response
|
|
30
|
-
* @returns {Promise<any>}
|
|
19
|
+
* @returns {Function}
|
|
31
20
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
21
|
+
export const makeExpressCallback =
|
|
22
|
+
({ controller, specification, errorDetails, logger, meta }) =>
|
|
23
|
+
/**
|
|
24
|
+
* Handle controller
|
|
25
|
+
* @async
|
|
26
|
+
* @param {Context} context
|
|
27
|
+
* @param {Request} request
|
|
28
|
+
* @param {Response} response
|
|
29
|
+
* @returns {Promise<any>}
|
|
30
|
+
*/
|
|
31
|
+
async (context, request, response) => {
|
|
32
|
+
try {
|
|
33
|
+
const allParameters = {
|
|
34
|
+
...(context.request?.params || {}),
|
|
35
|
+
...(context.request?.query || {}),
|
|
36
|
+
};
|
|
37
|
+
const parameters = parseParams({
|
|
38
|
+
query: allParameters,
|
|
39
|
+
spec: context.operation.parameters,
|
|
40
|
+
});
|
|
41
|
+
const url = `${request.protocol}://${request.get('Host')}${request.originalUrl}`;
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
43
|
+
const responseBody = await controller({
|
|
44
|
+
context,
|
|
45
|
+
request,
|
|
46
|
+
response,
|
|
47
|
+
parameters,
|
|
48
|
+
specification,
|
|
49
|
+
post: request.body,
|
|
50
|
+
url,
|
|
51
|
+
logger,
|
|
52
|
+
meta,
|
|
53
|
+
});
|
|
54
|
+
logger.debug({
|
|
55
|
+
url,
|
|
56
|
+
parameters,
|
|
57
|
+
post: request.body,
|
|
58
|
+
response: responseBody,
|
|
59
|
+
});
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
return responseBody;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const errorCodeStatus = getStatusByError(error);
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
if (errorCodeStatus >= 500) {
|
|
66
|
+
logger.error(error);
|
|
67
|
+
} else {
|
|
68
|
+
logger.warn(error);
|
|
69
|
+
}
|
|
71
70
|
|
|
72
|
-
|
|
71
|
+
response.status(errorCodeStatus);
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
if (errorDetails) {
|
|
74
|
+
return {
|
|
75
|
+
errors: [
|
|
76
|
+
{
|
|
77
|
+
message: error.message,
|
|
78
|
+
value: error.valueOf(),
|
|
79
|
+
type: error.constructor.name,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
status: errorCodeStatus,
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
message: error.message,
|
|
85
|
+
};
|
|
81
86
|
}
|
|
82
|
-
],
|
|
83
|
-
status: errorCodeStatus,
|
|
84
|
-
timestamp: new Date(),
|
|
85
|
-
message: error.message
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
88
|
+
return {
|
|
89
|
+
status: errorCodeStatus,
|
|
90
|
+
timestamp: new Date(),
|
|
91
|
+
message: error.message,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
};
|
package/src/openapi.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises'
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get the OpenAPI specification from the file.
|
|
@@ -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
|
-
|
|
15
|
-
}
|
|
12
|
+
const fileUrl = new URL(file, base);
|
|
13
|
+
const openAPISpecification = JSON.parse(await readFile(fileUrl, 'utf8'));
|
|
14
|
+
return { openAPISpecification };
|
|
15
|
+
};
|
package/src/operation-ids.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const operations = ['get', 'put', 'patch', 'post', 'delete']
|
|
1
|
+
const operations = ['get', 'put', 'patch', 'post', 'delete'];
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get all operation ID's from the specification.
|
|
@@ -6,9 +6,11 @@ const operations = ['get', 'put', 'patch', 'post', 'delete']
|
|
|
6
6
|
* @param {object} params.specification
|
|
7
7
|
* @returns {string[]}
|
|
8
8
|
*/
|
|
9
|
-
export const operationIds = ({ specification }) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
export const operationIds = ({ specification }) =>
|
|
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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { types } from './types.js'
|
|
1
|
+
import { types } from './types.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Parse params to the types defined in the spec
|
|
@@ -8,27 +8,32 @@ import { types } from './types.js'
|
|
|
8
8
|
* @returns {object}
|
|
9
9
|
*/
|
|
10
10
|
export const parseParams = ({ query, spec }) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
spec
|
|
12
|
+
.map((parameter) => {
|
|
13
|
+
const { name, schema } = parameter;
|
|
14
|
+
const {
|
|
15
|
+
type,
|
|
16
|
+
default: defaultValue,
|
|
17
|
+
example: exampleValue,
|
|
18
|
+
} = schema;
|
|
19
|
+
const Type = types[type];
|
|
20
|
+
const paramName = query?.[name];
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
if (!paramName) {
|
|
23
|
+
return { name, value: defaultValue ?? exampleValue };
|
|
24
|
+
}
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
if (Type === Boolean) {
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
value: JSON.parse(paramName.toLowerCase()),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
27
32
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const value = new Type(paramName).valueOf();
|
|
34
|
+
return { name, value };
|
|
35
|
+
})
|
|
36
|
+
.reduce((acc, { name, value }) => {
|
|
37
|
+
acc[name] = value;
|
|
38
|
+
return acc;
|
|
39
|
+
}, {});
|
package/src/router.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { OpenAPIBackend } from 'openapi-backend'
|
|
2
|
-
import addFormats from 'ajv-formats'
|
|
3
|
-
import { makeExpressCallback } from './express-callback.js'
|
|
4
|
-
import { operationIds } from './operation-ids.js'
|
|
5
|
-
import { notFound } from './handlers/not-found.js'
|
|
6
|
-
import { requestValidation } from './handlers/request-validation.js'
|
|
7
|
-
import { responseValidation } from './handlers/response-validation.js'
|
|
8
|
-
import { unauthorized } from './handlers/unauthorized.js'
|
|
1
|
+
import { OpenAPIBackend } from 'openapi-backend';
|
|
2
|
+
import addFormats from 'ajv-formats';
|
|
3
|
+
import { makeExpressCallback } from './express-callback.js';
|
|
4
|
+
import { operationIds } from './operation-ids.js';
|
|
5
|
+
import { notFound } from './handlers/not-found.js';
|
|
6
|
+
import { requestValidation } from './handlers/request-validation.js';
|
|
7
|
+
import { responseValidation } from './handlers/response-validation.js';
|
|
8
|
+
import { unauthorized } from './handlers/unauthorized.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* @typedef {import('./api.js').Logger} Logger
|
|
12
12
|
* @typedef {import('./api.js').SecurityHandler} SecurityHandler
|
|
13
13
|
* @typedef {import('ajv').Options} AjvOpts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
14
17
|
* Setup the router
|
|
15
18
|
* @param {object} params
|
|
16
19
|
* @param {string=} params.secret
|
|
@@ -23,60 +26,76 @@ import { unauthorized } from './handlers/unauthorized.js'
|
|
|
23
26
|
* @param {object=} params.meta
|
|
24
27
|
* @param {SecurityHandler[]=} params.securityHandlers
|
|
25
28
|
* @param {AjvOpts=} params.ajvOptions
|
|
26
|
-
* @returns {{ api
|
|
29
|
+
* @returns {{ api: OpenAPIBackend<any>, openAPISpecification: object }}
|
|
27
30
|
*/
|
|
28
|
-
export const setupRouter = ({
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
export const setupRouter = ({
|
|
32
|
+
secret,
|
|
33
|
+
openAPISpecification,
|
|
34
|
+
controllers,
|
|
31
35
|
apiRoot,
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
36
|
+
strictSpecification,
|
|
37
|
+
errorDetails,
|
|
38
|
+
logger,
|
|
39
|
+
meta,
|
|
40
|
+
securityHandlers = [],
|
|
41
|
+
ajvOptions = {},
|
|
42
|
+
}) => {
|
|
43
|
+
const api = new OpenAPIBackend({
|
|
44
|
+
definition: openAPISpecification,
|
|
45
|
+
apiRoot,
|
|
46
|
+
strict: strictSpecification,
|
|
47
|
+
ajvOpts: ajvOptions,
|
|
48
|
+
customizeAjv: (originalAjv) => {
|
|
49
|
+
addFormats(originalAjv);
|
|
50
|
+
return originalAjv;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
54
|
+
api.register({
|
|
55
|
+
unauthorizedHandler: unauthorized,
|
|
56
|
+
validationFail: requestValidation,
|
|
57
|
+
notFound,
|
|
58
|
+
postResponseHandler: responseValidation,
|
|
59
|
+
});
|
|
46
60
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
operationIds({ specification: openAPISpecification }).forEach(
|
|
62
|
+
(operationId) => {
|
|
63
|
+
if (!Object.hasOwn(controllers, operationId)) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
api.register(
|
|
67
|
+
operationId,
|
|
68
|
+
makeExpressCallback({
|
|
69
|
+
controller: controllers[operationId],
|
|
70
|
+
specification: openAPISpecification,
|
|
71
|
+
errorDetails,
|
|
72
|
+
logger,
|
|
73
|
+
meta,
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
);
|
|
62
78
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
api.register('notImplemented', (context) => {
|
|
80
|
+
const { mock } = context.api.mockResponseForOperation(
|
|
81
|
+
context.operation.operationId
|
|
82
|
+
);
|
|
83
|
+
return mock;
|
|
84
|
+
});
|
|
69
85
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
if (secret) {
|
|
87
|
+
api.registerSecurityHandler(
|
|
88
|
+
'apiKey',
|
|
89
|
+
(context) => context.request.headers['x-api-key'] === secret
|
|
90
|
+
);
|
|
91
|
+
}
|
|
76
92
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
securityHandlers.forEach((securityHandler) => {
|
|
94
|
+
api.registerSecurityHandler(
|
|
95
|
+
securityHandler.name,
|
|
96
|
+
securityHandler.handler
|
|
97
|
+
);
|
|
98
|
+
});
|
|
80
99
|
|
|
81
|
-
|
|
82
|
-
}
|
|
100
|
+
return { api, openAPISpecification };
|
|
101
|
+
};
|
package/src/server.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import express from 'express'
|
|
2
|
-
import cors from 'cors'
|
|
3
|
-
import compression from 'compression'
|
|
4
|
-
import helmet from 'helmet'
|
|
5
|
-
import * as Sentry from '@sentry/node'
|
|
6
|
-
import bodyParser from 'body-parser'
|
|
7
|
-
import { openAPI } from './openapi.js'
|
|
8
|
-
import { Api } from './api.js'
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import cors from 'cors';
|
|
3
|
+
import compression from 'compression';
|
|
4
|
+
import helmet from 'helmet';
|
|
5
|
+
import * as Sentry from '@sentry/node';
|
|
6
|
+
import bodyParser from 'body-parser';
|
|
7
|
+
import { openAPI } from './openapi.js';
|
|
8
|
+
import { Api } from './api.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Get the origin resource policy
|
|
@@ -13,14 +13,14 @@ import { Api } from './api.js'
|
|
|
13
13
|
* @returns {{ crossOriginResourcePolicy: { policy: string, directives: object } }}
|
|
14
14
|
*/
|
|
15
15
|
const getOriginResourcePolicy = (origin) => ({
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
})
|
|
16
|
+
crossOriginResourcePolicy: {
|
|
17
|
+
policy: origin === '*' ? 'cross-origin' : 'same-origin',
|
|
18
|
+
directives: {
|
|
19
|
+
// ...
|
|
20
|
+
'require-trusted-types-for': ["'script'"],
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
26
|
* @typedef {import('express-serve-static-core').Request} Request
|
|
@@ -29,16 +29,23 @@ const getOriginResourcePolicy = (origin) => ({
|
|
|
29
29
|
* @typedef {import('./api.js').ApiSchema} ApiSchema
|
|
30
30
|
* @typedef {import('./api.js').Logger} Logger
|
|
31
31
|
* @typedef {import('express').Express} Express
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @template {object} [T=object]
|
|
32
36
|
* @typedef {object} Controller
|
|
33
37
|
* @property {Context=} context
|
|
34
38
|
* @property {Request=} request
|
|
35
39
|
* @property {Response=} response
|
|
36
40
|
* @property {object=} parameters
|
|
37
41
|
* @property {object=} specification
|
|
38
|
-
* @property {
|
|
42
|
+
* @property {T=} post
|
|
39
43
|
* @property {string=} url
|
|
40
44
|
* @property {Logger=} logger
|
|
41
45
|
* @property {object=} meta
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
42
49
|
* @typedef {object} SentryConfig
|
|
43
50
|
* @property {string=} dsn
|
|
44
51
|
* @property {number=} tracesSampleRate
|
|
@@ -59,55 +66,63 @@ const getOriginResourcePolicy = (origin) => ({
|
|
|
59
66
|
* @param {any[]=} params.middleware
|
|
60
67
|
* @returns {Promise<{ app: Express }>}
|
|
61
68
|
*/
|
|
62
|
-
export const setupServer = async ({
|
|
63
|
-
|
|
64
|
-
origin
|
|
65
|
-
|
|
69
|
+
export const setupServer = async ({
|
|
70
|
+
apis,
|
|
71
|
+
origin = '*',
|
|
72
|
+
staticFolder,
|
|
73
|
+
sentry,
|
|
74
|
+
poweredBy = 'TroJS',
|
|
75
|
+
version = '1.0.0',
|
|
76
|
+
middleware = [],
|
|
77
|
+
}) => {
|
|
78
|
+
const corsOptions = {
|
|
79
|
+
origin,
|
|
80
|
+
};
|
|
66
81
|
|
|
67
|
-
|
|
82
|
+
const app = express();
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
if (sentry) {
|
|
85
|
+
Sentry.init({
|
|
86
|
+
dsn: sentry.dsn,
|
|
87
|
+
integrations: [
|
|
88
|
+
new Sentry.Integrations.Http({ tracing: true }),
|
|
89
|
+
new Sentry.Integrations.Express({ app }),
|
|
90
|
+
],
|
|
91
|
+
tracesSampleRate: sentry.tracesSampleRate || 1.0,
|
|
92
|
+
profilesSampleRate: sentry.profilesSampleRate || 1.0,
|
|
93
|
+
release: sentry.release,
|
|
94
|
+
});
|
|
80
95
|
|
|
81
|
-
|
|
82
|
-
|
|
96
|
+
app.use(Sentry.Handlers.requestHandler());
|
|
97
|
+
}
|
|
83
98
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
99
|
+
app.use(cors(corsOptions));
|
|
100
|
+
app.use(compression());
|
|
101
|
+
app.use(helmet(getOriginResourcePolicy(origin)));
|
|
102
|
+
app.use(express.json());
|
|
103
|
+
middleware.forEach((fn) => app.use(fn));
|
|
104
|
+
app.use(bodyParser.urlencoded({ extended: false }));
|
|
105
|
+
app.use((_request, response, next) => {
|
|
106
|
+
response.setHeader('X-Powered-By', poweredBy);
|
|
107
|
+
response.setHeader('X-Version', version);
|
|
108
|
+
next();
|
|
109
|
+
});
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
111
|
+
if (staticFolder) {
|
|
112
|
+
app.use(express.static(staticFolder));
|
|
113
|
+
}
|
|
99
114
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
115
|
+
apis.forEach((api) => {
|
|
116
|
+
const apiRoutes = new Api(api);
|
|
117
|
+
const routes = apiRoutes.setup();
|
|
118
|
+
app.use(`/${api.version}`, routes);
|
|
119
|
+
});
|
|
105
120
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
121
|
+
if (sentry) {
|
|
122
|
+
app.use(Sentry.Handlers.errorHandler());
|
|
123
|
+
}
|
|
109
124
|
|
|
110
|
-
|
|
111
|
-
}
|
|
125
|
+
return { app };
|
|
126
|
+
};
|
|
112
127
|
|
|
113
|
-
export { openAPI, Api }
|
|
128
|
+
export { openAPI, Api };
|
package/src/types.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
const types = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
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
|
+
};
|
|
11
11
|
|
|
12
|
-
export { types }
|
|
12
|
+
export { types };
|