@athenna/http 5.48.0 → 5.50.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/package.json +1 -1
- package/src/kernels/HttpKernel.js +9 -2
- package/src/router/Route.js +9 -1
- package/src/router/RouteSchema.d.ts +1 -0
- package/src/router/RouteSchema.js +11 -5
- package/src/server/ServerImpl.d.ts +1 -0
- package/src/server/ServerImpl.js +31 -0
- package/src/exceptions/ResponseValidationException.d.ts +0 -5
- package/src/exceptions/ResponseValidationException.js +0 -11
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@ import { Log } from '@athenna/logger';
|
|
|
13
13
|
import { Config } from '@athenna/config';
|
|
14
14
|
import { sep, isAbsolute, resolve } from 'node:path';
|
|
15
15
|
import { Annotation } from '@athenna/ioc';
|
|
16
|
-
import { File, Path, Module, String } from '@athenna/common';
|
|
16
|
+
import { File, Path, Module, String, Json } from '@athenna/common';
|
|
17
17
|
import { HttpExceptionHandler } from '#src/handlers/HttpExceptionHandler';
|
|
18
18
|
export class HttpKernel {
|
|
19
19
|
/**
|
|
@@ -57,7 +57,14 @@ export class HttpKernel {
|
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
if (swaggerPlugin) {
|
|
60
|
-
|
|
60
|
+
const openapiConfig = Json.omit(Config.get('openapi', {}), ['paths']);
|
|
61
|
+
const pluginConfig = Json.omit(Config.get('http.swagger.configurations', {}), ['swagger']);
|
|
62
|
+
const swaggerConfig = Config.get('http.swagger.configurations.swagger', {});
|
|
63
|
+
await Server.plugin(swaggerPlugin, {
|
|
64
|
+
...pluginConfig,
|
|
65
|
+
...openapiConfig,
|
|
66
|
+
...swaggerConfig
|
|
67
|
+
});
|
|
61
68
|
}
|
|
62
69
|
else {
|
|
63
70
|
debug('Not able to register swagger plugin. Install @fastify/swagger package.');
|
package/src/router/Route.js
CHANGED
|
@@ -238,17 +238,25 @@ export class Route extends Macroable {
|
|
|
238
238
|
* ```
|
|
239
239
|
*/
|
|
240
240
|
schema(options) {
|
|
241
|
-
const { schema, zod } = normalizeRouteSchema(options);
|
|
241
|
+
const { schema, swaggerSchema, zod } = normalizeRouteSchema(options);
|
|
242
242
|
this.route.fastify.schema = schema;
|
|
243
243
|
if (zod) {
|
|
244
244
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
245
245
|
// @ts-ignore
|
|
246
246
|
this.route.fastify.config.zod = zod;
|
|
247
|
+
if (Object.keys(zod.response).length) {
|
|
248
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
249
|
+
// @ts-ignore
|
|
250
|
+
this.route.fastify.config.swaggerSchema = swaggerSchema;
|
|
251
|
+
}
|
|
247
252
|
}
|
|
248
253
|
else {
|
|
249
254
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
250
255
|
// @ts-ignore
|
|
251
256
|
delete this.route.fastify.config.zod;
|
|
257
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
258
|
+
// @ts-ignore
|
|
259
|
+
delete this.route.fastify.config.swaggerSchema;
|
|
252
260
|
}
|
|
253
261
|
return this;
|
|
254
262
|
}
|
|
@@ -23,6 +23,7 @@ export type RouteZodSchemas = {
|
|
|
23
23
|
};
|
|
24
24
|
export declare function normalizeRouteSchema(options: RouteSchemaOptions): {
|
|
25
25
|
schema: FastifySchema;
|
|
26
|
+
swaggerSchema: FastifySchema;
|
|
26
27
|
zod: RouteZodSchemas | null;
|
|
27
28
|
};
|
|
28
29
|
export declare function parseRequestWithZod(req: FastifyRequest, schemas: RouteZodSchemas): Promise<void>;
|
|
@@ -8,11 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Is } from '@athenna/common';
|
|
10
10
|
import { ZodValidationException } from '#src/exceptions/ZodValidationException';
|
|
11
|
-
import { ResponseValidationException } from '#src/exceptions/ResponseValidationException';
|
|
12
11
|
export function normalizeRouteSchema(options) {
|
|
13
12
|
const request = {};
|
|
14
13
|
const response = {};
|
|
15
14
|
const schema = { ...options };
|
|
15
|
+
const swaggerSchema = { ...options };
|
|
16
16
|
const requestKeys = ['body', 'headers', 'params', 'querystring'];
|
|
17
17
|
requestKeys.forEach(key => {
|
|
18
18
|
if (!isZodSchema(options[key])) {
|
|
@@ -20,20 +20,27 @@ export function normalizeRouteSchema(options) {
|
|
|
20
20
|
}
|
|
21
21
|
request[key] = options[key];
|
|
22
22
|
schema[key] = toJsonSchema(options[key], 'input');
|
|
23
|
+
swaggerSchema[key] = toJsonSchema(options[key], 'input');
|
|
23
24
|
});
|
|
24
25
|
if (options.response && Is.Object(options.response)) {
|
|
25
26
|
schema.response = { ...options.response };
|
|
27
|
+
swaggerSchema.response = { ...options.response };
|
|
26
28
|
Object.entries(options.response).forEach(([statusCode, value]) => {
|
|
27
29
|
if (!isZodSchema(value)) {
|
|
28
30
|
return;
|
|
29
31
|
}
|
|
30
32
|
response[statusCode] = value;
|
|
31
|
-
|
|
33
|
+
swaggerSchema.response[statusCode] = toJsonSchema(value, 'output');
|
|
34
|
+
delete schema.response[statusCode];
|
|
32
35
|
});
|
|
36
|
+
if (!Object.keys(schema.response).length) {
|
|
37
|
+
delete schema.response;
|
|
38
|
+
}
|
|
33
39
|
}
|
|
34
40
|
const hasZodSchemas = Object.keys(request).length > 0 || Object.keys(response).length > 0;
|
|
35
41
|
return {
|
|
36
42
|
schema,
|
|
43
|
+
swaggerSchema,
|
|
37
44
|
zod: hasZodSchemas ? { request, response } : null
|
|
38
45
|
};
|
|
39
46
|
}
|
|
@@ -57,9 +64,8 @@ export async function parseResponseWithZod(reply, payload, schemas) {
|
|
|
57
64
|
if (!schema) {
|
|
58
65
|
return payload;
|
|
59
66
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
67
|
+
const result = await schema.safeParseAsync(payload);
|
|
68
|
+
return result.success ? result.data : payload;
|
|
63
69
|
}
|
|
64
70
|
function getResponseSchema(statusCode, schemas) {
|
|
65
71
|
return (schemas[statusCode] ||
|
|
@@ -138,6 +138,7 @@ export declare class ServerImpl extends Macroable {
|
|
|
138
138
|
options(options: Omit<RouteJson, 'methods'>): void;
|
|
139
139
|
private toRouteHooks;
|
|
140
140
|
private getFastifyOptionsWithOpenApiSchema;
|
|
141
|
+
private configureSwaggerTransform;
|
|
141
142
|
private getOpenApiRouteSchema;
|
|
142
143
|
private getOpenApiPathCandidates;
|
|
143
144
|
private normalizePath;
|
package/src/server/ServerImpl.js
CHANGED
|
@@ -282,13 +282,21 @@ export class ServerImpl extends Macroable {
|
|
|
282
282
|
const automaticSchema = this.getOpenApiRouteSchema(options);
|
|
283
283
|
const fastifyOptions = { ...options.fastify };
|
|
284
284
|
if (!automaticSchema) {
|
|
285
|
+
this.configureSwaggerTransform(fastifyOptions);
|
|
285
286
|
return fastifyOptions;
|
|
286
287
|
}
|
|
287
288
|
const normalizedSchema = normalizeRouteSchema(automaticSchema);
|
|
288
289
|
const currentConfig = { ...(fastifyOptions.config || {}) };
|
|
290
|
+
const currentSwaggerSchema =
|
|
291
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
292
|
+
// @ts-ignore
|
|
293
|
+
currentConfig.swaggerSchema || fastifyOptions.schema;
|
|
289
294
|
fastifyOptions.schema = this.mergeFastifySchemas(normalizedSchema.schema, fastifyOptions.schema);
|
|
290
295
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
291
296
|
// @ts-ignore
|
|
297
|
+
currentConfig.swaggerSchema = this.mergeFastifySchemas(normalizedSchema.swaggerSchema, currentSwaggerSchema);
|
|
298
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
299
|
+
// @ts-ignore
|
|
292
300
|
const currentZod = currentConfig.zod;
|
|
293
301
|
const mergedZod = this.mergeZodSchemas(normalizedSchema.zod, currentZod);
|
|
294
302
|
if (mergedZod) {
|
|
@@ -297,8 +305,31 @@ export class ServerImpl extends Macroable {
|
|
|
297
305
|
currentConfig.zod = mergedZod;
|
|
298
306
|
}
|
|
299
307
|
fastifyOptions.config = currentConfig;
|
|
308
|
+
this.configureSwaggerTransform(fastifyOptions);
|
|
300
309
|
return fastifyOptions;
|
|
301
310
|
}
|
|
311
|
+
configureSwaggerTransform(fastifyOptions) {
|
|
312
|
+
const config = fastifyOptions?.config;
|
|
313
|
+
if (!config?.swaggerSchema) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const customTransform = config.swaggerTransform;
|
|
317
|
+
if (customTransform === false) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
config.swaggerTransform = (args) => {
|
|
321
|
+
const transformed = Is.Function(customTransform)
|
|
322
|
+
? customTransform(args)
|
|
323
|
+
: args;
|
|
324
|
+
if (transformed === false) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
...transformed,
|
|
329
|
+
schema: this.mergeFastifySchemas(transformed?.schema || args.schema, config.swaggerSchema)
|
|
330
|
+
};
|
|
331
|
+
};
|
|
332
|
+
}
|
|
302
333
|
getOpenApiRouteSchema(options) {
|
|
303
334
|
const paths = Config.get('openapi.paths', {});
|
|
304
335
|
const methods = options.methods || [];
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { HttpException } from '#src/exceptions/HttpException';
|
|
2
|
-
export class ResponseValidationException extends HttpException {
|
|
3
|
-
constructor(error) {
|
|
4
|
-
const name = 'ResponseValidationException';
|
|
5
|
-
const code = 'E_RESPONSE_VALIDATION_ERROR';
|
|
6
|
-
const status = 500;
|
|
7
|
-
const message = 'The server failed to generate a valid response.';
|
|
8
|
-
const details = error.issues;
|
|
9
|
-
super({ name, message, status, code, details });
|
|
10
|
-
}
|
|
11
|
-
}
|