@tsed/platform-params 8.0.1 → 8.0.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.
Files changed (48) hide show
  1. package/lib/types/decorators/locals.d.ts +1 -1
  2. package/package.json +14 -13
  3. package/src/builder/PlatformParams.spec.ts +431 -0
  4. package/src/builder/PlatformParams.ts +86 -0
  5. package/src/decorators/bodyParams.spec.ts +121 -0
  6. package/src/decorators/bodyParams.ts +93 -0
  7. package/src/decorators/context.spec.ts +16 -0
  8. package/src/decorators/context.ts +70 -0
  9. package/src/decorators/cookies.spec.ts +19 -0
  10. package/src/decorators/cookies.ts +102 -0
  11. package/src/decorators/headerParams.spec.ts +16 -0
  12. package/src/decorators/headerParams.ts +49 -0
  13. package/src/decorators/locals.spec.ts +16 -0
  14. package/src/decorators/locals.ts +57 -0
  15. package/src/decorators/paramFn.ts +14 -0
  16. package/src/decorators/pathParams.spec.ts +58 -0
  17. package/src/decorators/pathParams.ts +97 -0
  18. package/src/decorators/queryParams.spec.ts +58 -0
  19. package/src/decorators/queryParams.ts +102 -0
  20. package/src/decorators/session.spec.ts +47 -0
  21. package/src/decorators/session.ts +51 -0
  22. package/src/decorators/useDeserialization.ts +14 -0
  23. package/src/decorators/useParam.spec.ts +82 -0
  24. package/src/decorators/useParam.ts +38 -0
  25. package/src/decorators/useParamExpression.ts +16 -0
  26. package/src/decorators/useParamType.ts +36 -0
  27. package/src/decorators/usePipe.ts +21 -0
  28. package/src/decorators/useType.ts +35 -0
  29. package/src/decorators/useValidation.ts +18 -0
  30. package/src/domain/ParamMetadata.ts +1 -0
  31. package/src/domain/ParamOptions.ts +12 -0
  32. package/src/domain/ParamTypes.ts +37 -0
  33. package/src/errors/ParamValidationError.spec.ts +152 -0
  34. package/src/errors/ParamValidationError.ts +38 -0
  35. package/src/errors/RequiredValidationError.spec.ts +49 -0
  36. package/src/errors/RequiredValidationError.ts +30 -0
  37. package/src/errors/ValidationError.spec.ts +18 -0
  38. package/src/errors/ValidationError.ts +11 -0
  39. package/src/exports.ts +1 -0
  40. package/src/index.ts +31 -0
  41. package/src/pipes/DeserializerPipe.spec.ts +39 -0
  42. package/src/pipes/DeserializerPipe.ts +15 -0
  43. package/src/pipes/ParseExpressionPipe.spec.ts +242 -0
  44. package/src/pipes/ParseExpressionPipe.ts +33 -0
  45. package/src/pipes/ValidationPipe.spec.ts +645 -0
  46. package/src/pipes/ValidationPipe.ts +112 -0
  47. package/src/utils/mapParamsOptions.spec.ts +28 -0
  48. package/src/utils/mapParamsOptions.ts +34 -0
@@ -0,0 +1,38 @@
1
+ import {nameOf} from "@tsed/core";
2
+ import {BadRequest} from "@tsed/exceptions";
3
+ import {JsonParameterStore} from "@tsed/schema";
4
+
5
+ import {ValidationError} from "./ValidationError.js";
6
+
7
+ export class ParamValidationError extends BadRequest {
8
+ public name: string = "PARAM_VALIDATION_ERROR";
9
+ public dataPath: string;
10
+ public requestType: string;
11
+
12
+ static from(metadata: JsonParameterStore, origin: any = {}) {
13
+ if (origin instanceof ValidationError || origin instanceof BadRequest) {
14
+ const name = nameOf(metadata.paramType)
15
+ .toLowerCase()
16
+ .replace(/parse|params|filter/gi, "");
17
+ const expression = metadata.expression;
18
+ const dataPath = `${name}${expression ? "." + expression : ""}`;
19
+ const message = `Bad request on parameter "request.${dataPath}".\n${origin.message}`.trim();
20
+
21
+ const error = new ParamValidationError(message);
22
+ error.dataPath = String(metadata.expression) || "";
23
+ error.requestType = nameOf(metadata.paramType);
24
+ error.origin = origin.origin || origin;
25
+
26
+ if (error.origin?.errors) {
27
+ error.origin?.errors.forEach((error: any) => {
28
+ error.requestPath = name;
29
+ error.dataPath = error.dataPath || (expression ? `.${expression}` : "");
30
+ });
31
+ }
32
+
33
+ return error;
34
+ }
35
+
36
+ return origin;
37
+ }
38
+ }
@@ -0,0 +1,49 @@
1
+ import {RequiredValidationError} from "./RequiredValidationError.js";
2
+
3
+ describe("RequiredValidationError", () => {
4
+ it("should have a message", () => {
5
+ const error = RequiredValidationError.from({
6
+ paramType: "name",
7
+ expression: "expression"
8
+ } as any);
9
+ expect(error.message).toEqual("It should have required parameter 'expression'");
10
+ expect(error.name).toEqual("REQUIRED_VALIDATION_ERROR");
11
+ expect({
12
+ name: error.name,
13
+ status: error.status,
14
+ headers: error.headers,
15
+ type: error.type,
16
+ message: error.message,
17
+ errors: [
18
+ {
19
+ dataPath: error.errors[0].dataPath,
20
+ keyword: error.errors[0].keyword,
21
+ message: error.errors[0].message,
22
+ modelName: error.errors[0].modelName,
23
+ params: {
24
+ missingProperty: error.errors[0].params.missingProperty
25
+ },
26
+ schemaPath: error.errors[0].schemaPath
27
+ }
28
+ ]
29
+ }).toEqual({
30
+ name: "REQUIRED_VALIDATION_ERROR",
31
+ status: 400,
32
+ headers: {},
33
+ type: "HTTP_EXCEPTION",
34
+ message: "It should have required parameter 'expression'",
35
+ errors: [
36
+ {
37
+ dataPath: "",
38
+ keyword: "required",
39
+ message: "It should have required parameter 'expression'",
40
+ modelName: "name",
41
+ params: {
42
+ missingProperty: "expression"
43
+ },
44
+ schemaPath: "#/required"
45
+ }
46
+ ]
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,30 @@
1
+ import {nameOf} from "@tsed/core";
2
+ import {JsonParameterStore} from "@tsed/schema";
3
+
4
+ import {ValidationError} from "./ValidationError.js";
5
+
6
+ export class RequiredValidationError extends ValidationError {
7
+ public name: string = "REQUIRED_VALIDATION_ERROR";
8
+
9
+ static from(metadata: JsonParameterStore) {
10
+ const name = nameOf(metadata.paramType);
11
+ const expression = metadata.expression;
12
+ const type = name.toLowerCase().replace(/parse|params|filter/gi, "");
13
+ const message = `It should have required parameter '${expression}'`;
14
+
15
+ const errors = [
16
+ {
17
+ dataPath: "",
18
+ keyword: "required",
19
+ message,
20
+ modelName: type,
21
+ params: {
22
+ missingProperty: expression
23
+ },
24
+ schemaPath: "#/required"
25
+ }
26
+ ];
27
+
28
+ return new RequiredValidationError(message, errors);
29
+ }
30
+ }
@@ -0,0 +1,18 @@
1
+ import {ValidationError} from "./ValidationError.js";
2
+
3
+ describe("ValidationError", () => {
4
+ it("should return error", () => {
5
+ const error = new ValidationError("should have required property", [
6
+ {
7
+ dataPath: "hello"
8
+ }
9
+ ]);
10
+
11
+ expect(error.errors).toEqual([
12
+ {
13
+ dataPath: "hello"
14
+ }
15
+ ]);
16
+ expect(error.message).toEqual("should have required property");
17
+ });
18
+ });
@@ -0,0 +1,11 @@
1
+ import {BadRequest} from "@tsed/exceptions";
2
+
3
+ export class ValidationError extends BadRequest {
4
+ public name: string = "VALIDATION_ERROR";
5
+ public errors: any[];
6
+
7
+ constructor(message: string, errors: any[] = []) {
8
+ super(message);
9
+ this.errors = errors;
10
+ }
11
+ }
package/src/exports.ts ADDED
@@ -0,0 +1 @@
1
+ export {PipeMethods} from "@tsed/schema";
package/src/index.ts ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @file Automatically generated by @tsed/barrels.
3
+ */
4
+ export * from "./builder/PlatformParams.js";
5
+ export * from "./decorators/bodyParams.js";
6
+ export * from "./decorators/context.js";
7
+ export * from "./decorators/cookies.js";
8
+ export * from "./decorators/headerParams.js";
9
+ export * from "./decorators/locals.js";
10
+ export * from "./decorators/paramFn.js";
11
+ export * from "./decorators/pathParams.js";
12
+ export * from "./decorators/queryParams.js";
13
+ export * from "./decorators/session.js";
14
+ export * from "./decorators/useDeserialization.js";
15
+ export * from "./decorators/useParam.js";
16
+ export * from "./decorators/useParamExpression.js";
17
+ export * from "./decorators/useParamType.js";
18
+ export * from "./decorators/usePipe.js";
19
+ export * from "./decorators/useType.js";
20
+ export * from "./decorators/useValidation.js";
21
+ export * from "./domain/ParamMetadata.js";
22
+ export * from "./domain/ParamOptions.js";
23
+ export * from "./domain/ParamTypes.js";
24
+ export * from "./errors/ParamValidationError.js";
25
+ export * from "./errors/RequiredValidationError.js";
26
+ export * from "./errors/ValidationError.js";
27
+ export * from "./exports.js";
28
+ export * from "./pipes/DeserializerPipe.js";
29
+ export * from "./pipes/ParseExpressionPipe.js";
30
+ export * from "./pipes/ValidationPipe.js";
31
+ export * from "./utils/mapParamsOptions.js";
@@ -0,0 +1,39 @@
1
+ import {PlatformTest} from "@tsed/platform-http/testing";
2
+ import {JsonParameterStore} from "@tsed/schema";
3
+
4
+ import {BodyParams} from "../decorators/bodyParams.js";
5
+ import {QueryParams} from "../decorators/queryParams.js";
6
+ import {DeserializerPipe} from "./DeserializerPipe.js";
7
+
8
+ describe("DeserializerPipe", () => {
9
+ beforeEach(PlatformTest.create);
10
+ afterEach(PlatformTest.reset);
11
+ it("should transform an object to a model", async () => {
12
+ const pipe = await PlatformTest.get<DeserializerPipe>(DeserializerPipe);
13
+
14
+ class Test {
15
+ test(@BodyParams(String) input: string[]) {}
16
+ }
17
+
18
+ const param = JsonParameterStore.get(Test, "test", 0);
19
+
20
+ // WHEN
21
+ const result = pipe.transform(["test"], param);
22
+
23
+ expect(result).toEqual(["test"]);
24
+ });
25
+ it("should transform an object to a model (Query)", async () => {
26
+ const pipe = await PlatformTest.get<DeserializerPipe>(DeserializerPipe);
27
+
28
+ class Test {
29
+ test(@QueryParams("test", String) input: string[]) {}
30
+ }
31
+
32
+ const param = JsonParameterStore.get(Test, "test", 0);
33
+
34
+ // WHEN
35
+ const result = pipe.transform(["test"], param);
36
+
37
+ expect(result).toEqual(["test"]);
38
+ });
39
+ });
@@ -0,0 +1,15 @@
1
+ import {Injectable, injectable} from "@tsed/di";
2
+ import {deserialize} from "@tsed/json-mapper";
3
+ import {JsonParameterStore, PipeMethods} from "@tsed/schema";
4
+
5
+ export class DeserializerPipe implements PipeMethods {
6
+ transform(value: any, param: JsonParameterStore) {
7
+ return deserialize(value, {
8
+ useAlias: true,
9
+ store: param,
10
+ ...(param.store.get(DeserializerPipe) || {})
11
+ });
12
+ }
13
+ }
14
+
15
+ injectable(DeserializerPipe);
@@ -0,0 +1,242 @@
1
+ import {DecoratorTypes} from "@tsed/core";
2
+ import {PlatformTest} from "@tsed/platform-http/testing";
3
+ import {JsonParameterStore} from "@tsed/schema";
4
+
5
+ import {ParamTypes} from "../../src/index.js";
6
+ import {ParseExpressionPipe} from "./ParseExpressionPipe.js";
7
+
8
+ describe("ParseExpressionPipe", () => {
9
+ beforeEach(PlatformTest.create);
10
+ beforeEach(PlatformTest.reset);
11
+ it(
12
+ "should parse expression",
13
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
14
+ // @ts-ignore
15
+ class Test {}
16
+
17
+ const param: any = new JsonParameterStore({
18
+ index: 0,
19
+ target: Test,
20
+ propertyKey: "test",
21
+ expression: "test",
22
+ paramType: ParamTypes.REQUEST,
23
+ dataPath: "$ctx.request",
24
+ decoratorType: DecoratorTypes.PARAM
25
+ });
26
+ // @ts-ignore
27
+ param._type = String;
28
+ param.collectionType = Array;
29
+
30
+ const scope: any = {
31
+ $ctx: {
32
+ request: {
33
+ test: "value"
34
+ }
35
+ }
36
+ };
37
+
38
+ // WHEN
39
+ const value = pipe.transform(scope, param);
40
+
41
+ expect(value).toEqual("value");
42
+ })
43
+ );
44
+ it(
45
+ "should return empty value",
46
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
47
+ // @ts-ignore
48
+ class Test {}
49
+
50
+ const param: any = new JsonParameterStore({
51
+ index: 0,
52
+ target: Test,
53
+ propertyKey: "test",
54
+ expression: "test",
55
+ paramType: ParamTypes.REQUEST,
56
+ dataPath: "$ctx.request",
57
+ decoratorType: DecoratorTypes.PARAM
58
+ });
59
+ // @ts-ignore
60
+ param._type = String;
61
+ param.collectionType = Array;
62
+
63
+ const scope: any = {
64
+ $ctx: {
65
+ request: {}
66
+ }
67
+ };
68
+
69
+ // WHEN
70
+ const value = pipe.transform(scope, param);
71
+
72
+ expect(value).toEqual(undefined);
73
+ })
74
+ );
75
+ it(
76
+ "should parse expression (for HEADER)",
77
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
78
+ // @ts-ignore
79
+ class Test {}
80
+
81
+ const param: any = new JsonParameterStore({
82
+ index: 0,
83
+ target: Test,
84
+ propertyKey: "test",
85
+ expression: "Content-Type",
86
+ paramType: ParamTypes.HEADER,
87
+ dataPath: "$ctx.request.headers",
88
+ decoratorType: DecoratorTypes.PARAM
89
+ });
90
+ // @ts-ignore
91
+ param._type = String;
92
+ param.collectionType = Array;
93
+
94
+ const scope: any = {
95
+ $ctx: {
96
+ request: {
97
+ headers: {
98
+ "content-type": "application/json"
99
+ }
100
+ }
101
+ }
102
+ };
103
+ // WHEN
104
+ const value = pipe.transform(scope, param);
105
+
106
+ expect(value).toEqual("application/json");
107
+ })
108
+ );
109
+ it(
110
+ "should return undefined when value is empty and Boolean (for QUERY)",
111
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
112
+ // @ts-ignore
113
+ class Test {}
114
+
115
+ const param = new JsonParameterStore({
116
+ index: 0,
117
+ target: Test,
118
+ propertyKey: "test",
119
+ expression: "test",
120
+ paramType: ParamTypes.QUERY,
121
+ dataPath: "$ctx.request.query",
122
+ decoratorType: DecoratorTypes.PARAM
123
+ });
124
+ // @ts-ignore
125
+ param._type = Boolean;
126
+
127
+ const scope: any = {
128
+ $ctx: {
129
+ request: {
130
+ query: {
131
+ test: ""
132
+ }
133
+ }
134
+ }
135
+ };
136
+
137
+ // WHEN
138
+ const value = pipe.transform(scope, param);
139
+
140
+ expect(value).toEqual(undefined);
141
+ })
142
+ );
143
+ it(
144
+ "should return empty when value is empty and String (for QUERY)",
145
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
146
+ // @ts-ignore
147
+ class Test {}
148
+
149
+ const param = new JsonParameterStore({
150
+ index: 0,
151
+ target: Test,
152
+ propertyKey: "test",
153
+ expression: "test",
154
+ paramType: ParamTypes.QUERY,
155
+ dataPath: "$ctx.request.query",
156
+ decoratorType: DecoratorTypes.PARAM
157
+ });
158
+
159
+ const scope: any = {
160
+ $ctx: {
161
+ request: {
162
+ query: {
163
+ test: ""
164
+ }
165
+ }
166
+ }
167
+ };
168
+ // @ts-ignore
169
+ param._type = String;
170
+ // WHEN
171
+ const value = pipe.transform(scope, param);
172
+
173
+ expect(value).toEqual("");
174
+ })
175
+ );
176
+ it(
177
+ "should return undefined when value is empty and Boolean (for PATH)",
178
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
179
+ // @ts-ignore
180
+ class Test {}
181
+
182
+ const param = new JsonParameterStore({
183
+ index: 0,
184
+ target: Test,
185
+ propertyKey: "test",
186
+ expression: "test",
187
+ paramType: ParamTypes.PATH,
188
+ dataPath: "$ctx.request.params",
189
+ decoratorType: DecoratorTypes.PARAM
190
+ });
191
+ // @ts-ignore
192
+ param._type = Boolean;
193
+
194
+ const scope: any = {
195
+ $ctx: {
196
+ request: {
197
+ params: {
198
+ test: ""
199
+ }
200
+ }
201
+ }
202
+ };
203
+ // WHEN
204
+ const value = pipe.transform(scope, param);
205
+
206
+ expect(value).toEqual(undefined);
207
+ })
208
+ );
209
+ it(
210
+ "should return empty when value is empty and String (for PATH)",
211
+ PlatformTest.inject([ParseExpressionPipe], (pipe: ParseExpressionPipe) => {
212
+ // @ts-ignore
213
+ class Test {}
214
+
215
+ const param = new JsonParameterStore({
216
+ index: 0,
217
+ target: Test,
218
+ propertyKey: "test",
219
+ expression: "test",
220
+ paramType: ParamTypes.PATH,
221
+ dataPath: "$ctx.request.params",
222
+ decoratorType: DecoratorTypes.PARAM
223
+ });
224
+ // @ts-ignore
225
+ param._type = String;
226
+
227
+ const scope: any = {
228
+ $ctx: {
229
+ request: {
230
+ params: {
231
+ test: ""
232
+ }
233
+ }
234
+ }
235
+ };
236
+ // WHEN
237
+ const value = pipe.transform(scope, param);
238
+
239
+ expect(value).toEqual("");
240
+ })
241
+ );
242
+ });
@@ -0,0 +1,33 @@
1
+ import {getValue} from "@tsed/core";
2
+ import {Injectable, injectable} from "@tsed/di";
3
+ import {JsonParameterStore, PipeMethods} from "@tsed/schema";
4
+
5
+ import {PlatformParamsScope} from "../builder/PlatformParams.js";
6
+ import {ParamTypes} from "../domain/ParamTypes.js";
7
+
8
+ export class ParseExpressionPipe implements PipeMethods {
9
+ transform(scope: PlatformParamsScope, param: JsonParameterStore) {
10
+ const {paramType, type} = param;
11
+
12
+ const value = getValue(scope, this.getKey(param));
13
+
14
+ if ([ParamTypes.QUERY, ParamTypes.PATH].includes(paramType as ParamTypes) && value === "" && type !== String) {
15
+ return undefined;
16
+ }
17
+
18
+ return value;
19
+ }
20
+
21
+ protected getKey(param: JsonParameterStore) {
22
+ let {expression, paramType, dataPath} = param;
23
+ paramType = paramType || param.parameter.get("in").toUpperCase();
24
+
25
+ if (expression && paramType === ParamTypes.HEADER) {
26
+ expression = String(expression).toLowerCase();
27
+ }
28
+
29
+ return [dataPath, expression].filter(Boolean).join(".");
30
+ }
31
+ }
32
+
33
+ injectable(ParseExpressionPipe).priority(-1000);