@naturalcycles/nodejs-lib 15.7.0 → 15.9.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/log/log.util.d.ts +1 -1
- package/dist/validation/ajv/ajvSchema.d.ts +18 -11
- package/dist/validation/ajv/ajvSchema.js +36 -33
- package/dist/validation/ajv/ajvValidationError.d.ts +2 -2
- package/dist/validation/ajv/getAjv.d.ts +1 -1
- package/dist/validation/joi/joi.shared.schemas.d.ts +1 -1
- package/dist/validation/joi/joi.validation.error.d.ts +2 -2
- package/dist/validation/joi/joi.validation.util.d.ts +4 -6
- package/dist/validation/joi/joi.validation.util.js +22 -21
- package/package.json +2 -2
- package/src/validation/ajv/ajvSchema.ts +60 -43
- package/src/validation/ajv/ajvValidationError.ts +2 -2
- package/src/validation/joi/joi.validation.error.ts +2 -2
- package/src/validation/joi/joi.validation.util.ts +32 -34
- package/dist/stream/transform/worker/workerClassProxy.js +0 -89
package/dist/log/log.util.d.ts
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
1
2
|
import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema';
|
|
2
3
|
import type { Ajv } from 'ajv';
|
|
3
4
|
import { AjvValidationError } from './ajvValidationError.js';
|
|
4
5
|
export interface AjvValidationOptions {
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Defaults to false.
|
|
8
|
+
*
|
|
9
|
+
* If set to true - AJV will mutate the input in case it needs to apply transformations
|
|
10
|
+
* (strip unknown properties, convert types, etc).
|
|
11
|
+
*
|
|
12
|
+
* If false - it will deep-clone the input to prevent its mutation. Will return the cloned/mutated object.
|
|
13
|
+
*/
|
|
14
|
+
mutateInput?: boolean;
|
|
15
|
+
inputName?: string;
|
|
16
|
+
inputId?: string;
|
|
7
17
|
}
|
|
8
18
|
export interface AjvSchemaCfg {
|
|
9
19
|
/**
|
|
@@ -11,11 +21,7 @@ export interface AjvSchemaCfg {
|
|
|
11
21
|
* AjvSchema default (not the same as Ajv defaults) parameters
|
|
12
22
|
*/
|
|
13
23
|
ajv: Ajv;
|
|
14
|
-
|
|
15
|
-
* Dependent schemas to pass to Ajv instance constructor.
|
|
16
|
-
* Simpler than instantiating and passing ajv instance yourself.
|
|
17
|
-
*/
|
|
18
|
-
objectName?: string;
|
|
24
|
+
inputName?: string;
|
|
19
25
|
/**
|
|
20
26
|
* Option of Ajv.
|
|
21
27
|
* If set to true - will mutate your input objects!
|
|
@@ -53,7 +59,6 @@ export declare class AjvSchema<T = unknown> {
|
|
|
53
59
|
*/
|
|
54
60
|
static create<T>(schema: JsonSchemaBuilder<T> | JsonSchema<T> | AjvSchema<T>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<T>;
|
|
55
61
|
readonly cfg: AjvSchemaCfg;
|
|
56
|
-
private getValidateFunction;
|
|
57
62
|
/**
|
|
58
63
|
* It returns the original object just for convenience.
|
|
59
64
|
* Reminder: Ajv will MUTATE your object under 2 circumstances:
|
|
@@ -62,7 +67,9 @@ export declare class AjvSchema<T = unknown> {
|
|
|
62
67
|
*
|
|
63
68
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
64
69
|
*/
|
|
65
|
-
validate(
|
|
66
|
-
isValid(
|
|
67
|
-
|
|
70
|
+
validate(input: T, opt?: AjvValidationOptions): T;
|
|
71
|
+
isValid(input: T, opt?: AjvValidationOptions): boolean;
|
|
72
|
+
getValidationResult(input: T, opt?: AjvValidationOptions): ValidationFunctionResult<T, AjvValidationError>;
|
|
73
|
+
getValidationFunction(): ValidationFunction<T, AjvValidationError>;
|
|
74
|
+
private getAJVValidateFunction;
|
|
68
75
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { _isObject, _lazyValue } from '@naturalcycles/js-lib';
|
|
1
|
+
import { _isObject, _lazyValue, } from '@naturalcycles/js-lib';
|
|
2
2
|
import { JsonSchemaAnyBuilder } from '@naturalcycles/js-lib/json-schema';
|
|
3
|
-
import { _filterNullishValues } from '@naturalcycles/js-lib/object';
|
|
3
|
+
import { _deepCopy, _filterNullishValues } from '@naturalcycles/js-lib/object';
|
|
4
4
|
import { _substringBefore } from '@naturalcycles/js-lib/string';
|
|
5
5
|
import { _inspect } from '../../string/inspect.js';
|
|
6
6
|
import { AjvValidationError } from './ajvValidationError.js';
|
|
@@ -19,22 +19,11 @@ export class AjvSchema {
|
|
|
19
19
|
lazy: false,
|
|
20
20
|
...cfg,
|
|
21
21
|
ajv: cfg.ajv || getAjv(),
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
// getAjv({
|
|
25
|
-
// schemas: cfg.schemas?.map(s => {
|
|
26
|
-
// if (s instanceof AjvSchema) return s.schema
|
|
27
|
-
// if (s instanceof JsonSchemaAnyBuilder) return s.build()
|
|
28
|
-
// return s as JsonSchema
|
|
29
|
-
// }),
|
|
30
|
-
// coerceTypes: cfg.coerceTypes || false,
|
|
31
|
-
// // verbose: true,
|
|
32
|
-
// }),
|
|
33
|
-
// Auto-detecting "ObjectName" from $id of the schema (e.g "Address.schema.json")
|
|
34
|
-
objectName: cfg.objectName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
|
|
22
|
+
// Auto-detecting "InputName" from $id of the schema (e.g "Address.schema.json")
|
|
23
|
+
inputName: cfg.inputName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
|
|
35
24
|
};
|
|
36
25
|
if (!cfg.lazy) {
|
|
37
|
-
this.
|
|
26
|
+
this.getAJVValidateFunction(); // compile eagerly
|
|
38
27
|
}
|
|
39
28
|
}
|
|
40
29
|
/**
|
|
@@ -64,7 +53,6 @@ export class AjvSchema {
|
|
|
64
53
|
return new AjvSchema(schema, cfg);
|
|
65
54
|
}
|
|
66
55
|
cfg;
|
|
67
|
-
getValidateFunction = _lazyValue(() => this.cfg.ajv.compile(this.schema));
|
|
68
56
|
/**
|
|
69
57
|
* It returns the original object just for convenience.
|
|
70
58
|
* Reminder: Ajv will MUTATE your object under 2 circumstances:
|
|
@@ -73,32 +61,47 @@ export class AjvSchema {
|
|
|
73
61
|
*
|
|
74
62
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
75
63
|
*/
|
|
76
|
-
validate(
|
|
77
|
-
const err = this.
|
|
64
|
+
validate(input, opt = {}) {
|
|
65
|
+
const [err, output] = this.getValidationResult(input, opt);
|
|
78
66
|
if (err)
|
|
79
67
|
throw err;
|
|
80
|
-
return
|
|
68
|
+
return output;
|
|
81
69
|
}
|
|
82
|
-
isValid(
|
|
83
|
-
|
|
70
|
+
isValid(input, opt) {
|
|
71
|
+
const [err] = this.getValidationResult(input, opt);
|
|
72
|
+
return !err;
|
|
84
73
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
74
|
+
getValidationResult(input, opt = {}) {
|
|
75
|
+
const fn = this.getAJVValidateFunction();
|
|
76
|
+
const item = opt.mutateInput || typeof input !== 'object' ? input : _deepCopy(input);
|
|
77
|
+
const valid = fn(item); // mutates item
|
|
78
|
+
if (valid)
|
|
79
|
+
return [null, item];
|
|
80
|
+
const errors = fn.errors;
|
|
81
|
+
const { inputId = _isObject(input) ? input['id'] : undefined, inputName = this.cfg.inputName || 'Object', } = opt;
|
|
82
|
+
const dataVar = [inputName, inputId].filter(Boolean).join('.');
|
|
91
83
|
let message = this.cfg.ajv.errorsText(errors, {
|
|
92
|
-
dataVar
|
|
84
|
+
dataVar,
|
|
93
85
|
separator,
|
|
94
86
|
});
|
|
95
|
-
const inputStringified = _inspect(
|
|
87
|
+
const inputStringified = _inspect(input, { maxLen: 4000 });
|
|
96
88
|
message = [message, 'Input: ' + inputStringified].join(separator);
|
|
97
|
-
|
|
89
|
+
const err = new AjvValidationError(message, _filterNullishValues({
|
|
98
90
|
errors,
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
inputName,
|
|
92
|
+
inputId,
|
|
101
93
|
}));
|
|
94
|
+
return [err, item];
|
|
102
95
|
}
|
|
96
|
+
getValidationFunction() {
|
|
97
|
+
return (input, opt) => {
|
|
98
|
+
return this.getValidationResult(input, {
|
|
99
|
+
mutateInput: opt?.mutateInput,
|
|
100
|
+
inputName: opt?.inputName,
|
|
101
|
+
inputId: opt?.inputId,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile(this.schema));
|
|
103
106
|
}
|
|
104
107
|
const separator = '\n';
|
|
@@ -3,8 +3,8 @@ import { AppError } from '@naturalcycles/js-lib/error/error.util.js';
|
|
|
3
3
|
import type { ErrorObject } from 'ajv';
|
|
4
4
|
export interface AjvValidationErrorData extends ErrorData {
|
|
5
5
|
errors: ErrorObject[];
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
inputName?: string;
|
|
7
|
+
inputId?: string;
|
|
8
8
|
}
|
|
9
9
|
export declare class AjvValidationError extends AppError<AjvValidationErrorData> {
|
|
10
10
|
constructor(message: string, data: AjvValidationErrorData);
|
|
@@ -6,7 +6,7 @@ import { Ajv } from 'ajv';
|
|
|
6
6
|
* This function should be used as much as possible,
|
|
7
7
|
* to benefit from cached Ajv instance.
|
|
8
8
|
*/
|
|
9
|
-
export declare const getAjv:
|
|
9
|
+
export declare const getAjv: any;
|
|
10
10
|
/**
|
|
11
11
|
* Create Ajv with modified defaults.
|
|
12
12
|
*
|
|
@@ -10,7 +10,7 @@ export declare const numberSchema: NumberSchema<number>;
|
|
|
10
10
|
export declare const numberSchemaTyped: <T>() => NumberSchema<T>;
|
|
11
11
|
export declare const integerSchema: NumberSchema<number>;
|
|
12
12
|
export declare const percentageSchema: NumberSchema<number>;
|
|
13
|
-
export declare const dateStringSchema: StringSchema<
|
|
13
|
+
export declare const dateStringSchema: StringSchema<IsoDate>;
|
|
14
14
|
export declare const binarySchema: import("joi").BinarySchema<Buffer<ArrayBufferLike>>;
|
|
15
15
|
export declare const dateObjectSchema: ObjectSchema<any>;
|
|
16
16
|
export declare const dateIntervalStringSchema: StringSchema<string>;
|
|
@@ -13,8 +13,8 @@ import type { ValidationErrorItem } from 'joi';
|
|
|
13
13
|
*/
|
|
14
14
|
export interface JoiValidationErrorData extends ErrorData {
|
|
15
15
|
joiValidationErrorItems: ValidationErrorItem[];
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
joiValidationInputName?: string;
|
|
17
|
+
joiValidationInputId?: string;
|
|
18
18
|
/**
|
|
19
19
|
* Error "annotation" is stripped in Error.message.
|
|
20
20
|
* This field contains the "full" annotation.
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
+
import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
1
2
|
import type { AnySchema, ValidationOptions } from 'joi';
|
|
2
3
|
import { JoiValidationError } from './joi.validation.error.js';
|
|
3
|
-
export
|
|
4
|
-
value: T;
|
|
5
|
-
error?: JoiValidationError;
|
|
6
|
-
}
|
|
4
|
+
export declare function getJoiValidationFunction<T>(schema: AnySchema<T>): ValidationFunction<T, JoiValidationError>;
|
|
7
5
|
/**
|
|
8
6
|
* Validates with Joi.
|
|
9
7
|
* Throws JoiValidationError if invalid.
|
|
@@ -11,7 +9,7 @@ export interface JoiValidationResult<T = any> {
|
|
|
11
9
|
*
|
|
12
10
|
* If `schema` is undefined - returns value as is.
|
|
13
11
|
*/
|
|
14
|
-
export declare function validate<T>(input: any, schema?: AnySchema<T>,
|
|
12
|
+
export declare function validate<T>(input: any, schema?: AnySchema<T>, inputName?: string, opt?: ValidationOptions): T;
|
|
15
13
|
/**
|
|
16
14
|
* Validates with Joi.
|
|
17
15
|
* Returns JoiValidationResult with converted value and error (if any).
|
|
@@ -21,7 +19,7 @@ export declare function validate<T>(input: any, schema?: AnySchema<T>, objectNam
|
|
|
21
19
|
*
|
|
22
20
|
* If `schema` is undefined - returns value as is.
|
|
23
21
|
*/
|
|
24
|
-
export declare function getValidationResult<T>(input:
|
|
22
|
+
export declare function getValidationResult<T>(input: T, schema?: AnySchema<T>, inputName?: string, options?: ValidationOptions): ValidationFunctionResult<T, JoiValidationError>;
|
|
25
23
|
/**
|
|
26
24
|
* Convenience function that returns true if !error.
|
|
27
25
|
*/
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* "Converts" mean e.g trims all strings from leading/trailing spaces.
|
|
7
7
|
*/
|
|
8
|
-
import { _hb, _isObject } from '@naturalcycles/js-lib';
|
|
8
|
+
import { _hb, _isObject, } from '@naturalcycles/js-lib';
|
|
9
|
+
import { _assert } from '@naturalcycles/js-lib/error/assert.js';
|
|
9
10
|
import { _truncateMiddle } from '@naturalcycles/js-lib/string/string.util.js';
|
|
10
11
|
import { JoiValidationError } from './joi.validation.error.js';
|
|
11
12
|
// Strip colors in production (for e.g Sentry reporting)
|
|
@@ -31,6 +32,12 @@ const defaultOptions = {
|
|
|
31
32
|
// stack: true,
|
|
32
33
|
// }
|
|
33
34
|
};
|
|
35
|
+
export function getJoiValidationFunction(schema) {
|
|
36
|
+
return (input, opt) => {
|
|
37
|
+
_assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Joi');
|
|
38
|
+
return getValidationResult(input, schema, opt?.inputName);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
34
41
|
/**
|
|
35
42
|
* Validates with Joi.
|
|
36
43
|
* Throws JoiValidationError if invalid.
|
|
@@ -38,11 +45,10 @@ const defaultOptions = {
|
|
|
38
45
|
*
|
|
39
46
|
* If `schema` is undefined - returns value as is.
|
|
40
47
|
*/
|
|
41
|
-
export function validate(input, schema,
|
|
42
|
-
const
|
|
43
|
-
if (error)
|
|
48
|
+
export function validate(input, schema, inputName, opt = {}) {
|
|
49
|
+
const [error, returnValue] = getValidationResult(input, schema, inputName, opt);
|
|
50
|
+
if (error)
|
|
44
51
|
throw error;
|
|
45
|
-
}
|
|
46
52
|
return returnValue;
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
@@ -54,20 +60,15 @@ export function validate(input, schema, objectName, opt = {}) {
|
|
|
54
60
|
*
|
|
55
61
|
* If `schema` is undefined - returns value as is.
|
|
56
62
|
*/
|
|
57
|
-
export function getValidationResult(input, schema,
|
|
63
|
+
export function getValidationResult(input, schema, inputName, options = {}) {
|
|
58
64
|
if (!schema)
|
|
59
|
-
return
|
|
65
|
+
return [null, input];
|
|
60
66
|
const { value, error } = schema.validate(input, {
|
|
61
67
|
...defaultOptions,
|
|
62
68
|
...options,
|
|
63
69
|
});
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
};
|
|
67
|
-
if (error) {
|
|
68
|
-
vr.error = createError(input, error, objectName);
|
|
69
|
-
}
|
|
70
|
-
return vr;
|
|
70
|
+
const err = error ? createError(input, error, inputName) : null;
|
|
71
|
+
return [err, value];
|
|
71
72
|
}
|
|
72
73
|
/**
|
|
73
74
|
* Convenience function that returns true if !error.
|
|
@@ -95,12 +96,12 @@ export function convert(input, schema) {
|
|
|
95
96
|
const { value } = schema.validate(input, defaultOptions);
|
|
96
97
|
return value;
|
|
97
98
|
}
|
|
98
|
-
function createError(value, err,
|
|
99
|
+
function createError(value, err, inputName) {
|
|
99
100
|
const tokens = [];
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
tokens.push('Invalid ' + [
|
|
101
|
+
const inputId = _isObject(value) ? value['id'] : undefined;
|
|
102
|
+
if (inputId || inputName) {
|
|
103
|
+
inputName ||= value?.constructor?.name;
|
|
104
|
+
tokens.push('Invalid ' + [inputName, inputId].filter(Boolean).join('.'));
|
|
104
105
|
}
|
|
105
106
|
const annotation = err.annotate(stripColors);
|
|
106
107
|
if (annotation.length > 100) {
|
|
@@ -123,8 +124,8 @@ function createError(value, err, objectName) {
|
|
|
123
124
|
const msg = tokens.join('\n');
|
|
124
125
|
const data = {
|
|
125
126
|
joiValidationErrorItems: err.details,
|
|
126
|
-
...(
|
|
127
|
-
...(
|
|
127
|
+
...(inputName && { joiValidationInputName: inputName }),
|
|
128
|
+
...(inputId && { joiValidationInputId: inputId }),
|
|
128
129
|
};
|
|
129
130
|
// Make annotation non-enumerable, to not get it automatically printed,
|
|
130
131
|
// but still accessible
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naturalcycles/nodejs-lib",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "15.
|
|
4
|
+
"version": "15.9.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@naturalcycles/js-lib": "^15",
|
|
7
7
|
"@types/js-yaml": "^4",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@types/through2-concurrent": "^2",
|
|
26
|
-
"@naturalcycles/dev-lib": "
|
|
26
|
+
"@naturalcycles/dev-lib": "19.22.0"
|
|
27
27
|
},
|
|
28
28
|
"exports": {
|
|
29
29
|
".": "./dist/index.js",
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
_isObject,
|
|
3
|
+
_lazyValue,
|
|
4
|
+
type ValidationFunction,
|
|
5
|
+
type ValidationFunctionResult,
|
|
6
|
+
} from '@naturalcycles/js-lib'
|
|
2
7
|
import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema'
|
|
3
8
|
import { JsonSchemaAnyBuilder } from '@naturalcycles/js-lib/json-schema'
|
|
4
|
-
import { _filterNullishValues } from '@naturalcycles/js-lib/object'
|
|
9
|
+
import { _deepCopy, _filterNullishValues } from '@naturalcycles/js-lib/object'
|
|
5
10
|
import { _substringBefore } from '@naturalcycles/js-lib/string'
|
|
6
11
|
import type { Ajv } from 'ajv'
|
|
7
12
|
import { _inspect } from '../../string/inspect.js'
|
|
@@ -9,8 +14,17 @@ import { AjvValidationError } from './ajvValidationError.js'
|
|
|
9
14
|
import { getAjv } from './getAjv.js'
|
|
10
15
|
|
|
11
16
|
export interface AjvValidationOptions {
|
|
12
|
-
|
|
13
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Defaults to false.
|
|
19
|
+
*
|
|
20
|
+
* If set to true - AJV will mutate the input in case it needs to apply transformations
|
|
21
|
+
* (strip unknown properties, convert types, etc).
|
|
22
|
+
*
|
|
23
|
+
* If false - it will deep-clone the input to prevent its mutation. Will return the cloned/mutated object.
|
|
24
|
+
*/
|
|
25
|
+
mutateInput?: boolean
|
|
26
|
+
inputName?: string
|
|
27
|
+
inputId?: string
|
|
14
28
|
}
|
|
15
29
|
|
|
16
30
|
export interface AjvSchemaCfg {
|
|
@@ -20,13 +34,7 @@ export interface AjvSchemaCfg {
|
|
|
20
34
|
*/
|
|
21
35
|
ajv: Ajv
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
* Dependent schemas to pass to Ajv instance constructor.
|
|
25
|
-
* Simpler than instantiating and passing ajv instance yourself.
|
|
26
|
-
*/
|
|
27
|
-
// schemas?: (JsonSchema | JsonSchemaBuilder | AjvSchema)[]
|
|
28
|
-
|
|
29
|
-
objectName?: string
|
|
37
|
+
inputName?: string
|
|
30
38
|
|
|
31
39
|
/**
|
|
32
40
|
* Option of Ajv.
|
|
@@ -59,23 +67,12 @@ export class AjvSchema<T = unknown> {
|
|
|
59
67
|
lazy: false,
|
|
60
68
|
...cfg,
|
|
61
69
|
ajv: cfg.ajv || getAjv(),
|
|
62
|
-
//
|
|
63
|
-
|
|
64
|
-
// getAjv({
|
|
65
|
-
// schemas: cfg.schemas?.map(s => {
|
|
66
|
-
// if (s instanceof AjvSchema) return s.schema
|
|
67
|
-
// if (s instanceof JsonSchemaAnyBuilder) return s.build()
|
|
68
|
-
// return s as JsonSchema
|
|
69
|
-
// }),
|
|
70
|
-
// coerceTypes: cfg.coerceTypes || false,
|
|
71
|
-
// // verbose: true,
|
|
72
|
-
// }),
|
|
73
|
-
// Auto-detecting "ObjectName" from $id of the schema (e.g "Address.schema.json")
|
|
74
|
-
objectName: cfg.objectName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
|
|
70
|
+
// Auto-detecting "InputName" from $id of the schema (e.g "Address.schema.json")
|
|
71
|
+
inputName: cfg.inputName || (schema.$id ? _substringBefore(schema.$id, '.') : undefined),
|
|
75
72
|
}
|
|
76
73
|
|
|
77
74
|
if (!cfg.lazy) {
|
|
78
|
-
this.
|
|
75
|
+
this.getAJVValidateFunction() // compile eagerly
|
|
79
76
|
}
|
|
80
77
|
}
|
|
81
78
|
|
|
@@ -114,8 +111,6 @@ export class AjvSchema<T = unknown> {
|
|
|
114
111
|
|
|
115
112
|
readonly cfg: AjvSchemaCfg
|
|
116
113
|
|
|
117
|
-
private getValidateFunction = _lazyValue(() => this.cfg.ajv.compile<T>(this.schema))
|
|
118
|
-
|
|
119
114
|
/**
|
|
120
115
|
* It returns the original object just for convenience.
|
|
121
116
|
* Reminder: Ajv will MUTATE your object under 2 circumstances:
|
|
@@ -124,44 +119,66 @@ export class AjvSchema<T = unknown> {
|
|
|
124
119
|
*
|
|
125
120
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
126
121
|
*/
|
|
127
|
-
validate(
|
|
128
|
-
const err = this.
|
|
122
|
+
validate(input: T, opt: AjvValidationOptions = {}): T {
|
|
123
|
+
const [err, output] = this.getValidationResult(input, opt)
|
|
129
124
|
if (err) throw err
|
|
130
|
-
return
|
|
125
|
+
return output
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
isValid(
|
|
134
|
-
|
|
128
|
+
isValid(input: T, opt?: AjvValidationOptions): boolean {
|
|
129
|
+
const [err] = this.getValidationResult(input, opt)
|
|
130
|
+
return !err
|
|
135
131
|
}
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
133
|
+
getValidationResult(
|
|
134
|
+
input: T,
|
|
135
|
+
opt: AjvValidationOptions = {},
|
|
136
|
+
): ValidationFunctionResult<T, AjvValidationError> {
|
|
137
|
+
const fn = this.getAJVValidateFunction()
|
|
138
|
+
|
|
139
|
+
const item = opt.mutateInput || typeof input !== 'object' ? input : _deepCopy(input)
|
|
139
140
|
|
|
140
|
-
const
|
|
141
|
+
const valid = fn(item) // mutates item
|
|
142
|
+
if (valid) return [null, item]
|
|
143
|
+
|
|
144
|
+
const errors = fn.errors!
|
|
141
145
|
|
|
142
146
|
const {
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
inputId = _isObject(input) ? (input['id' as keyof T] as any) : undefined,
|
|
148
|
+
inputName = this.cfg.inputName || 'Object',
|
|
145
149
|
} = opt
|
|
146
|
-
const
|
|
150
|
+
const dataVar = [inputName, inputId].filter(Boolean).join('.')
|
|
147
151
|
|
|
148
152
|
let message = this.cfg.ajv.errorsText(errors, {
|
|
149
|
-
dataVar
|
|
153
|
+
dataVar,
|
|
150
154
|
separator,
|
|
151
155
|
})
|
|
152
156
|
|
|
153
|
-
const inputStringified = _inspect(
|
|
157
|
+
const inputStringified = _inspect(input, { maxLen: 4000 })
|
|
154
158
|
message = [message, 'Input: ' + inputStringified].join(separator)
|
|
155
159
|
|
|
156
|
-
|
|
160
|
+
const err = new AjvValidationError(
|
|
157
161
|
message,
|
|
158
162
|
_filterNullishValues({
|
|
159
163
|
errors,
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
inputName,
|
|
165
|
+
inputId,
|
|
162
166
|
}),
|
|
163
167
|
)
|
|
168
|
+
return [err, item]
|
|
164
169
|
}
|
|
170
|
+
|
|
171
|
+
getValidationFunction(): ValidationFunction<T, AjvValidationError> {
|
|
172
|
+
return (input, opt) => {
|
|
173
|
+
return this.getValidationResult(input, {
|
|
174
|
+
mutateInput: opt?.mutateInput,
|
|
175
|
+
inputName: opt?.inputName,
|
|
176
|
+
inputId: opt?.inputId,
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private getAJVValidateFunction = _lazyValue(() => this.cfg.ajv.compile<T>(this.schema))
|
|
165
182
|
}
|
|
166
183
|
|
|
167
184
|
const separator = '\n'
|
|
@@ -4,8 +4,8 @@ import type { ErrorObject } from 'ajv'
|
|
|
4
4
|
|
|
5
5
|
export interface AjvValidationErrorData extends ErrorData {
|
|
6
6
|
errors: ErrorObject[]
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
inputName?: string
|
|
8
|
+
inputId?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export class AjvValidationError extends AppError<AjvValidationErrorData> {
|
|
@@ -14,8 +14,8 @@ import type { ValidationErrorItem } from 'joi'
|
|
|
14
14
|
*/
|
|
15
15
|
export interface JoiValidationErrorData extends ErrorData {
|
|
16
16
|
joiValidationErrorItems: ValidationErrorItem[]
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
joiValidationInputName?: string
|
|
18
|
+
joiValidationInputId?: string
|
|
19
19
|
/**
|
|
20
20
|
* Error "annotation" is stripped in Error.message.
|
|
21
21
|
* This field contains the "full" annotation.
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
* "Converts" mean e.g trims all strings from leading/trailing spaces.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
_hb,
|
|
11
|
+
_isObject,
|
|
12
|
+
type ValidationFunction,
|
|
13
|
+
type ValidationFunctionResult,
|
|
14
|
+
} from '@naturalcycles/js-lib'
|
|
15
|
+
import { _assert } from '@naturalcycles/js-lib/error/assert.js'
|
|
10
16
|
import { _truncateMiddle } from '@naturalcycles/js-lib/string/string.util.js'
|
|
11
17
|
import type { AnySchema, ValidationError, ValidationOptions } from 'joi'
|
|
12
18
|
import type { JoiValidationErrorData } from './joi.validation.error.js'
|
|
13
19
|
import { JoiValidationError } from './joi.validation.error.js'
|
|
14
20
|
|
|
15
|
-
// todo: consider replacing with Tuple of [error, value]
|
|
16
|
-
export interface JoiValidationResult<T = any> {
|
|
17
|
-
value: T
|
|
18
|
-
error?: JoiValidationError
|
|
19
|
-
}
|
|
20
|
-
|
|
21
21
|
// Strip colors in production (for e.g Sentry reporting)
|
|
22
22
|
// const stripColors = process.env.NODE_ENV === 'production' || !!process.env.GAE_INSTANCE
|
|
23
23
|
// Currently colors do more bad than good, so let's strip them always for now
|
|
@@ -43,6 +43,15 @@ const defaultOptions: ValidationOptions = {
|
|
|
43
43
|
// }
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
export function getJoiValidationFunction<T>(
|
|
47
|
+
schema: AnySchema<T>,
|
|
48
|
+
): ValidationFunction<T, JoiValidationError> {
|
|
49
|
+
return (input, opt) => {
|
|
50
|
+
_assert(!opt?.mutateInput, 'mutateInput=true is not yet supported with Joi')
|
|
51
|
+
return getValidationResult(input, schema, opt?.inputName)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
/**
|
|
47
56
|
* Validates with Joi.
|
|
48
57
|
* Throws JoiValidationError if invalid.
|
|
@@ -53,15 +62,11 @@ const defaultOptions: ValidationOptions = {
|
|
|
53
62
|
export function validate<T>(
|
|
54
63
|
input: any,
|
|
55
64
|
schema?: AnySchema<T>,
|
|
56
|
-
|
|
65
|
+
inputName?: string,
|
|
57
66
|
opt: ValidationOptions = {},
|
|
58
67
|
): T {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
if (error) {
|
|
62
|
-
throw error
|
|
63
|
-
}
|
|
64
|
-
|
|
68
|
+
const [error, returnValue] = getValidationResult(input, schema, inputName, opt)
|
|
69
|
+
if (error) throw error
|
|
65
70
|
return returnValue
|
|
66
71
|
}
|
|
67
72
|
|
|
@@ -75,27 +80,20 @@ export function validate<T>(
|
|
|
75
80
|
* If `schema` is undefined - returns value as is.
|
|
76
81
|
*/
|
|
77
82
|
export function getValidationResult<T>(
|
|
78
|
-
input:
|
|
83
|
+
input: T,
|
|
79
84
|
schema?: AnySchema<T>,
|
|
80
|
-
|
|
85
|
+
inputName?: string,
|
|
81
86
|
options: ValidationOptions = {},
|
|
82
|
-
):
|
|
83
|
-
if (!schema) return
|
|
87
|
+
): ValidationFunctionResult<T, JoiValidationError> {
|
|
88
|
+
if (!schema) return [null, input]
|
|
84
89
|
|
|
85
90
|
const { value, error } = schema.validate(input, {
|
|
86
91
|
...defaultOptions,
|
|
87
92
|
...options,
|
|
88
93
|
})
|
|
89
94
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
if (error) {
|
|
95
|
-
vr.error = createError(input, error, objectName)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return vr
|
|
95
|
+
const err = error ? createError(input, error, inputName) : null
|
|
96
|
+
return [err, value]
|
|
99
97
|
}
|
|
100
98
|
|
|
101
99
|
/**
|
|
@@ -127,15 +125,15 @@ export function convert<T>(input: any, schema?: AnySchema<T>): T {
|
|
|
127
125
|
return value
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
function createError(value: any, err: ValidationError,
|
|
128
|
+
function createError(value: any, err: ValidationError, inputName?: string): JoiValidationError {
|
|
131
129
|
const tokens: string[] = []
|
|
132
130
|
|
|
133
|
-
const
|
|
131
|
+
const inputId = _isObject(value) ? (value['id'] as string) : undefined
|
|
134
132
|
|
|
135
|
-
if (
|
|
136
|
-
|
|
133
|
+
if (inputId || inputName) {
|
|
134
|
+
inputName ||= value?.constructor?.name
|
|
137
135
|
|
|
138
|
-
tokens.push('Invalid ' + [
|
|
136
|
+
tokens.push('Invalid ' + [inputName, inputId].filter(Boolean).join('.'))
|
|
139
137
|
}
|
|
140
138
|
|
|
141
139
|
const annotation = err.annotate(stripColors)
|
|
@@ -168,8 +166,8 @@ function createError(value: any, err: ValidationError, objectName?: string): Joi
|
|
|
168
166
|
|
|
169
167
|
const data: JoiValidationErrorData = {
|
|
170
168
|
joiValidationErrorItems: err.details,
|
|
171
|
-
...(
|
|
172
|
-
...(
|
|
169
|
+
...(inputName && { joiValidationInputName: inputName }),
|
|
170
|
+
...(inputId && { joiValidationInputId: inputId }),
|
|
173
171
|
}
|
|
174
172
|
|
|
175
173
|
// Make annotation non-enumerable, to not get it automatically printed,
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
const started = Date.now()
|
|
2
|
-
import { workerData, parentPort } from 'node:worker_threads'
|
|
3
|
-
import { inspect } from 'node:util'
|
|
4
|
-
const { workerFile, workerIndex, logEvery = 1000, metric = 'worker' } = workerData || {}
|
|
5
|
-
|
|
6
|
-
if (!workerFile) {
|
|
7
|
-
throw new Error('workerData.workerFile is required!')
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
// console.log(`worker#${workerIndex} created`)
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
// require('esbuild-register') // alternative
|
|
14
|
-
// require('ts-node/register/transpile-only')
|
|
15
|
-
// require('tsx/cjs/api').register() // https://tsx.is/dev-api/register-cjs
|
|
16
|
-
const { register } = await import('tsx/esm/api')
|
|
17
|
-
register() // https://tsx.is/dev-api/register-esm
|
|
18
|
-
// require('tsconfig-paths/register')
|
|
19
|
-
} catch {} // require if exists
|
|
20
|
-
|
|
21
|
-
const { WorkerClass } = await import(workerFile)
|
|
22
|
-
const worker = new WorkerClass(workerData)
|
|
23
|
-
|
|
24
|
-
console.log(`${metric}#${workerIndex} loaded in ${Date.now() - started} ms`)
|
|
25
|
-
|
|
26
|
-
let errors = 0
|
|
27
|
-
let processed = 0
|
|
28
|
-
|
|
29
|
-
parentPort.on('message', async msg => {
|
|
30
|
-
if (msg === null) {
|
|
31
|
-
// console.log(`EXIT (null) received by ${index}, exiting`)
|
|
32
|
-
parentPort.close()
|
|
33
|
-
|
|
34
|
-
logStats(true)
|
|
35
|
-
|
|
36
|
-
return
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// console.log(`message received by worker ${index}: `, msg)
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const out = await worker.process(msg.payload, msg.index)
|
|
43
|
-
|
|
44
|
-
parentPort.postMessage({
|
|
45
|
-
index: msg.index,
|
|
46
|
-
payload: out,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
processed++
|
|
50
|
-
|
|
51
|
-
if (processed % logEvery === 0) logStats()
|
|
52
|
-
} catch (err) {
|
|
53
|
-
parentPort.postMessage({
|
|
54
|
-
index: msg.index,
|
|
55
|
-
error: err,
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
errors++
|
|
59
|
-
console.log(`${metric}#${workerIndex} errors: ${errors}`)
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
const inspectOpt = {
|
|
64
|
-
colors: true,
|
|
65
|
-
breakLength: 120,
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function logStats(final) {
|
|
69
|
-
const { rss, heapUsed, heapTotal, external } = process.memoryUsage()
|
|
70
|
-
|
|
71
|
-
console.log(
|
|
72
|
-
inspect(
|
|
73
|
-
{
|
|
74
|
-
[`${metric}${workerIndex}`]: processed,
|
|
75
|
-
errors,
|
|
76
|
-
heapUsed: mb(heapUsed),
|
|
77
|
-
heapTotal: mb(heapTotal),
|
|
78
|
-
rss: mb(rss),
|
|
79
|
-
external: mb(external),
|
|
80
|
-
...(final ? { final: true } : {}),
|
|
81
|
-
},
|
|
82
|
-
inspectOpt,
|
|
83
|
-
),
|
|
84
|
-
)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function mb(b) {
|
|
88
|
-
return Math.round(b / (1024 * 1024))
|
|
89
|
-
}
|