@naturalcycles/backend-lib 9.43.0 → 9.44.1
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/server/server.model.d.ts +5 -0
- package/dist/validation/ajv/ajvValidateRequest.js +20 -11
- package/dist/validation/joi/joiValidateRequest.js +1 -1
- package/dist/validation/validateRequest.util.d.ts +10 -0
- package/dist/validation/zod/zodValidateRequest.js +1 -1
- package/package.json +2 -2
- package/src/server/server.model.ts +7 -0
- package/src/validation/ajv/ajvValidateRequest.ts +26 -10
- package/src/validation/joi/joiValidateRequest.ts +1 -1
- package/src/validation/validateRequest.util.ts +11 -0
- package/src/validation/zod/zodValidateRequest.ts +1 -1
|
@@ -18,6 +18,11 @@ export interface BackendRequest extends Request {
|
|
|
18
18
|
* Only used for request logging purposes.
|
|
19
19
|
*/
|
|
20
20
|
userId?: string;
|
|
21
|
+
/**
|
|
22
|
+
* It's set to unknown (instead of `any`) to prevent implicit use of any
|
|
23
|
+
* in unexpected places.
|
|
24
|
+
*/
|
|
25
|
+
body: unknown;
|
|
21
26
|
/**
|
|
22
27
|
* Raw Buffer of the `req.body`, before it's stringified and json-parsed.
|
|
23
28
|
* Useful for when something mutates `req.body` json (e.g j validation), and you
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { _deepCopy } from '@naturalcycles/js-lib/object';
|
|
2
1
|
import { AjvSchema, getCoercingAjv, } from '@naturalcycles/nodejs-lib/ajv';
|
|
3
2
|
import { handleValidationError } from '../validateRequest.util.js';
|
|
4
3
|
class AjvValidateRequest {
|
|
5
4
|
body(req, schema, opt = {}) {
|
|
6
|
-
return this.validate(req, 'body', schema, opt);
|
|
5
|
+
return this.validate(req, 'body', schema, req.rawBody ? () => JSON.parse(req.rawBody.toString()) : undefined, opt);
|
|
7
6
|
}
|
|
8
7
|
/**
|
|
9
8
|
* Query validation uses type coercion (unlike body validation),
|
|
@@ -12,7 +11,11 @@ class AjvValidateRequest {
|
|
|
12
11
|
* Coercion mutates the input, even if the end result is that the input failed the validation.
|
|
13
12
|
*/
|
|
14
13
|
query(req, schema, opt = {}) {
|
|
15
|
-
|
|
14
|
+
const originalQuery = JSON.stringify(req.query);
|
|
15
|
+
return this.validate(req, 'query', schema, () => JSON.parse(originalQuery), {
|
|
16
|
+
coerceTypes: true,
|
|
17
|
+
...opt,
|
|
18
|
+
});
|
|
16
19
|
}
|
|
17
20
|
/**
|
|
18
21
|
* Params validation uses type coercion (unlike body validation),
|
|
@@ -21,7 +24,11 @@ class AjvValidateRequest {
|
|
|
21
24
|
* Coercion mutates the input, even if the end result is that the input failed the validation.
|
|
22
25
|
*/
|
|
23
26
|
params(req, schema, opt = {}) {
|
|
24
|
-
|
|
27
|
+
const originalParams = JSON.stringify(req.params);
|
|
28
|
+
return this.validate(req, 'params', schema, () => JSON.parse(originalParams), {
|
|
29
|
+
coerceTypes: true,
|
|
30
|
+
...opt,
|
|
31
|
+
});
|
|
25
32
|
}
|
|
26
33
|
/**
|
|
27
34
|
* Does NOT mutate `req.headers`,
|
|
@@ -32,18 +39,20 @@ class AjvValidateRequest {
|
|
|
32
39
|
* there may be additional consumers for `req.headers` (e.g middlewares, etc).
|
|
33
40
|
*/
|
|
34
41
|
headers(req, schema, opt = {}) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
return this.validate(req, 'headers', schema, undefined, {
|
|
43
|
+
mutateInput: false,
|
|
44
|
+
...opt,
|
|
45
|
+
});
|
|
39
46
|
}
|
|
40
|
-
validate(req, reqProperty, schema, opt = {}) {
|
|
41
|
-
const input = req[reqProperty] || {};
|
|
42
|
-
const { coerceTypes } = opt;
|
|
47
|
+
validate(req, reqProperty, schema, getOriginalInput, opt = {}) {
|
|
48
|
+
const input = (req[reqProperty] || {});
|
|
49
|
+
const { coerceTypes, mutateInput } = opt;
|
|
43
50
|
const ajv = coerceTypes ? getCoercingAjv() : undefined;
|
|
44
51
|
const ajvSchema = AjvSchema.create(schema, { ajv });
|
|
45
52
|
const [error, output] = ajvSchema.getValidationResult(input, {
|
|
46
53
|
inputName: `request.${reqProperty}`,
|
|
54
|
+
getOriginalInput,
|
|
55
|
+
mutateInput,
|
|
47
56
|
});
|
|
48
57
|
if (error) {
|
|
49
58
|
handleValidationError(error, input, opt);
|
|
@@ -23,7 +23,7 @@ class ValidateRequest {
|
|
|
23
23
|
return this.validate(req, 'headers', schema, opt);
|
|
24
24
|
}
|
|
25
25
|
validate(req, reqProperty, schema, opt = {}) {
|
|
26
|
-
const originalProperty = req[reqProperty] || {};
|
|
26
|
+
const originalProperty = (req[reqProperty] || {});
|
|
27
27
|
// Joi does not mutate the input
|
|
28
28
|
const [error, value] = getValidationResult(originalProperty, schema, `request.${reqProperty}`);
|
|
29
29
|
if (error) {
|
|
@@ -20,4 +20,14 @@ export interface ReqValidationOptions<ERR extends AppError> {
|
|
|
20
20
|
* typically: request path params and request query params.
|
|
21
21
|
*/
|
|
22
22
|
coerceTypes?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Default value depends on the implementation and the object.
|
|
25
|
+
* Joi and Zod do not mutate input (even if you pass `mutateInput: true` - that feature is not supported).
|
|
26
|
+
* AJV by default does mutate the input, but depending on the property:
|
|
27
|
+
*
|
|
28
|
+
* body - will be mutated, by req.rawBody will be used (if available) for error message snippet
|
|
29
|
+
* params, query - will NOT mutate by default (unless you pass `mutateInput: true`)
|
|
30
|
+
* headers - will NOT mutate by default
|
|
31
|
+
*/
|
|
32
|
+
mutateInput?: boolean;
|
|
23
33
|
}
|
|
@@ -23,7 +23,7 @@ class ZodValidateRequest {
|
|
|
23
23
|
return this.validate(req, 'headers', schema, opt);
|
|
24
24
|
}
|
|
25
25
|
validate(req, reqProperty, schema, opt = {}) {
|
|
26
|
-
const originalProperty = req[reqProperty] || {};
|
|
26
|
+
const originalProperty = (req[reqProperty] || {});
|
|
27
27
|
// Zod does not mutate the input
|
|
28
28
|
const [error, data] = zSafeValidate(originalProperty, schema);
|
|
29
29
|
if (error) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/backend-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.44.1",
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"@sentry/node": "^10"
|
|
7
7
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@sentry/node": "^10",
|
|
30
30
|
"@types/ejs": "^3",
|
|
31
31
|
"fastify": "^5",
|
|
32
|
-
"@naturalcycles/dev-lib": "
|
|
32
|
+
"@naturalcycles/dev-lib": "20.12.11"
|
|
33
33
|
},
|
|
34
34
|
"exports": {
|
|
35
35
|
".": "./dist/index.js",
|
|
@@ -20,6 +20,13 @@ export interface BackendRequest extends Request {
|
|
|
20
20
|
* Only used for request logging purposes.
|
|
21
21
|
*/
|
|
22
22
|
userId?: string
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* It's set to unknown (instead of `any`) to prevent implicit use of any
|
|
26
|
+
* in unexpected places.
|
|
27
|
+
*/
|
|
28
|
+
body: unknown
|
|
29
|
+
|
|
23
30
|
/**
|
|
24
31
|
* Raw Buffer of the `req.body`, before it's stringified and json-parsed.
|
|
25
32
|
* Useful for when something mutates `req.body` json (e.g j validation), and you
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { _deepCopy } from '@naturalcycles/js-lib/object'
|
|
2
1
|
import {
|
|
3
2
|
AjvSchema,
|
|
4
3
|
type AjvValidationError,
|
|
@@ -14,7 +13,13 @@ class AjvValidateRequest {
|
|
|
14
13
|
schema: SchemaHandledByAjv<IN, OUT>,
|
|
15
14
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
16
15
|
): OUT {
|
|
17
|
-
return this.validate(
|
|
16
|
+
return this.validate(
|
|
17
|
+
req,
|
|
18
|
+
'body',
|
|
19
|
+
schema,
|
|
20
|
+
req.rawBody ? () => JSON.parse(req.rawBody!.toString()) : undefined,
|
|
21
|
+
opt,
|
|
22
|
+
)
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
/**
|
|
@@ -28,7 +33,11 @@ class AjvValidateRequest {
|
|
|
28
33
|
schema: SchemaHandledByAjv<IN, OUT>,
|
|
29
34
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
30
35
|
): OUT {
|
|
31
|
-
|
|
36
|
+
const originalQuery = JSON.stringify(req.query)
|
|
37
|
+
return this.validate(req, 'query', schema, () => JSON.parse(originalQuery), {
|
|
38
|
+
coerceTypes: true,
|
|
39
|
+
...opt,
|
|
40
|
+
})
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
/**
|
|
@@ -42,7 +51,11 @@ class AjvValidateRequest {
|
|
|
42
51
|
schema: SchemaHandledByAjv<IN, OUT>,
|
|
43
52
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
44
53
|
): OUT {
|
|
45
|
-
|
|
54
|
+
const originalParams = JSON.stringify(req.params)
|
|
55
|
+
return this.validate(req, 'params', schema, () => JSON.parse(originalParams), {
|
|
56
|
+
coerceTypes: true,
|
|
57
|
+
...opt,
|
|
58
|
+
})
|
|
46
59
|
}
|
|
47
60
|
|
|
48
61
|
/**
|
|
@@ -58,26 +71,29 @@ class AjvValidateRequest {
|
|
|
58
71
|
schema: SchemaHandledByAjv<IN, OUT>,
|
|
59
72
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
60
73
|
): OUT {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
return this.validate(req, 'headers', schema, undefined, {
|
|
75
|
+
mutateInput: false,
|
|
76
|
+
...opt,
|
|
77
|
+
})
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
private validate<IN, OUT>(
|
|
68
81
|
req: BackendRequest,
|
|
69
82
|
reqProperty: 'body' | 'params' | 'query' | 'headers',
|
|
70
83
|
schema: SchemaHandledByAjv<IN, OUT>,
|
|
84
|
+
getOriginalInput?: () => IN,
|
|
71
85
|
opt: ReqValidationOptions<AjvValidationError> = {},
|
|
72
86
|
): OUT {
|
|
73
|
-
const input
|
|
87
|
+
const input = (req[reqProperty] || {}) as IN
|
|
74
88
|
|
|
75
|
-
const { coerceTypes } = opt
|
|
89
|
+
const { coerceTypes, mutateInput } = opt
|
|
76
90
|
const ajv = coerceTypes ? getCoercingAjv() : undefined
|
|
77
91
|
const ajvSchema = AjvSchema.create(schema, { ajv })
|
|
78
92
|
|
|
79
93
|
const [error, output] = ajvSchema.getValidationResult(input, {
|
|
80
94
|
inputName: `request.${reqProperty}`,
|
|
95
|
+
getOriginalInput,
|
|
96
|
+
mutateInput,
|
|
81
97
|
})
|
|
82
98
|
|
|
83
99
|
if (error) {
|
|
@@ -51,7 +51,7 @@ class ValidateRequest {
|
|
|
51
51
|
schema: AnySchema<T>,
|
|
52
52
|
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
53
53
|
): T {
|
|
54
|
-
const originalProperty = req[reqProperty] || {}
|
|
54
|
+
const originalProperty = (req[reqProperty] || {}) as T
|
|
55
55
|
|
|
56
56
|
// Joi does not mutate the input
|
|
57
57
|
const [error, value] = getValidationResult(originalProperty, schema, `request.${reqProperty}`)
|
|
@@ -69,4 +69,15 @@ export interface ReqValidationOptions<ERR extends AppError> {
|
|
|
69
69
|
* typically: request path params and request query params.
|
|
70
70
|
*/
|
|
71
71
|
coerceTypes?: boolean
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Default value depends on the implementation and the object.
|
|
75
|
+
* Joi and Zod do not mutate input (even if you pass `mutateInput: true` - that feature is not supported).
|
|
76
|
+
* AJV by default does mutate the input, but depending on the property:
|
|
77
|
+
*
|
|
78
|
+
* body - will be mutated, by req.rawBody will be used (if available) for error message snippet
|
|
79
|
+
* params, query - will NOT mutate by default (unless you pass `mutateInput: true`)
|
|
80
|
+
* headers - will NOT mutate by default
|
|
81
|
+
*/
|
|
82
|
+
mutateInput?: boolean
|
|
72
83
|
}
|
|
@@ -50,7 +50,7 @@ class ZodValidateRequest {
|
|
|
50
50
|
schema: ZodType<T>,
|
|
51
51
|
opt: ReqValidationOptions<ZodValidationError> = {},
|
|
52
52
|
): T {
|
|
53
|
-
const originalProperty = req[reqProperty] || {}
|
|
53
|
+
const originalProperty = (req[reqProperty] || {}) as T
|
|
54
54
|
|
|
55
55
|
// Zod does not mutate the input
|
|
56
56
|
const [error, data] = zSafeValidate(
|