@lucaapp/service-utils 1.40.3 → 1.41.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/dist/lib/api/api.js
CHANGED
|
@@ -9,6 +9,7 @@ require("express-async-errors");
|
|
|
9
9
|
const swagger_ui_express_1 = __importDefault(require("swagger-ui-express"));
|
|
10
10
|
const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi");
|
|
11
11
|
const endpoint_1 = require("./endpoint");
|
|
12
|
+
const openapi_1 = require("./openapi");
|
|
12
13
|
class Api {
|
|
13
14
|
constructor(options = {}) {
|
|
14
15
|
this.router = options.router || (0, express_1.Router)();
|
|
@@ -32,23 +33,23 @@ class Api {
|
|
|
32
33
|
}
|
|
33
34
|
get(path, summary, options, handler) {
|
|
34
35
|
(0, endpoint_1.mountEndpoint)(this.router, 'get', path, options, handler, this.debug);
|
|
35
|
-
(0,
|
|
36
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'get', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
36
37
|
}
|
|
37
38
|
post(path, summary, options, handler) {
|
|
38
39
|
(0, endpoint_1.mountEndpoint)(this.router, 'post', path, options, handler, this.debug);
|
|
39
|
-
(0,
|
|
40
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'post', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
40
41
|
}
|
|
41
42
|
patch(path, summary, options, handler) {
|
|
42
43
|
(0, endpoint_1.mountEndpoint)(this.router, 'patch', path, options, handler, this.debug);
|
|
43
|
-
(0,
|
|
44
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'patch', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
44
45
|
}
|
|
45
46
|
put(path, summary, options, handler) {
|
|
46
47
|
(0, endpoint_1.mountEndpoint)(this.router, 'put', path, options, handler, this.debug);
|
|
47
|
-
(0,
|
|
48
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'put', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
48
49
|
}
|
|
49
50
|
delete(path, summary, options, handler) {
|
|
50
51
|
(0, endpoint_1.mountEndpoint)(this.router, 'delete', path, options, handler, this.debug);
|
|
51
|
-
(0,
|
|
52
|
+
(0, openapi_1.registerEndpoint)(this.registry, 'delete', this.prefixPath, path, summary, options, this.tags, this.debug);
|
|
52
53
|
}
|
|
53
54
|
generateOpenAPISpec() {
|
|
54
55
|
const generator = new zod_to_openapi_1.OpenAPIGenerator(this.registry.definitions, this.openApiVersion);
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
|
-
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
|
3
2
|
import { Middleware, EndpointResponseSchema } from './types/middleware';
|
|
4
3
|
import { EndpointOptions, EndpointHandler } from './types/endpoint';
|
|
5
4
|
import { ZodObjectSchemaOrUndefined } from './types/utils';
|
|
6
5
|
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
7
6
|
export declare const mountEndpoint: <TResponseSchemas extends readonly EndpointResponseSchema[], TRequestBodySchema extends ZodObjectSchemaOrUndefined, TRequestParamsSchema extends ZodObjectSchemaOrUndefined, TRequestQuerySchema extends ZodObjectSchemaOrUndefined, TRequestHeadersSchema extends ZodObjectSchemaOrUndefined, TMiddlewares extends readonly Middleware<any, any, any, any, any, any>[] | undefined = undefined>(router: Router, method: HttpMethod, path: string, options: EndpointOptions<TResponseSchemas, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TMiddlewares>, handler: EndpointHandler<TResponseSchemas, TRequestBodySchema, TRequestParamsSchema, TRequestQuerySchema, TRequestHeadersSchema, TMiddlewares>, debug: boolean) => void;
|
|
8
|
-
export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
|
|
9
7
|
export {};
|
package/dist/lib/api/endpoint.js
CHANGED
|
@@ -3,99 +3,165 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
6
|
+
exports.mountEndpoint = void 0;
|
|
7
7
|
const zod_1 = require("zod");
|
|
8
8
|
const assert_1 = __importDefault(require("assert"));
|
|
9
9
|
const http_1 = require("./types/http");
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
},
|
|
24
|
-
}),
|
|
25
|
-
};
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
const existingResponse = responses[response.status];
|
|
29
|
-
existingResponse.description =
|
|
30
|
-
existingResponse.description + ' | ' + response.description;
|
|
31
|
-
// No Content merge
|
|
32
|
-
if (!response.schema) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
// No Content
|
|
36
|
-
if (!existingResponse.content ||
|
|
37
|
-
!existingResponse.content['application/json']) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
existingResponse.content['application/json'].schema =
|
|
41
|
-
existingResponse.content['application/json'].schema.or(response.schema);
|
|
10
|
+
const sendBadContextError = (response, error, debug) => {
|
|
11
|
+
response.err = error;
|
|
12
|
+
return response.status(500).send({
|
|
13
|
+
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
14
|
+
message: 'Bad Context',
|
|
15
|
+
...(debug && {
|
|
16
|
+
debug: {
|
|
17
|
+
name: error.name,
|
|
18
|
+
message: error.message,
|
|
19
|
+
stack: error.stack,
|
|
20
|
+
issues: error.issues,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
42
23
|
});
|
|
43
|
-
return responses;
|
|
44
24
|
};
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
return false;
|
|
48
|
-
// can't access possibly existing type property without casting to any
|
|
49
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
-
if (typeof error.type !== 'string')
|
|
51
|
-
return false;
|
|
52
|
-
return true;
|
|
53
|
-
};
|
|
54
|
-
const validateAndSendResponse = async (expressResponse, response, validResponses) => {
|
|
55
|
-
(0, assert_1.default)(typeof response.status === 'number');
|
|
56
|
-
// get schema respective to status code
|
|
57
|
-
const responseSchema = validResponses.find(responseSchema => responseSchema.status === response.status);
|
|
58
|
-
const schema = responseSchema?.schema;
|
|
59
|
-
const isZodSchema = schema instanceof zod_1.z.ZodSchema;
|
|
60
|
-
const isZodVoid = schema instanceof zod_1.z.ZodVoid;
|
|
61
|
-
(0, assert_1.default)(isZodSchema || isZodVoid);
|
|
62
|
-
// validate response schema
|
|
63
|
-
const validatedResponseBody = await schema.parseAsync(response.body);
|
|
64
|
-
if (validatedResponseBody === undefined) {
|
|
65
|
-
// No Content Response
|
|
66
|
-
return expressResponse.status(response.status).end();
|
|
67
|
-
}
|
|
68
|
-
expressResponse.status(response.status).send(validatedResponseBody);
|
|
69
|
-
};
|
|
70
|
-
const sendBadResponseError = (response, error) => {
|
|
25
|
+
const sendBadResponseError = (response, error, debug) => {
|
|
26
|
+
response.err = error;
|
|
71
27
|
return response.status(500).send({
|
|
72
28
|
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
73
|
-
message: '
|
|
29
|
+
message: 'Bad Response',
|
|
30
|
+
...(debug && {
|
|
31
|
+
debug: {
|
|
32
|
+
name: error.name,
|
|
33
|
+
message: error.message,
|
|
34
|
+
stack: error.stack,
|
|
35
|
+
issues: error.issues,
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
const sendBadRequestError = (response, error) => {
|
|
41
|
+
response.err = error;
|
|
42
|
+
return response.status(400).send({
|
|
43
|
+
error: http_1.HTTPStatus.BAD_REQUEST,
|
|
44
|
+
message: error.message,
|
|
74
45
|
issues: error.issues,
|
|
75
46
|
});
|
|
76
47
|
};
|
|
77
48
|
const sendKnownErrorResponse = (response, statusCode, error, debug) => {
|
|
49
|
+
response.err = error;
|
|
78
50
|
response.status(statusCode).send({
|
|
79
51
|
error: error.type,
|
|
80
52
|
message: error.message,
|
|
81
|
-
...(debug && {
|
|
53
|
+
...(debug && {
|
|
54
|
+
debug: {
|
|
55
|
+
name: error.name,
|
|
56
|
+
message: error.message,
|
|
57
|
+
stack: error.stack,
|
|
58
|
+
meta: error.meta,
|
|
59
|
+
},
|
|
60
|
+
}),
|
|
82
61
|
});
|
|
83
62
|
};
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
error
|
|
87
|
-
|
|
88
|
-
|
|
63
|
+
const sendUnknownErrorResponse = (response, error, debug) => {
|
|
64
|
+
if (error instanceof Error) {
|
|
65
|
+
response.err = error;
|
|
66
|
+
// try to pass forward meta if it exists even for unknown/unmapped errors
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
68
|
+
const meta = error.meta;
|
|
69
|
+
response.status(500).send({
|
|
70
|
+
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
71
|
+
message: 'Internal Server Error',
|
|
72
|
+
...(debug && {
|
|
73
|
+
debug: {
|
|
74
|
+
name: error.name,
|
|
75
|
+
message: error.message,
|
|
76
|
+
stack: error.stack,
|
|
77
|
+
meta,
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
response.status(500).send({
|
|
84
|
+
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
85
|
+
message: 'Internal Server Error',
|
|
86
|
+
...(debug && {
|
|
87
|
+
debug: {
|
|
88
|
+
message: 'Non Error Object',
|
|
89
|
+
error: error,
|
|
90
|
+
},
|
|
91
|
+
}),
|
|
89
92
|
});
|
|
90
93
|
};
|
|
91
|
-
const
|
|
94
|
+
const sendErrorResponse = (response, error, knownErrors, debug) => {
|
|
95
|
+
if (isTypedError(error) && knownErrors && knownErrors[error.type]) {
|
|
96
|
+
const statusCode = knownErrors[error.type];
|
|
97
|
+
sendKnownErrorResponse(response, statusCode, error, debug);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
sendUnknownErrorResponse(response, error, debug);
|
|
101
|
+
return;
|
|
102
|
+
};
|
|
103
|
+
const isTypedError = (error) => {
|
|
104
|
+
if (!(error instanceof Error))
|
|
105
|
+
return false;
|
|
106
|
+
// can't access possibly existing type property without casting to any
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
108
|
+
if (typeof error.type !== 'string')
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
};
|
|
112
|
+
const validateAndSendResponse = async (expressResponse, response, validResponses, debug) => {
|
|
92
113
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const
|
|
114
|
+
(0, assert_1.default)(typeof response.status === 'number');
|
|
115
|
+
// get schema respective to status code
|
|
116
|
+
const responseSchema = validResponses.find(responseSchema => responseSchema.status === response.status);
|
|
117
|
+
const schema = responseSchema?.schema;
|
|
118
|
+
const isZodSchema = schema instanceof zod_1.z.ZodSchema;
|
|
119
|
+
const isZodVoid = schema instanceof zod_1.z.ZodVoid;
|
|
120
|
+
(0, assert_1.default)(isZodSchema || isZodVoid);
|
|
121
|
+
// validate response schema
|
|
122
|
+
const validatedResponseBody = await schema.parseAsync(response.body);
|
|
123
|
+
if (validatedResponseBody === undefined) {
|
|
124
|
+
// No Content Response
|
|
125
|
+
expressResponse.status(response.status).end();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
expressResponse.status(response.status).send(validatedResponseBody);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof zod_1.ZodError) {
|
|
132
|
+
sendBadResponseError(expressResponse, error, debug);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
sendErrorResponse(expressResponse, error, undefined, debug);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const handleMiddleware = async (middleware, context, request, response, debug) =>
|
|
139
|
+
// resolves true when next middleware should continue
|
|
140
|
+
// resolves false when a response was sent and next middleware should not be called
|
|
141
|
+
new Promise(async (resolve) => {
|
|
142
|
+
try {
|
|
143
|
+
let handlerRequestBody;
|
|
144
|
+
let handlerRequestParams;
|
|
145
|
+
let handlerRequestQuery;
|
|
146
|
+
let handlerRequestHeaders;
|
|
147
|
+
try {
|
|
148
|
+
handlerRequestBody =
|
|
149
|
+
((await middleware.options.schemas?.body?.parseAsync(request.body)) ||
|
|
150
|
+
undefined);
|
|
151
|
+
handlerRequestParams =
|
|
152
|
+
((await middleware.options.schemas?.params?.parseAsync(request.params)) || undefined);
|
|
153
|
+
handlerRequestQuery =
|
|
154
|
+
((await middleware.options.schemas?.query?.parseAsync(request.query)) || undefined);
|
|
155
|
+
handlerRequestHeaders =
|
|
156
|
+
((await middleware.options.schemas?.headers?.parseAsync(request.headers)) || undefined);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error instanceof zod_1.ZodError) {
|
|
160
|
+
sendBadRequestError(response, error);
|
|
161
|
+
return resolve(false);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
99
165
|
const { ip, baseUrl, originalUrl, route, method } = request;
|
|
100
166
|
const handlerRequest = {
|
|
101
167
|
body: handlerRequestBody,
|
|
@@ -109,17 +175,8 @@ const handleMiddleware = async (middleware, context, request, response, debug) =
|
|
|
109
175
|
path: route.path,
|
|
110
176
|
};
|
|
111
177
|
await middleware.handler(handlerRequest, async (middlewareResponse) => {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
resolve(false);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
if (error instanceof zod_1.ZodError) {
|
|
118
|
-
sendBadResponseError(response, error);
|
|
119
|
-
return resolve(false);
|
|
120
|
-
}
|
|
121
|
-
reject(error);
|
|
122
|
-
}
|
|
178
|
+
await validateAndSendResponse(response, middlewareResponse, middleware.options.responses, debug);
|
|
179
|
+
resolve(false);
|
|
123
180
|
}, async (returnedContext) => {
|
|
124
181
|
try {
|
|
125
182
|
const validatedContext = await middleware.options.schemas?.context?.parseAsync(returnedContext);
|
|
@@ -128,40 +185,40 @@ const handleMiddleware = async (middleware, context, request, response, debug) =
|
|
|
128
185
|
}
|
|
129
186
|
catch (error) {
|
|
130
187
|
if (error instanceof zod_1.ZodError) {
|
|
131
|
-
|
|
188
|
+
sendBadContextError(response, error, debug);
|
|
132
189
|
return resolve(false);
|
|
133
190
|
}
|
|
134
|
-
|
|
191
|
+
sendErrorResponse(response, error, undefined, debug);
|
|
192
|
+
resolve(false);
|
|
135
193
|
}
|
|
136
194
|
});
|
|
137
195
|
}
|
|
138
196
|
catch (error) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
return resolve(false);
|
|
142
|
-
}
|
|
143
|
-
if (isTypedError(error) &&
|
|
144
|
-
middleware.options.errors &&
|
|
145
|
-
middleware.options.errors[error.type]) {
|
|
146
|
-
const statusCode = middleware.options.errors[error.type];
|
|
147
|
-
sendKnownErrorResponse(response, statusCode, error, debug);
|
|
148
|
-
return resolve(false);
|
|
149
|
-
}
|
|
150
|
-
// bubble up unknown errors
|
|
151
|
-
reject(error);
|
|
197
|
+
sendErrorResponse(response, error, middleware.options.errors, debug);
|
|
198
|
+
resolve(false);
|
|
152
199
|
}
|
|
153
200
|
});
|
|
154
201
|
const mountEndpoint = (router, method, path, options, handler, debug) => {
|
|
155
202
|
(0, assert_1.default)(options.responses.length > 0, 'You need to specify at least one response');
|
|
156
|
-
router[method](path, async (request, response
|
|
203
|
+
router[method](path, async (request, response) => {
|
|
157
204
|
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
undefined);
|
|
205
|
+
let handlerRequestBody;
|
|
206
|
+
let handlerRequestParams;
|
|
207
|
+
let handlerRequestQuery;
|
|
208
|
+
let handlerRequestHeaders;
|
|
209
|
+
try {
|
|
210
|
+
// parse request schemas
|
|
211
|
+
handlerRequestBody = ((await options.schemas?.body?.parseAsync(request.body)) || undefined);
|
|
212
|
+
handlerRequestParams = ((await options.schemas?.params?.parseAsync(request.params)) || undefined);
|
|
213
|
+
handlerRequestQuery = ((await options.schemas?.query?.parseAsync(request.query)) || undefined);
|
|
214
|
+
handlerRequestHeaders = ((await options.schemas?.headers?.parseAsync(request.headers)) || undefined);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (error instanceof zod_1.ZodError) {
|
|
218
|
+
return sendBadRequestError(response, error);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
165
222
|
const handlerRequest = {
|
|
166
223
|
body: handlerRequestBody,
|
|
167
224
|
params: handlerRequestParams,
|
|
@@ -177,148 +234,12 @@ const mountEndpoint = (router, method, path, options, handler, debug) => {
|
|
|
177
234
|
}
|
|
178
235
|
}
|
|
179
236
|
await handler(handlerRequest, ctx, async (controllerResponse) => {
|
|
180
|
-
|
|
181
|
-
await validateAndSendResponse(response, controllerResponse, options.responses);
|
|
182
|
-
}
|
|
183
|
-
catch (error) {
|
|
184
|
-
if (error instanceof zod_1.ZodError) {
|
|
185
|
-
return sendBadResponseError(response, error);
|
|
186
|
-
}
|
|
187
|
-
next(error);
|
|
188
|
-
}
|
|
237
|
+
await validateAndSendResponse(response, controllerResponse, options.responses, debug);
|
|
189
238
|
});
|
|
190
239
|
}
|
|
191
240
|
catch (error) {
|
|
192
|
-
|
|
193
|
-
return sendBadRequestError(response, error);
|
|
194
|
-
}
|
|
195
|
-
if (isTypedError(error) &&
|
|
196
|
-
options.errors &&
|
|
197
|
-
options.errors[error.type]) {
|
|
198
|
-
const statusCode = options.errors[error.type];
|
|
199
|
-
return sendKnownErrorResponse(response, statusCode, error, debug);
|
|
200
|
-
}
|
|
201
|
-
if (error instanceof Error) {
|
|
202
|
-
return response.status(500).send({
|
|
203
|
-
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
204
|
-
message: error.message,
|
|
205
|
-
...(debug && { stack: error.stack }),
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
return response.status(500).send({
|
|
209
|
-
error: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
210
|
-
message: 'Unknown Error',
|
|
211
|
-
});
|
|
241
|
+
sendErrorResponse(response, error, options.errors, debug);
|
|
212
242
|
}
|
|
213
243
|
});
|
|
214
244
|
};
|
|
215
245
|
exports.mountEndpoint = mountEndpoint;
|
|
216
|
-
const registerEndpoint = (registry, method, prefixPath, path, summary,
|
|
217
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
218
|
-
options, tags, debug) => {
|
|
219
|
-
let bodySchema = options.schemas?.body || undefined;
|
|
220
|
-
let paramsSchema = options.schemas?.params || undefined;
|
|
221
|
-
let querySchema = options.schemas?.query || undefined;
|
|
222
|
-
let headersSchema = options.schemas?.headers || undefined;
|
|
223
|
-
let responseSchemas = options.responses;
|
|
224
|
-
if (options.errors) {
|
|
225
|
-
for (const errorType of Object.keys(options.errors)) {
|
|
226
|
-
const statusCode = options.errors[errorType];
|
|
227
|
-
responseSchemas.push({
|
|
228
|
-
status: statusCode,
|
|
229
|
-
description: errorType,
|
|
230
|
-
schema: zod_1.z.object({
|
|
231
|
-
error: zod_1.z.literal(errorType),
|
|
232
|
-
message: zod_1.z.string(),
|
|
233
|
-
}),
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
for (const middleware of options.middlewares || []) {
|
|
238
|
-
if (middleware.options.schemas?.body) {
|
|
239
|
-
bodySchema = bodySchema
|
|
240
|
-
? bodySchema.merge(middleware.options.schemas.body)
|
|
241
|
-
: middleware.options.schemas.body;
|
|
242
|
-
}
|
|
243
|
-
if (middleware.options.schemas?.params) {
|
|
244
|
-
paramsSchema = paramsSchema
|
|
245
|
-
? paramsSchema.merge(middleware.options.schemas.params)
|
|
246
|
-
: middleware.options.schemas.params;
|
|
247
|
-
}
|
|
248
|
-
if (middleware.options.schemas?.query) {
|
|
249
|
-
querySchema = querySchema
|
|
250
|
-
? querySchema.merge(middleware.options.schemas.query)
|
|
251
|
-
: middleware.options.schemas.query;
|
|
252
|
-
}
|
|
253
|
-
if (middleware.options.schemas?.headers) {
|
|
254
|
-
headersSchema = headersSchema
|
|
255
|
-
? headersSchema.merge(middleware.options.schemas.headers)
|
|
256
|
-
: middleware.options.schemas.headers;
|
|
257
|
-
}
|
|
258
|
-
if (middleware.options.responses) {
|
|
259
|
-
responseSchemas = responseSchemas.concat(middleware.options.responses);
|
|
260
|
-
}
|
|
261
|
-
if (middleware.options.errors) {
|
|
262
|
-
for (const errorType of Object.keys(middleware.options.errors)) {
|
|
263
|
-
const statusCode = middleware.options.errors[errorType];
|
|
264
|
-
responseSchemas.push({
|
|
265
|
-
status: statusCode,
|
|
266
|
-
description: errorType,
|
|
267
|
-
schema: zod_1.z.object({
|
|
268
|
-
error: zod_1.z.literal(errorType),
|
|
269
|
-
message: zod_1.z.string(),
|
|
270
|
-
}),
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
// Add ZodError response
|
|
276
|
-
responseSchemas.push({
|
|
277
|
-
status: 400,
|
|
278
|
-
description: 'Bad Request',
|
|
279
|
-
schema: zod_1.z.object({
|
|
280
|
-
error: zod_1.z.literal(http_1.HTTPStatus.BAD_REQUEST),
|
|
281
|
-
message: zod_1.z.string(),
|
|
282
|
-
issues: zod_1.z.array(zod_1.z
|
|
283
|
-
.object({
|
|
284
|
-
message: zod_1.z.string(),
|
|
285
|
-
})
|
|
286
|
-
.openapi({ additionalProperties: true })),
|
|
287
|
-
}),
|
|
288
|
-
});
|
|
289
|
-
// Add UnknownError response
|
|
290
|
-
responseSchemas.push({
|
|
291
|
-
status: 500,
|
|
292
|
-
description: 'Internal Server Error',
|
|
293
|
-
schema: zod_1.z.object({
|
|
294
|
-
error: zod_1.z.literal(http_1.HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
295
|
-
message: zod_1.z.string(),
|
|
296
|
-
...(debug && { stack: zod_1.z.string().optional() }),
|
|
297
|
-
}),
|
|
298
|
-
});
|
|
299
|
-
const responseMap = buildResponseConfig(responseSchemas);
|
|
300
|
-
registry.registerPath({
|
|
301
|
-
method,
|
|
302
|
-
path: mapExpressParamsToOpenAPIParams(prefixPath + path),
|
|
303
|
-
summary,
|
|
304
|
-
tags,
|
|
305
|
-
// description: summary,
|
|
306
|
-
request: {
|
|
307
|
-
...(bodySchema && {
|
|
308
|
-
body: {
|
|
309
|
-
content: {
|
|
310
|
-
'application/json': {
|
|
311
|
-
schema: bodySchema,
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
}),
|
|
316
|
-
params: paramsSchema,
|
|
317
|
-
query: querySchema,
|
|
318
|
-
headers: headersSchema ? [headersSchema] : [],
|
|
319
|
-
},
|
|
320
|
-
// tags: ['meep'],
|
|
321
|
-
responses: responseMap,
|
|
322
|
-
});
|
|
323
|
-
};
|
|
324
|
-
exports.registerEndpoint = registerEndpoint;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
|
|
2
|
+
import { EndpointOptions } from './types/endpoint';
|
|
3
|
+
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch';
|
|
4
|
+
export declare const registerEndpoint: (registry: OpenAPIRegistry, method: HttpMethod, prefixPath: string, path: string, summary: string, options: EndpointOptions<any, any, any, any, any, any>, tags: string[], debug: boolean) => void;
|
|
5
|
+
export {};
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerEndpoint = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const http_1 = require("./types/http");
|
|
6
|
+
const mapExpressParamsToOpenAPIParams = (path) => path.replaceAll(/\:([^\:\/]+)/g, '{$1}');
|
|
7
|
+
const buildResponseConfig = (responseOptions) => {
|
|
8
|
+
const responses = {};
|
|
9
|
+
responseOptions.forEach(response => {
|
|
10
|
+
const isAlreadyPresent = !!responses[String(response.status)];
|
|
11
|
+
if (!isAlreadyPresent) {
|
|
12
|
+
responses[String(response.status)] = {
|
|
13
|
+
description: response.description,
|
|
14
|
+
...(!(response.schema instanceof zod_1.z.ZodVoid) && {
|
|
15
|
+
content: {
|
|
16
|
+
'application/json': {
|
|
17
|
+
schema: response.schema,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const existingResponse = responses[response.status];
|
|
25
|
+
existingResponse.description =
|
|
26
|
+
existingResponse.description + ' | ' + response.description;
|
|
27
|
+
// No Content merge
|
|
28
|
+
if (!response.schema) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// No Content
|
|
32
|
+
if (!existingResponse.content ||
|
|
33
|
+
!existingResponse.content['application/json']) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
existingResponse.content['application/json'].schema =
|
|
37
|
+
existingResponse.content['application/json'].schema.or(response.schema);
|
|
38
|
+
});
|
|
39
|
+
return responses;
|
|
40
|
+
};
|
|
41
|
+
const registerEndpoint = (registry, method, prefixPath, path, summary,
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
43
|
+
options, tags, debug) => {
|
|
44
|
+
let bodySchema = options.schemas?.body || undefined;
|
|
45
|
+
let paramsSchema = options.schemas?.params || undefined;
|
|
46
|
+
let querySchema = options.schemas?.query || undefined;
|
|
47
|
+
let headersSchema = options.schemas?.headers || undefined;
|
|
48
|
+
let responseSchemas = options.responses;
|
|
49
|
+
if (options.errors) {
|
|
50
|
+
for (const errorType of Object.keys(options.errors)) {
|
|
51
|
+
const statusCode = options.errors[errorType];
|
|
52
|
+
responseSchemas.push({
|
|
53
|
+
status: statusCode,
|
|
54
|
+
description: errorType,
|
|
55
|
+
schema: zod_1.z.object({
|
|
56
|
+
error: zod_1.z.literal(errorType),
|
|
57
|
+
message: zod_1.z.string(),
|
|
58
|
+
}),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const middleware of options.middlewares || []) {
|
|
63
|
+
if (middleware.options.schemas?.body) {
|
|
64
|
+
bodySchema = bodySchema
|
|
65
|
+
? bodySchema.merge(middleware.options.schemas.body)
|
|
66
|
+
: middleware.options.schemas.body;
|
|
67
|
+
}
|
|
68
|
+
if (middleware.options.schemas?.params) {
|
|
69
|
+
paramsSchema = paramsSchema
|
|
70
|
+
? paramsSchema.merge(middleware.options.schemas.params)
|
|
71
|
+
: middleware.options.schemas.params;
|
|
72
|
+
}
|
|
73
|
+
if (middleware.options.schemas?.query) {
|
|
74
|
+
querySchema = querySchema
|
|
75
|
+
? querySchema.merge(middleware.options.schemas.query)
|
|
76
|
+
: middleware.options.schemas.query;
|
|
77
|
+
}
|
|
78
|
+
if (middleware.options.schemas?.headers) {
|
|
79
|
+
headersSchema = headersSchema
|
|
80
|
+
? headersSchema.merge(middleware.options.schemas.headers)
|
|
81
|
+
: middleware.options.schemas.headers;
|
|
82
|
+
}
|
|
83
|
+
if (middleware.options.responses) {
|
|
84
|
+
responseSchemas = responseSchemas.concat(middleware.options.responses);
|
|
85
|
+
}
|
|
86
|
+
if (middleware.options.errors) {
|
|
87
|
+
for (const errorType of Object.keys(middleware.options.errors)) {
|
|
88
|
+
const statusCode = middleware.options.errors[errorType];
|
|
89
|
+
responseSchemas.push({
|
|
90
|
+
status: statusCode,
|
|
91
|
+
description: errorType,
|
|
92
|
+
schema: zod_1.z.object({
|
|
93
|
+
error: zod_1.z.literal(errorType),
|
|
94
|
+
message: zod_1.z.string(),
|
|
95
|
+
...(debug && {
|
|
96
|
+
debug: zod_1.z
|
|
97
|
+
.object({
|
|
98
|
+
name: zod_1.z.string().optional(),
|
|
99
|
+
stack: zod_1.z.string().optional(),
|
|
100
|
+
message: zod_1.z.string().optional(),
|
|
101
|
+
meta: zod_1.z.object({}).passthrough().optional(),
|
|
102
|
+
})
|
|
103
|
+
.optional(),
|
|
104
|
+
}),
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Add ZodError response
|
|
111
|
+
responseSchemas.push({
|
|
112
|
+
status: 400,
|
|
113
|
+
description: http_1.HTTPStatus.BAD_REQUEST,
|
|
114
|
+
schema: zod_1.z.object({
|
|
115
|
+
error: zod_1.z.literal(http_1.HTTPStatus.BAD_REQUEST),
|
|
116
|
+
message: zod_1.z.string(),
|
|
117
|
+
issues: zod_1.z.array(zod_1.z
|
|
118
|
+
.object({
|
|
119
|
+
message: zod_1.z.string(),
|
|
120
|
+
})
|
|
121
|
+
.openapi({ additionalProperties: true })),
|
|
122
|
+
}),
|
|
123
|
+
});
|
|
124
|
+
// Add UnknownError response
|
|
125
|
+
responseSchemas.push({
|
|
126
|
+
status: 500,
|
|
127
|
+
description: http_1.HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
128
|
+
schema: zod_1.z.object({
|
|
129
|
+
error: zod_1.z.literal(http_1.HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
130
|
+
message: zod_1.z.string(),
|
|
131
|
+
...(debug && {
|
|
132
|
+
debug: zod_1.z
|
|
133
|
+
.object({
|
|
134
|
+
name: zod_1.z.string().optional(),
|
|
135
|
+
stack: zod_1.z.string().optional(),
|
|
136
|
+
message: zod_1.z.string().optional(),
|
|
137
|
+
meta: zod_1.z.object({}).passthrough().optional(),
|
|
138
|
+
})
|
|
139
|
+
.optional(),
|
|
140
|
+
}),
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
const responseMap = buildResponseConfig(responseSchemas);
|
|
144
|
+
registry.registerPath({
|
|
145
|
+
method,
|
|
146
|
+
path: mapExpressParamsToOpenAPIParams(prefixPath + path),
|
|
147
|
+
summary,
|
|
148
|
+
tags,
|
|
149
|
+
// description: summary,
|
|
150
|
+
request: {
|
|
151
|
+
...(bodySchema && {
|
|
152
|
+
body: {
|
|
153
|
+
content: {
|
|
154
|
+
'application/json': {
|
|
155
|
+
schema: bodySchema,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
}),
|
|
160
|
+
params: paramsSchema,
|
|
161
|
+
query: querySchema,
|
|
162
|
+
headers: headersSchema ? [headersSchema] : [],
|
|
163
|
+
},
|
|
164
|
+
responses: responseMap,
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
exports.registerEndpoint = registerEndpoint;
|
|
@@ -147,7 +147,7 @@ class ServiceIdentity {
|
|
|
147
147
|
if (method !== payload.method) {
|
|
148
148
|
return respond((0, api_1.forbidden)(`${method} !== ${payload.method}`));
|
|
149
149
|
}
|
|
150
|
-
next({ payload: payload.data });
|
|
150
|
+
return next({ payload: payload.data });
|
|
151
151
|
});
|
|
152
152
|
this.identityJWKSRoute = async (_, response) => {
|
|
153
153
|
response.send(await this.getIdentityJWKS());
|