@grupodiariodaregiao/bunstone 0.2.7 → 0.2.9
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/index.js +115 -84
- package/dist/lib/http-exceptions.d.ts +2 -2
- package/lib/app-startup.ts +42 -1
- package/lib/http-exceptions.ts +7 -2
- package/lib/http-params.ts +329 -324
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -67726,6 +67726,85 @@ var ZodRealError = $constructor("ZodError", initializer2, {
|
|
|
67726
67726
|
Parent: Error
|
|
67727
67727
|
});
|
|
67728
67728
|
|
|
67729
|
+
// lib/http-exceptions.ts
|
|
67730
|
+
class HttpException extends Error {
|
|
67731
|
+
status;
|
|
67732
|
+
response;
|
|
67733
|
+
constructor(response, status) {
|
|
67734
|
+
const responseObj = typeof response === "string" ? { message: response } : response;
|
|
67735
|
+
super(JSON.stringify(responseObj));
|
|
67736
|
+
this.status = status;
|
|
67737
|
+
this.response = responseObj;
|
|
67738
|
+
Object.setPrototypeOf(this, HttpException.prototype);
|
|
67739
|
+
}
|
|
67740
|
+
getResponse() {
|
|
67741
|
+
return this.response;
|
|
67742
|
+
}
|
|
67743
|
+
getStatus() {
|
|
67744
|
+
return this.status;
|
|
67745
|
+
}
|
|
67746
|
+
}
|
|
67747
|
+
|
|
67748
|
+
class BadRequestException extends HttpException {
|
|
67749
|
+
constructor(response = "Bad Request") {
|
|
67750
|
+
super(response, 400);
|
|
67751
|
+
}
|
|
67752
|
+
}
|
|
67753
|
+
|
|
67754
|
+
class UnauthorizedException extends HttpException {
|
|
67755
|
+
constructor(response = "Unauthorized") {
|
|
67756
|
+
super(response, 401);
|
|
67757
|
+
}
|
|
67758
|
+
}
|
|
67759
|
+
|
|
67760
|
+
class ForbiddenException extends HttpException {
|
|
67761
|
+
constructor(response = "Forbidden") {
|
|
67762
|
+
super(response, 403);
|
|
67763
|
+
}
|
|
67764
|
+
}
|
|
67765
|
+
|
|
67766
|
+
class NotFoundException extends HttpException {
|
|
67767
|
+
constructor(response = "Not Found") {
|
|
67768
|
+
super(response, 404);
|
|
67769
|
+
}
|
|
67770
|
+
}
|
|
67771
|
+
|
|
67772
|
+
class ConflictException extends HttpException {
|
|
67773
|
+
constructor(response = "Conflict") {
|
|
67774
|
+
super(response, 409);
|
|
67775
|
+
}
|
|
67776
|
+
}
|
|
67777
|
+
|
|
67778
|
+
class UnprocessableEntityException extends HttpException {
|
|
67779
|
+
constructor(response = "Unprocessable Entity") {
|
|
67780
|
+
super(response, 422);
|
|
67781
|
+
}
|
|
67782
|
+
}
|
|
67783
|
+
|
|
67784
|
+
class InternalServerErrorException extends HttpException {
|
|
67785
|
+
constructor(response = "Internal Server Error") {
|
|
67786
|
+
super(response, 500);
|
|
67787
|
+
}
|
|
67788
|
+
}
|
|
67789
|
+
|
|
67790
|
+
class OkResponse extends HttpException {
|
|
67791
|
+
constructor(response = "OK") {
|
|
67792
|
+
super(response, 200);
|
|
67793
|
+
}
|
|
67794
|
+
}
|
|
67795
|
+
|
|
67796
|
+
class CreatedResponse extends HttpException {
|
|
67797
|
+
constructor(response = "Created") {
|
|
67798
|
+
super(response, 201);
|
|
67799
|
+
}
|
|
67800
|
+
}
|
|
67801
|
+
|
|
67802
|
+
class NoContentResponse extends HttpException {
|
|
67803
|
+
constructor() {
|
|
67804
|
+
super("", 204);
|
|
67805
|
+
}
|
|
67806
|
+
}
|
|
67807
|
+
|
|
67729
67808
|
// lib/utils/is-zod-schema.ts
|
|
67730
67809
|
var isZodSchema = (obj) => {
|
|
67731
67810
|
return obj && typeof obj === "object" && typeof obj.parse === "function";
|
|
@@ -67852,8 +67931,15 @@ async function processParameters(request, target, propertyKey) {
|
|
|
67852
67931
|
}
|
|
67853
67932
|
} catch (e2) {
|
|
67854
67933
|
if (e2 instanceof ZodError) {
|
|
67855
|
-
|
|
67934
|
+
throw new BadRequestException({
|
|
67935
|
+
status: 400,
|
|
67936
|
+
errors: e2.issues.map((issue) => ({
|
|
67937
|
+
field: issue.path.join("."),
|
|
67938
|
+
message: issue.message
|
|
67939
|
+
}))
|
|
67940
|
+
});
|
|
67856
67941
|
}
|
|
67942
|
+
throw e2;
|
|
67857
67943
|
}
|
|
67858
67944
|
}
|
|
67859
67945
|
return args;
|
|
@@ -67965,10 +68051,7 @@ function appendValue(formData, key, value) {
|
|
|
67965
68051
|
}
|
|
67966
68052
|
}
|
|
67967
68053
|
function badRequest(message) {
|
|
67968
|
-
throw new
|
|
67969
|
-
status: 400,
|
|
67970
|
-
headers: { "content-type": "application/json" }
|
|
67971
|
-
});
|
|
68054
|
+
throw new BadRequestException(message);
|
|
67972
68055
|
}
|
|
67973
68056
|
|
|
67974
68057
|
// lib/adapters/form-data.ts
|
|
@@ -68283,84 +68366,6 @@ QueryBus = __legacyDecorateClassTS([
|
|
|
68283
68366
|
Injectable()
|
|
68284
68367
|
], QueryBus);
|
|
68285
68368
|
|
|
68286
|
-
// lib/http-exceptions.ts
|
|
68287
|
-
class HttpException extends Error {
|
|
68288
|
-
response;
|
|
68289
|
-
status;
|
|
68290
|
-
constructor(response, status) {
|
|
68291
|
-
super(typeof response === "string" ? response : JSON.stringify(response));
|
|
68292
|
-
this.response = response;
|
|
68293
|
-
this.status = status;
|
|
68294
|
-
Object.setPrototypeOf(this, HttpException.prototype);
|
|
68295
|
-
}
|
|
68296
|
-
getResponse() {
|
|
68297
|
-
return this.response;
|
|
68298
|
-
}
|
|
68299
|
-
getStatus() {
|
|
68300
|
-
return this.status;
|
|
68301
|
-
}
|
|
68302
|
-
}
|
|
68303
|
-
|
|
68304
|
-
class BadRequestException extends HttpException {
|
|
68305
|
-
constructor(response = "Bad Request") {
|
|
68306
|
-
super(response, 400);
|
|
68307
|
-
}
|
|
68308
|
-
}
|
|
68309
|
-
|
|
68310
|
-
class UnauthorizedException extends HttpException {
|
|
68311
|
-
constructor(response = "Unauthorized") {
|
|
68312
|
-
super(response, 401);
|
|
68313
|
-
}
|
|
68314
|
-
}
|
|
68315
|
-
|
|
68316
|
-
class ForbiddenException extends HttpException {
|
|
68317
|
-
constructor(response = "Forbidden") {
|
|
68318
|
-
super(response, 403);
|
|
68319
|
-
}
|
|
68320
|
-
}
|
|
68321
|
-
|
|
68322
|
-
class NotFoundException extends HttpException {
|
|
68323
|
-
constructor(response = "Not Found") {
|
|
68324
|
-
super(response, 404);
|
|
68325
|
-
}
|
|
68326
|
-
}
|
|
68327
|
-
|
|
68328
|
-
class ConflictException extends HttpException {
|
|
68329
|
-
constructor(response = "Conflict") {
|
|
68330
|
-
super(response, 409);
|
|
68331
|
-
}
|
|
68332
|
-
}
|
|
68333
|
-
|
|
68334
|
-
class UnprocessableEntityException extends HttpException {
|
|
68335
|
-
constructor(response = "Unprocessable Entity") {
|
|
68336
|
-
super(response, 422);
|
|
68337
|
-
}
|
|
68338
|
-
}
|
|
68339
|
-
|
|
68340
|
-
class InternalServerErrorException extends HttpException {
|
|
68341
|
-
constructor(response = "Internal Server Error") {
|
|
68342
|
-
super(response, 500);
|
|
68343
|
-
}
|
|
68344
|
-
}
|
|
68345
|
-
|
|
68346
|
-
class OkResponse extends HttpException {
|
|
68347
|
-
constructor(response = "OK") {
|
|
68348
|
-
super(response, 200);
|
|
68349
|
-
}
|
|
68350
|
-
}
|
|
68351
|
-
|
|
68352
|
-
class CreatedResponse extends HttpException {
|
|
68353
|
-
constructor(response = "Created") {
|
|
68354
|
-
super(response, 201);
|
|
68355
|
-
}
|
|
68356
|
-
}
|
|
68357
|
-
|
|
68358
|
-
class NoContentResponse extends HttpException {
|
|
68359
|
-
constructor() {
|
|
68360
|
-
super("", 204);
|
|
68361
|
-
}
|
|
68362
|
-
}
|
|
68363
|
-
|
|
68364
68369
|
// lib/openapi.ts
|
|
68365
68370
|
var import_reflect_metadata12 = __toESM(require_Reflect(), 1);
|
|
68366
68371
|
var API_TAGS_METADATA = "dip:openapi:tags";
|
|
@@ -68612,11 +68617,37 @@ class AppStartup {
|
|
|
68612
68617
|
AppStartup.elysia.error({
|
|
68613
68618
|
HttpException
|
|
68614
68619
|
});
|
|
68615
|
-
AppStartup.elysia.onError(({ error, set }) => {
|
|
68620
|
+
AppStartup.elysia.onError(({ code: code2, error, set }) => {
|
|
68616
68621
|
if (error instanceof HttpException) {
|
|
68617
68622
|
set.status = error.getStatus();
|
|
68618
68623
|
return error.getResponse();
|
|
68619
68624
|
}
|
|
68625
|
+
if (code2 === "VALIDATION") {
|
|
68626
|
+
set.status = 400;
|
|
68627
|
+
const extractField = (path) => {
|
|
68628
|
+
if (Array.isArray(path)) {
|
|
68629
|
+
return path.join(".").replace(/^body\./, "").replace(/^query\./, "").replace(/^params\./, "");
|
|
68630
|
+
}
|
|
68631
|
+
if (typeof path === "string") {
|
|
68632
|
+
return path.replace(/^body\./, "").replace(/^query\./, "").replace(/^params\./, "");
|
|
68633
|
+
}
|
|
68634
|
+
return "";
|
|
68635
|
+
};
|
|
68636
|
+
const allErrors = error.all;
|
|
68637
|
+
const errors = Array.isArray(allErrors) && allErrors.length > 0 ? allErrors.map((err) => ({
|
|
68638
|
+
field: extractField(err.path),
|
|
68639
|
+
message: err.message
|
|
68640
|
+
})) : [
|
|
68641
|
+
{
|
|
68642
|
+
field: extractField(error.path),
|
|
68643
|
+
message: error.message
|
|
68644
|
+
}
|
|
68645
|
+
];
|
|
68646
|
+
return {
|
|
68647
|
+
status: 400,
|
|
68648
|
+
errors
|
|
68649
|
+
};
|
|
68650
|
+
}
|
|
68620
68651
|
return error;
|
|
68621
68652
|
});
|
|
68622
68653
|
if (options?.cors) {
|
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* Base class for all HTTP exceptions.
|
|
3
3
|
*/
|
|
4
4
|
export declare class HttpException extends Error {
|
|
5
|
-
readonly response: string | object;
|
|
6
5
|
readonly status: number;
|
|
6
|
+
readonly response: object;
|
|
7
7
|
constructor(response: string | object, status: number);
|
|
8
|
-
getResponse():
|
|
8
|
+
getResponse(): object;
|
|
9
9
|
getStatus(): number;
|
|
10
10
|
}
|
|
11
11
|
/**
|
package/lib/app-startup.ts
CHANGED
|
@@ -79,11 +79,52 @@ export class AppStartup {
|
|
|
79
79
|
HttpException,
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
AppStartup.elysia.onError(({ error, set }) => {
|
|
82
|
+
AppStartup.elysia.onError(({ code, error, set }) => {
|
|
83
83
|
if (error instanceof HttpException) {
|
|
84
84
|
set.status = error.getStatus();
|
|
85
85
|
return error.getResponse();
|
|
86
86
|
}
|
|
87
|
+
|
|
88
|
+
if (code === "VALIDATION") {
|
|
89
|
+
set.status = 400;
|
|
90
|
+
|
|
91
|
+
const extractField = (path: any): string => {
|
|
92
|
+
if (Array.isArray(path)) {
|
|
93
|
+
return path
|
|
94
|
+
.join(".")
|
|
95
|
+
.replace(/^body\./, "")
|
|
96
|
+
.replace(/^query\./, "")
|
|
97
|
+
.replace(/^params\./, "");
|
|
98
|
+
}
|
|
99
|
+
if (typeof path === "string") {
|
|
100
|
+
return path
|
|
101
|
+
.replace(/^body\./, "")
|
|
102
|
+
.replace(/^query\./, "")
|
|
103
|
+
.replace(/^params\./, "");
|
|
104
|
+
}
|
|
105
|
+
return "";
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const allErrors = (error as any).all;
|
|
109
|
+
const errors =
|
|
110
|
+
Array.isArray(allErrors) && allErrors.length > 0
|
|
111
|
+
? allErrors.map((err: any) => ({
|
|
112
|
+
field: extractField(err.path),
|
|
113
|
+
message: err.message,
|
|
114
|
+
}))
|
|
115
|
+
: [
|
|
116
|
+
{
|
|
117
|
+
field: extractField((error as any).path),
|
|
118
|
+
message: error.message,
|
|
119
|
+
},
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
status: 400,
|
|
124
|
+
errors,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
87
128
|
return error;
|
|
88
129
|
});
|
|
89
130
|
|
package/lib/http-exceptions.ts
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
* Base class for all HTTP exceptions.
|
|
3
3
|
*/
|
|
4
4
|
export class HttpException extends Error {
|
|
5
|
+
public readonly response: object;
|
|
6
|
+
|
|
5
7
|
constructor(
|
|
6
|
-
|
|
8
|
+
response: string | object,
|
|
7
9
|
public readonly status: number,
|
|
8
10
|
) {
|
|
9
|
-
|
|
11
|
+
const responseObj =
|
|
12
|
+
typeof response === "string" ? { message: response } : response;
|
|
13
|
+
super(JSON.stringify(responseObj));
|
|
14
|
+
this.response = responseObj;
|
|
10
15
|
Object.setPrototypeOf(this, HttpException.prototype);
|
|
11
16
|
}
|
|
12
17
|
|
package/lib/http-params.ts
CHANGED
|
@@ -1,395 +1,400 @@
|
|
|
1
1
|
import "reflect-metadata";
|
|
2
2
|
import { ZodError, type ZodType } from "zod/v4";
|
|
3
3
|
import { PARAM_METADATA_KEY } from "./constants";
|
|
4
|
+
import { BadRequestException } from "./http-exceptions";
|
|
4
5
|
import { isZodSchema } from "./utils/is-zod-schema";
|
|
5
6
|
|
|
6
7
|
export enum ParamType {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
8
|
+
BODY = "body",
|
|
9
|
+
QUERY = "query",
|
|
10
|
+
PARAM = "param",
|
|
11
|
+
HEADER = "header",
|
|
12
|
+
REQUEST = "request",
|
|
13
|
+
FORM_DATA = "form-data",
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function setParamMetadata(
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
target: any,
|
|
18
|
+
propertyKey: string | symbol,
|
|
19
|
+
parameterIndex: number,
|
|
20
|
+
type: ParamType,
|
|
21
|
+
key?: string,
|
|
22
|
+
options?: unknown,
|
|
22
23
|
) {
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
const existingParams =
|
|
25
|
+
Reflect.getOwnMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
existingParams.push({ index: parameterIndex, type, key, options });
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
Reflect.defineMetadata(
|
|
30
|
+
PARAM_METADATA_KEY,
|
|
31
|
+
existingParams,
|
|
32
|
+
target,
|
|
33
|
+
propertyKey,
|
|
34
|
+
);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function Body(schema?: ZodType): any;
|
|
37
38
|
export function Body(): any {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
39
|
+
if (arguments.length === 1) {
|
|
40
|
+
const arg = arguments[0] as ZodType;
|
|
41
|
+
if (isZodSchema(arg)) {
|
|
42
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
43
|
+
setParamMetadata(
|
|
44
|
+
target,
|
|
45
|
+
propertyKey as string,
|
|
46
|
+
parameterIndex,
|
|
47
|
+
ParamType.BODY,
|
|
48
|
+
undefined,
|
|
49
|
+
{ zodSchema: arg },
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
56
|
+
setParamMetadata(
|
|
57
|
+
target,
|
|
58
|
+
propertyKey as string,
|
|
59
|
+
parameterIndex,
|
|
60
|
+
ParamType.BODY,
|
|
61
|
+
);
|
|
62
|
+
};
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
export function Param(schema?: ZodType): any;
|
|
65
66
|
export function Param(key?: string): any;
|
|
66
67
|
export function Param(): any {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
68
|
+
let key: string | undefined;
|
|
69
|
+
if (arguments.length === 1) {
|
|
70
|
+
if (isZodSchema(arguments[0])) {
|
|
71
|
+
return function (target: any, propertyKey: any, parameterIndex: any) {
|
|
72
|
+
setParamMetadata(
|
|
73
|
+
target,
|
|
74
|
+
propertyKey as string,
|
|
75
|
+
parameterIndex,
|
|
76
|
+
ParamType.PARAM,
|
|
77
|
+
undefined,
|
|
78
|
+
{
|
|
79
|
+
zodSchema: arguments[0] as ZodType,
|
|
80
|
+
},
|
|
81
|
+
);
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
key = arguments[0] as string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
88
|
+
setParamMetadata(
|
|
89
|
+
target,
|
|
90
|
+
propertyKey as string,
|
|
91
|
+
parameterIndex,
|
|
92
|
+
ParamType.PARAM,
|
|
93
|
+
key,
|
|
94
|
+
);
|
|
95
|
+
};
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
export function Query(schema?: ZodType): any;
|
|
98
99
|
export function Query(key?: string): any;
|
|
99
100
|
export function Query(): any {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
101
|
+
let key: string | undefined;
|
|
102
|
+
if (arguments.length === 1) {
|
|
103
|
+
if (isZodSchema(arguments[0])) {
|
|
104
|
+
return function (target: any, propertyKey: any, parameterIndex: any) {
|
|
105
|
+
setParamMetadata(
|
|
106
|
+
target,
|
|
107
|
+
propertyKey as string,
|
|
108
|
+
parameterIndex,
|
|
109
|
+
ParamType.QUERY,
|
|
110
|
+
undefined,
|
|
111
|
+
{
|
|
112
|
+
zodSchema: arguments[0] as ZodType,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
key = arguments[0] as string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
121
|
+
setParamMetadata(
|
|
122
|
+
target,
|
|
123
|
+
propertyKey as string,
|
|
124
|
+
parameterIndex,
|
|
125
|
+
ParamType.QUERY,
|
|
126
|
+
key,
|
|
127
|
+
);
|
|
128
|
+
};
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
export function Header(key: string): any {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
132
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
133
|
+
setParamMetadata(
|
|
134
|
+
target,
|
|
135
|
+
propertyKey,
|
|
136
|
+
parameterIndex,
|
|
137
|
+
ParamType.HEADER,
|
|
138
|
+
key,
|
|
139
|
+
);
|
|
140
|
+
};
|
|
140
141
|
}
|
|
141
142
|
|
|
142
143
|
export function Request(): any {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
return (target: any, propertyKey: any, parameterIndex: any) => {
|
|
145
|
+
setParamMetadata(target, propertyKey, parameterIndex, ParamType.REQUEST);
|
|
146
|
+
};
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
export async function processParameters(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
request: any,
|
|
151
|
+
target: any,
|
|
152
|
+
propertyKey: string,
|
|
152
153
|
): Promise<any[]> {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
154
|
+
const paramMetadata =
|
|
155
|
+
Reflect.getOwnMetadata(
|
|
156
|
+
PARAM_METADATA_KEY,
|
|
157
|
+
Object.getPrototypeOf(target),
|
|
158
|
+
propertyKey,
|
|
159
|
+
) || [];
|
|
160
|
+
|
|
161
|
+
const paramTypes =
|
|
162
|
+
Reflect.getMetadata("design:paramtypes", target, propertyKey) || [];
|
|
163
|
+
|
|
164
|
+
const args: any[] = new Array(paramTypes.length);
|
|
165
|
+
let cachedFormData: FormData | null = null;
|
|
166
|
+
|
|
167
|
+
for (const metadata of paramMetadata) {
|
|
168
|
+
const { index, type, key } = metadata;
|
|
169
|
+
|
|
170
|
+
switch (type) {
|
|
171
|
+
case ParamType.BODY:
|
|
172
|
+
try {
|
|
173
|
+
args[index] = request.body;
|
|
174
|
+
} catch (_e) {
|
|
175
|
+
args[index] = null;
|
|
176
|
+
}
|
|
177
|
+
break;
|
|
178
|
+
|
|
179
|
+
case ParamType.QUERY:
|
|
180
|
+
if (key) {
|
|
181
|
+
args[index] = request.query?.[key];
|
|
182
|
+
} else {
|
|
183
|
+
args[index] = request.query;
|
|
184
|
+
}
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case ParamType.PARAM:
|
|
188
|
+
if (key === undefined) {
|
|
189
|
+
args[index] = request.params;
|
|
190
|
+
} else {
|
|
191
|
+
args[index] = request.params?.[key];
|
|
192
|
+
}
|
|
193
|
+
break;
|
|
194
|
+
|
|
195
|
+
case ParamType.HEADER:
|
|
196
|
+
if (key) {
|
|
197
|
+
args[index] = request.headers?.[key];
|
|
198
|
+
}
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case ParamType.REQUEST:
|
|
202
|
+
args[index] = request;
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
case ParamType.FORM_DATA:
|
|
206
|
+
cachedFormData = cachedFormData || (await readFormData(request));
|
|
207
|
+
args[index] = extractFormDataPayload(
|
|
208
|
+
cachedFormData,
|
|
209
|
+
metadata.options as FormDataOptions | undefined,
|
|
210
|
+
);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
if (metadata.options?.zodSchema) {
|
|
216
|
+
const zodSchema = metadata.options.zodSchema as ZodType;
|
|
217
|
+
if (isZodSchema(zodSchema)) {
|
|
218
|
+
args[index] = zodSchema.parse(args[index]);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
} catch (e) {
|
|
222
|
+
if (e instanceof ZodError) {
|
|
223
|
+
throw new BadRequestException({
|
|
224
|
+
status: 400,
|
|
225
|
+
errors: e.issues.map((issue) => ({
|
|
226
|
+
field: issue.path.join("."),
|
|
227
|
+
message: issue.message,
|
|
228
|
+
})),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
throw e;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return args;
|
|
228
236
|
}
|
|
229
237
|
|
|
230
238
|
export type FormDataOptions = {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
239
|
+
fileField?: string;
|
|
240
|
+
allowedTypes?: string[];
|
|
241
|
+
jsonField?: string;
|
|
234
242
|
};
|
|
235
243
|
|
|
236
244
|
export type FormDataFields = Record<string, string | string[]>;
|
|
237
245
|
|
|
238
246
|
export type FormDataPayload = {
|
|
239
|
-
|
|
240
|
-
|
|
247
|
+
files: File[];
|
|
248
|
+
json?: unknown;
|
|
241
249
|
};
|
|
242
250
|
|
|
243
251
|
const FORM_DATA_CACHE = Symbol.for("dip:form-data-cache");
|
|
244
252
|
|
|
245
253
|
async function readFormData(request: any): Promise<FormData> {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
254
|
+
if (request?.[FORM_DATA_CACHE]) {
|
|
255
|
+
return request[FORM_DATA_CACHE];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const existingBody = request?.body;
|
|
259
|
+
const bodyAsFormData = tryResolveFromBody(existingBody);
|
|
260
|
+
if (bodyAsFormData) {
|
|
261
|
+
request[FORM_DATA_CACHE] = bodyAsFormData;
|
|
262
|
+
return bodyAsFormData;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const requestLike = request?.request || request?.raw || request;
|
|
266
|
+
|
|
267
|
+
if (!requestLike || typeof requestLike.formData !== "function") {
|
|
268
|
+
throw new Error("FormData is not available on this request.");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let formData: FormData;
|
|
272
|
+
try {
|
|
273
|
+
formData =
|
|
274
|
+
typeof requestLike.clone === "function"
|
|
275
|
+
? await requestLike.clone().formData()
|
|
276
|
+
: await requestLike.formData();
|
|
277
|
+
} catch (err: any) {
|
|
278
|
+
const fallback = tryResolveFromBody(existingBody);
|
|
279
|
+
if (fallback) {
|
|
280
|
+
request[FORM_DATA_CACHE] = fallback;
|
|
281
|
+
return fallback;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const reason =
|
|
285
|
+
err instanceof Error
|
|
286
|
+
? err.message
|
|
287
|
+
: "Body already consumed or unreadable";
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Could not read multipart form data from the request. ${reason}`,
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!(formData instanceof FormData)) {
|
|
294
|
+
throw new Error("Could not read multipart form data from the request.");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
request[FORM_DATA_CACHE] = formData;
|
|
298
|
+
return formData;
|
|
291
299
|
}
|
|
292
300
|
|
|
293
301
|
function extractFormDataPayload(
|
|
294
|
-
|
|
295
|
-
|
|
302
|
+
formData: FormData,
|
|
303
|
+
options: FormDataOptions = {},
|
|
296
304
|
): FormDataPayload {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
305
|
+
const { fileField, allowedTypes, jsonField } = options;
|
|
306
|
+
const files: File[] = [];
|
|
307
|
+
|
|
308
|
+
const allowed = (allowedTypes || []).map((item) => item.toLowerCase());
|
|
309
|
+
const getFiles = fileField
|
|
310
|
+
? formData.getAll(fileField)
|
|
311
|
+
: Array.from(formData.values());
|
|
312
|
+
|
|
313
|
+
for (const value of getFiles) {
|
|
314
|
+
if (value instanceof File) {
|
|
315
|
+
if (allowed.length > 0 && !isAllowedFileType(value, allowed)) {
|
|
316
|
+
badRequest(
|
|
317
|
+
`File type for "${
|
|
318
|
+
value.name
|
|
319
|
+
}" is not allowed. Allowed: ${allowed.join(", ")}`,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
files.push(value);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let parsedJson: unknown;
|
|
328
|
+
|
|
329
|
+
if (jsonField) {
|
|
330
|
+
const rawJson = formData.get(jsonField);
|
|
331
|
+
|
|
332
|
+
if (typeof rawJson === "string") {
|
|
333
|
+
try {
|
|
334
|
+
parsedJson = JSON.parse(rawJson);
|
|
335
|
+
} catch {
|
|
336
|
+
badRequest(`Failed to parse JSON field "${jsonField}".`);
|
|
337
|
+
}
|
|
338
|
+
} else if (rawJson !== null) {
|
|
339
|
+
badRequest(`JSON field "${jsonField}" must be a string value.`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
files,
|
|
345
|
+
json: parsedJson,
|
|
346
|
+
};
|
|
339
347
|
}
|
|
340
348
|
|
|
341
349
|
function isAllowedFileType(file: File, allowedTypes: string[]): boolean {
|
|
342
|
-
|
|
343
|
-
|
|
350
|
+
const mime = file.type?.toLowerCase?.() || "";
|
|
351
|
+
const extension = file.name.split(".").pop()?.toLowerCase();
|
|
344
352
|
|
|
345
|
-
|
|
346
|
-
|
|
353
|
+
if (mime && allowedTypes.includes(mime)) return true;
|
|
354
|
+
if (extension && allowedTypes.includes(extension)) return true;
|
|
347
355
|
|
|
348
|
-
|
|
356
|
+
return allowedTypes.length === 0;
|
|
349
357
|
}
|
|
350
358
|
|
|
351
359
|
function isFormDataLike(value: unknown): value is FormData {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
return (
|
|
361
|
+
typeof value === "object" &&
|
|
362
|
+
value !== null &&
|
|
363
|
+
typeof (value as FormData).get === "function" &&
|
|
364
|
+
typeof (value as FormData).entries === "function"
|
|
365
|
+
);
|
|
358
366
|
}
|
|
359
367
|
|
|
360
368
|
function tryResolveFromBody(body: unknown): FormData | null {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
369
|
+
if (!body) return null;
|
|
370
|
+
if (isFormDataLike(body)) return body;
|
|
371
|
+
if (typeof body !== "object") return null;
|
|
372
|
+
|
|
373
|
+
const formData = new (globalThis as any).FormData();
|
|
374
|
+
for (const [key, value] of Object.entries(body)) {
|
|
375
|
+
if (value === undefined || value === null) continue;
|
|
376
|
+
if (Array.isArray(value)) {
|
|
377
|
+
value.forEach((item) => {
|
|
378
|
+
appendValue(formData, key, item);
|
|
379
|
+
});
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
appendValue(formData, key, value);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return formData;
|
|
378
386
|
}
|
|
379
387
|
|
|
380
388
|
function appendValue(formData: FormData, key: string, value: unknown) {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
389
|
+
if (value instanceof File || value instanceof Blob) {
|
|
390
|
+
formData.append(key, value);
|
|
391
|
+
} else if (typeof value === "object") {
|
|
392
|
+
formData.append(key, JSON.stringify(value));
|
|
393
|
+
} else {
|
|
394
|
+
formData.append(key, String(value));
|
|
395
|
+
}
|
|
388
396
|
}
|
|
389
397
|
|
|
390
398
|
function badRequest(message: string): never {
|
|
391
|
-
|
|
392
|
-
status: 400,
|
|
393
|
-
headers: { "content-type": "application/json" },
|
|
394
|
-
});
|
|
399
|
+
throw new BadRequestException(message);
|
|
395
400
|
}
|