@naturalcycles/backend-lib 5.1.0 → 5.2.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/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/server/genericErrorMiddleware.js +0 -1
- package/dist/server/server.model.d.ts +1 -0
- package/dist/server/{reqValidationMiddleware.d.ts → validation/reqValidationMiddleware.d.ts} +9 -1
- package/dist/server/validation/reqValidationMiddleware.js +77 -0
- package/dist/server/{validateMiddleware.d.ts → validation/validateMiddleware.d.ts} +1 -1
- package/dist/server/{validateMiddleware.js → validation/validateMiddleware.js} +1 -1
- package/dist/server/{zodValidateMiddleware.d.ts → validation/zodValidateMiddleware.d.ts} +1 -1
- package/dist/server/{zodValidateMiddleware.js → validation/zodValidateMiddleware.js} +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -3
- package/src/server/genericErrorMiddleware.ts +0 -1
- package/src/server/{reqValidationMiddleware.ts → validation/reqValidationMiddleware.ts} +64 -2
- package/src/server/{validateMiddleware.ts → validation/validateMiddleware.ts} +3 -2
- package/src/server/{zodValidateMiddleware.ts → validation/zodValidateMiddleware.ts} +3 -2
- package/dist/server/reqValidationMiddleware.js +0 -40
package/dist/index.d.ts
CHANGED
|
@@ -20,11 +20,11 @@ export * from './server/notFoundMiddleware';
|
|
|
20
20
|
export * from './server/okMiddleware';
|
|
21
21
|
export * from './server/basicAuthMiddleware';
|
|
22
22
|
export * from './server/requestTimeoutMiddleware';
|
|
23
|
-
export * from './server/reqValidationMiddleware';
|
|
23
|
+
export * from './server/validation/reqValidationMiddleware';
|
|
24
24
|
export * from './server/simpleRequestLoggerMiddleware';
|
|
25
25
|
export * from './server/serverStatusMiddleware';
|
|
26
|
-
export * from './server/validateMiddleware';
|
|
27
|
-
export * from './server/zodValidateMiddleware';
|
|
26
|
+
export * from './server/validation/validateMiddleware';
|
|
27
|
+
export * from './server/validation/zodValidateMiddleware';
|
|
28
28
|
export * from './server/request.log.util';
|
|
29
29
|
export * from './server/startServer';
|
|
30
30
|
export * from './server/startServer.model';
|
package/dist/index.js
CHANGED
|
@@ -25,11 +25,11 @@ tslib_1.__exportStar(require("./server/notFoundMiddleware"), exports);
|
|
|
25
25
|
tslib_1.__exportStar(require("./server/okMiddleware"), exports);
|
|
26
26
|
tslib_1.__exportStar(require("./server/basicAuthMiddleware"), exports);
|
|
27
27
|
tslib_1.__exportStar(require("./server/requestTimeoutMiddleware"), exports);
|
|
28
|
-
tslib_1.__exportStar(require("./server/reqValidationMiddleware"), exports);
|
|
28
|
+
tslib_1.__exportStar(require("./server/validation/reqValidationMiddleware"), exports);
|
|
29
29
|
tslib_1.__exportStar(require("./server/simpleRequestLoggerMiddleware"), exports);
|
|
30
30
|
tslib_1.__exportStar(require("./server/serverStatusMiddleware"), exports);
|
|
31
|
-
tslib_1.__exportStar(require("./server/validateMiddleware"), exports);
|
|
32
|
-
tslib_1.__exportStar(require("./server/zodValidateMiddleware"), exports);
|
|
31
|
+
tslib_1.__exportStar(require("./server/validation/validateMiddleware"), exports);
|
|
32
|
+
tslib_1.__exportStar(require("./server/validation/zodValidateMiddleware"), exports);
|
|
33
33
|
tslib_1.__exportStar(require("./server/request.log.util"), exports);
|
|
34
34
|
tslib_1.__exportStar(require("./server/startServer"), exports);
|
|
35
35
|
tslib_1.__exportStar(require("./server/startServer.model"), exports);
|
|
@@ -57,7 +57,6 @@ function respondWithError(req, res, err) {
|
|
|
57
57
|
// Otherwise, it breaks the _isHttpErrorResponse function check, and error get formatted/detected wrongly
|
|
58
58
|
httpError.data['httpStatusCode'] = httpError.data.backendResponseStatusCode;
|
|
59
59
|
httpError.data.headersSent = headersSent || undefined;
|
|
60
|
-
httpError.data.report ||= undefined; // set to undefined if false
|
|
61
60
|
(0, js_lib_1._filterUndefinedValues)(httpError.data, true);
|
|
62
61
|
formatError?.(httpError); // Mutates
|
|
63
62
|
res.status(httpError.data.backendResponseStatusCode).json({
|
package/dist/server/{reqValidationMiddleware.d.ts → validation/reqValidationMiddleware.d.ts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AnySchema, JoiValidationError } from '@naturalcycles/nodejs-lib';
|
|
2
|
-
import { BackendRequestHandler } from '
|
|
2
|
+
import { BackendRequest, BackendRequestHandler } from '../server.model';
|
|
3
3
|
export interface ReqValidationOptions<ERR extends Error> {
|
|
4
4
|
/**
|
|
5
5
|
* Pass a 'dot-paths' (e.g `pw`, or `input.pw`) that needs to be redacted from the output, in case of error.
|
|
@@ -13,3 +13,11 @@ export interface ReqValidationOptions<ERR extends Error> {
|
|
|
13
13
|
report?: boolean | ((err: ERR) => boolean);
|
|
14
14
|
}
|
|
15
15
|
export declare function reqValidation(reqProperty: 'body' | 'params' | 'query', schema: AnySchema, opt?: ReqValidationOptions<JoiValidationError>): BackendRequestHandler;
|
|
16
|
+
declare class ValidateRequest {
|
|
17
|
+
body<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
|
|
18
|
+
query<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
|
|
19
|
+
params<T>(req: BackendRequest, schema: AnySchema<T>, opt?: ReqValidationOptions<JoiValidationError>): T;
|
|
20
|
+
private validate;
|
|
21
|
+
}
|
|
22
|
+
export declare const validateRequest: ValidateRequest;
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateRequest = exports.reqValidation = void 0;
|
|
4
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
+
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
+
const REDACTED = 'REDACTED';
|
|
7
|
+
function reqValidation(reqProperty, schema, opt = {}) {
|
|
8
|
+
const reportPredicate = typeof opt.report === 'function' ? opt.report : () => opt.report;
|
|
9
|
+
return (req, res, next) => {
|
|
10
|
+
const { value, error } = (0, nodejs_lib_1.getValidationResult)(req[reqProperty], schema, `request ${reqProperty}`);
|
|
11
|
+
if (error) {
|
|
12
|
+
const report = reportPredicate(error);
|
|
13
|
+
if (opt.redactPaths) {
|
|
14
|
+
redact(opt.redactPaths, req[reqProperty], error);
|
|
15
|
+
error.data.joiValidationErrorItems.length = 0; // clears the array
|
|
16
|
+
delete error.data.annotation;
|
|
17
|
+
}
|
|
18
|
+
return next(new js_lib_1.AppError(error.message, {
|
|
19
|
+
backendResponseStatusCode: 400,
|
|
20
|
+
report,
|
|
21
|
+
...error.data,
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
// mutate req to replace the property with the value, converted by Joi
|
|
25
|
+
req[reqProperty] = value;
|
|
26
|
+
next();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
exports.reqValidation = reqValidation;
|
|
30
|
+
/**
|
|
31
|
+
* Mutates error
|
|
32
|
+
*/
|
|
33
|
+
function redact(redactPaths, obj, error) {
|
|
34
|
+
redactPaths
|
|
35
|
+
.map(path => (0, js_lib_1._get)(obj, path))
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.forEach(secret => {
|
|
38
|
+
error.message = error.message.replace(secret, REDACTED);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
class ValidateRequest {
|
|
42
|
+
body(req, schema, opt = {}) {
|
|
43
|
+
return this.validate(req, 'body', schema, opt);
|
|
44
|
+
}
|
|
45
|
+
query(req, schema, opt = {}) {
|
|
46
|
+
return this.validate(req, 'query', schema, opt);
|
|
47
|
+
}
|
|
48
|
+
params(req, schema, opt = {}) {
|
|
49
|
+
return this.validate(req, 'params', schema, opt);
|
|
50
|
+
}
|
|
51
|
+
validate(req, reqProperty, schema, opt = {}) {
|
|
52
|
+
const { value, error } = (0, nodejs_lib_1.getValidationResult)(req[reqProperty], schema, `request ${reqProperty}`);
|
|
53
|
+
if (error) {
|
|
54
|
+
let report;
|
|
55
|
+
if (typeof opt.report === 'boolean') {
|
|
56
|
+
report = opt.report;
|
|
57
|
+
}
|
|
58
|
+
else if (typeof opt.report === 'function') {
|
|
59
|
+
report = opt.report(error);
|
|
60
|
+
}
|
|
61
|
+
if (opt.redactPaths) {
|
|
62
|
+
redact(opt.redactPaths, req[reqProperty], error);
|
|
63
|
+
error.data.joiValidationErrorItems.length = 0; // clears the array
|
|
64
|
+
delete error.data.annotation;
|
|
65
|
+
}
|
|
66
|
+
throw new js_lib_1.AppError(error.message, {
|
|
67
|
+
backendResponseStatusCode: 400,
|
|
68
|
+
report,
|
|
69
|
+
...error.data,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// mutate req to replace the property with the value, converted by Joi
|
|
73
|
+
req[reqProperty] = value;
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.validateRequest = new ValidateRequest();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib';
|
|
2
2
|
import { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib';
|
|
3
|
+
import { BackendRequestHandler } from '../server.model';
|
|
3
4
|
import { ReqValidationOptions } from './reqValidationMiddleware';
|
|
4
|
-
import { BackendRequestHandler } from './server.model';
|
|
5
5
|
export declare function validateBody(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
|
|
6
6
|
export declare function validateParams(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
|
|
7
7
|
export declare function validateQuery(schema: JsonSchema | JsonSchemaBuilder | AjvSchema, opt?: ReqValidationOptions<AjvValidationError>): BackendRequestHandler;
|
|
@@ -26,7 +26,7 @@ function validateObject(prop, schema, opt = {}) {
|
|
|
26
26
|
const ajvSchema = nodejs_lib_1.AjvSchema.create(schema, {
|
|
27
27
|
objectName: `request ${prop}`,
|
|
28
28
|
});
|
|
29
|
-
const reportPredicate = typeof opt.report === 'function' ? opt.report : () =>
|
|
29
|
+
const reportPredicate = typeof opt.report === 'function' ? opt.report : () => opt.report;
|
|
30
30
|
return (req, res, next) => {
|
|
31
31
|
const error = ajvSchema.getValidationError(req[prop]);
|
|
32
32
|
if (error) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ZodSchema, ZodValidationError } from '@naturalcycles/js-lib';
|
|
2
|
+
import { BackendRequestHandler } from '../server.model';
|
|
2
3
|
import { ReqValidationOptions } from './reqValidationMiddleware';
|
|
3
|
-
import { BackendRequestHandler } from './server.model';
|
|
4
4
|
/**
|
|
5
5
|
* Validates req property (body, params or query).
|
|
6
6
|
* Supports Joi schema or AjvSchema (from nodejs-lib).
|
|
@@ -10,7 +10,7 @@ const REDACTED = 'REDACTED';
|
|
|
10
10
|
* Throws http 400 on error.
|
|
11
11
|
*/
|
|
12
12
|
function zodReqValidate(prop, schema, opt = {}) {
|
|
13
|
-
const reportPredicate = typeof opt.report === 'function' ? opt.report : () =>
|
|
13
|
+
const reportPredicate = typeof opt.report === 'function' ? opt.report : () => opt.report;
|
|
14
14
|
return (req, res, next) => {
|
|
15
15
|
const { error } = (0, js_lib_1.zSafeValidate)(req[prop], schema);
|
|
16
16
|
if (!error) {
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -20,11 +20,11 @@ export * from './server/notFoundMiddleware'
|
|
|
20
20
|
export * from './server/okMiddleware'
|
|
21
21
|
export * from './server/basicAuthMiddleware'
|
|
22
22
|
export * from './server/requestTimeoutMiddleware'
|
|
23
|
-
export * from './server/reqValidationMiddleware'
|
|
23
|
+
export * from './server/validation/reqValidationMiddleware'
|
|
24
24
|
export * from './server/simpleRequestLoggerMiddleware'
|
|
25
25
|
export * from './server/serverStatusMiddleware'
|
|
26
|
-
export * from './server/validateMiddleware'
|
|
27
|
-
export * from './server/zodValidateMiddleware'
|
|
26
|
+
export * from './server/validation/validateMiddleware'
|
|
27
|
+
export * from './server/validation/zodValidateMiddleware'
|
|
28
28
|
export * from './server/request.log.util'
|
|
29
29
|
export * from './server/startServer'
|
|
30
30
|
export * from './server/startServer.model'
|
|
@@ -90,7 +90,6 @@ export function respondWithError(req: BackendRequest, res: BackendResponse, err:
|
|
|
90
90
|
// Otherwise, it breaks the _isHttpErrorResponse function check, and error get formatted/detected wrongly
|
|
91
91
|
httpError.data['httpStatusCode'] = httpError.data.backendResponseStatusCode
|
|
92
92
|
httpError.data.headersSent = headersSent || undefined
|
|
93
|
-
httpError.data.report ||= undefined // set to undefined if false
|
|
94
93
|
_filterUndefinedValues(httpError.data, true)
|
|
95
94
|
|
|
96
95
|
formatError?.(httpError) // Mutates
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _get, AppError } from '@naturalcycles/js-lib'
|
|
2
2
|
import { AnySchema, getValidationResult, JoiValidationError } from '@naturalcycles/nodejs-lib'
|
|
3
|
-
import { BackendRequestHandler } from '
|
|
3
|
+
import { BackendRequest, BackendRequestHandler } from '../server.model'
|
|
4
4
|
|
|
5
5
|
const REDACTED = 'REDACTED'
|
|
6
6
|
|
|
@@ -23,7 +23,8 @@ export function reqValidation(
|
|
|
23
23
|
schema: AnySchema,
|
|
24
24
|
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
25
25
|
): BackendRequestHandler {
|
|
26
|
-
const reportPredicate =
|
|
26
|
+
const reportPredicate =
|
|
27
|
+
typeof opt.report === 'function' ? opt.report : () => opt.report as boolean | undefined
|
|
27
28
|
|
|
28
29
|
return (req, res, next) => {
|
|
29
30
|
const { value, error } = getValidationResult(req[reqProperty], schema, `request ${reqProperty}`)
|
|
@@ -62,3 +63,64 @@ function redact(redactPaths: string[], obj: any, error: Error): void {
|
|
|
62
63
|
error.message = error.message.replace(secret, REDACTED)
|
|
63
64
|
})
|
|
64
65
|
}
|
|
66
|
+
|
|
67
|
+
class ValidateRequest {
|
|
68
|
+
body<T>(
|
|
69
|
+
req: BackendRequest,
|
|
70
|
+
schema: AnySchema<T>,
|
|
71
|
+
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
72
|
+
): T {
|
|
73
|
+
return this.validate(req, 'body', schema, opt)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
query<T>(
|
|
77
|
+
req: BackendRequest,
|
|
78
|
+
schema: AnySchema<T>,
|
|
79
|
+
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
80
|
+
): T {
|
|
81
|
+
return this.validate(req, 'query', schema, opt)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
params<T>(
|
|
85
|
+
req: BackendRequest,
|
|
86
|
+
schema: AnySchema<T>,
|
|
87
|
+
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
88
|
+
): T {
|
|
89
|
+
return this.validate(req, 'params', schema, opt)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private validate<T>(
|
|
93
|
+
req: BackendRequest,
|
|
94
|
+
reqProperty: 'body' | 'params' | 'query',
|
|
95
|
+
schema: AnySchema<T>,
|
|
96
|
+
opt: ReqValidationOptions<JoiValidationError> = {},
|
|
97
|
+
): T {
|
|
98
|
+
const { value, error } = getValidationResult(req[reqProperty], schema, `request ${reqProperty}`)
|
|
99
|
+
if (error) {
|
|
100
|
+
let report: boolean | undefined
|
|
101
|
+
if (typeof opt.report === 'boolean') {
|
|
102
|
+
report = opt.report
|
|
103
|
+
} else if (typeof opt.report === 'function') {
|
|
104
|
+
report = opt.report(error)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (opt.redactPaths) {
|
|
108
|
+
redact(opt.redactPaths, req[reqProperty], error)
|
|
109
|
+
error.data.joiValidationErrorItems.length = 0 // clears the array
|
|
110
|
+
delete error.data.annotation
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new AppError(error.message, {
|
|
114
|
+
backendResponseStatusCode: 400,
|
|
115
|
+
report,
|
|
116
|
+
...error.data,
|
|
117
|
+
})
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// mutate req to replace the property with the value, converted by Joi
|
|
121
|
+
req[reqProperty] = value
|
|
122
|
+
return value
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export const validateRequest = new ValidateRequest()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JsonSchema, JsonSchemaBuilder, _get, AppError } from '@naturalcycles/js-lib'
|
|
2
2
|
import { AjvSchema, AjvValidationError } from '@naturalcycles/nodejs-lib'
|
|
3
|
+
import { BackendRequestHandler } from '../server.model'
|
|
3
4
|
import { ReqValidationOptions } from './reqValidationMiddleware'
|
|
4
|
-
import { BackendRequestHandler } from './server.model'
|
|
5
5
|
|
|
6
6
|
const REDACTED = 'REDACTED'
|
|
7
7
|
|
|
@@ -41,7 +41,8 @@ function validateObject(
|
|
|
41
41
|
objectName: `request ${prop}`,
|
|
42
42
|
})
|
|
43
43
|
|
|
44
|
-
const reportPredicate =
|
|
44
|
+
const reportPredicate =
|
|
45
|
+
typeof opt.report === 'function' ? opt.report : () => opt.report as boolean | undefined
|
|
45
46
|
|
|
46
47
|
return (req, res, next) => {
|
|
47
48
|
const error = ajvSchema.getValidationError(req[prop])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { _get, AppError, ZodSchema, ZodValidationError, zSafeValidate } from '@naturalcycles/js-lib'
|
|
2
|
+
import { BackendRequestHandler } from '../server.model'
|
|
2
3
|
import { ReqValidationOptions } from './reqValidationMiddleware'
|
|
3
|
-
import { BackendRequestHandler } from './server.model'
|
|
4
4
|
|
|
5
5
|
const REDACTED = 'REDACTED'
|
|
6
6
|
|
|
@@ -15,7 +15,8 @@ export function zodReqValidate(
|
|
|
15
15
|
schema: ZodSchema,
|
|
16
16
|
opt: ReqValidationOptions<ZodValidationError<any>> = {},
|
|
17
17
|
): BackendRequestHandler {
|
|
18
|
-
const reportPredicate =
|
|
18
|
+
const reportPredicate =
|
|
19
|
+
typeof opt.report === 'function' ? opt.report : () => opt.report as boolean | undefined
|
|
19
20
|
|
|
20
21
|
return (req, res, next) => {
|
|
21
22
|
const { error } = zSafeValidate(req[prop], schema)
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.reqValidation = void 0;
|
|
4
|
-
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
5
|
-
const nodejs_lib_1 = require("@naturalcycles/nodejs-lib");
|
|
6
|
-
const REDACTED = 'REDACTED';
|
|
7
|
-
function reqValidation(reqProperty, schema, opt = {}) {
|
|
8
|
-
const reportPredicate = typeof opt.report === 'function' ? opt.report : () => !!opt.report;
|
|
9
|
-
return (req, res, next) => {
|
|
10
|
-
const { value, error } = (0, nodejs_lib_1.getValidationResult)(req[reqProperty], schema, `request ${reqProperty}`);
|
|
11
|
-
if (error) {
|
|
12
|
-
const report = reportPredicate(error);
|
|
13
|
-
if (opt.redactPaths) {
|
|
14
|
-
redact(opt.redactPaths, req[reqProperty], error);
|
|
15
|
-
error.data.joiValidationErrorItems.length = 0; // clears the array
|
|
16
|
-
delete error.data.annotation;
|
|
17
|
-
}
|
|
18
|
-
return next(new js_lib_1.AppError(error.message, {
|
|
19
|
-
backendResponseStatusCode: 400,
|
|
20
|
-
report,
|
|
21
|
-
...error.data,
|
|
22
|
-
}));
|
|
23
|
-
}
|
|
24
|
-
// mutate req to replace the property with the value, converted by Joi
|
|
25
|
-
req[reqProperty] = value;
|
|
26
|
-
next();
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
exports.reqValidation = reqValidation;
|
|
30
|
-
/**
|
|
31
|
-
* Mutates error
|
|
32
|
-
*/
|
|
33
|
-
function redact(redactPaths, obj, error) {
|
|
34
|
-
redactPaths
|
|
35
|
-
.map(path => (0, js_lib_1._get)(obj, path))
|
|
36
|
-
.filter(Boolean)
|
|
37
|
-
.forEach(secret => {
|
|
38
|
-
error.message = error.message.replace(secret, REDACTED);
|
|
39
|
-
});
|
|
40
|
-
}
|