@naturalcycles/nodejs-lib 15.8.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 +16 -10
- package/dist/validation/ajv/ajvSchema.js +33 -31
- 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 -3
- package/dist/validation/joi/joi.validation.util.js +19 -12
- package/package.json +2 -2
- package/src/validation/ajv/ajvSchema.ts +53 -40
- 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 +27 -12
package/dist/log/log.util.d.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
1
|
+
import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
2
2
|
import type { JsonSchema, JsonSchemaBuilder } from '@naturalcycles/js-lib/json-schema';
|
|
3
3
|
import type { Ajv } from 'ajv';
|
|
4
4
|
import { AjvValidationError } from './ajvValidationError.js';
|
|
5
5
|
export interface AjvValidationOptions {
|
|
6
|
-
|
|
7
|
-
|
|
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;
|
|
8
17
|
}
|
|
9
18
|
export interface AjvSchemaCfg {
|
|
10
19
|
/**
|
|
@@ -12,11 +21,7 @@ export interface AjvSchemaCfg {
|
|
|
12
21
|
* AjvSchema default (not the same as Ajv defaults) parameters
|
|
13
22
|
*/
|
|
14
23
|
ajv: Ajv;
|
|
15
|
-
|
|
16
|
-
* Dependent schemas to pass to Ajv instance constructor.
|
|
17
|
-
* Simpler than instantiating and passing ajv instance yourself.
|
|
18
|
-
*/
|
|
19
|
-
objectName?: string;
|
|
24
|
+
inputName?: string;
|
|
20
25
|
/**
|
|
21
26
|
* Option of Ajv.
|
|
22
27
|
* If set to true - will mutate your input objects!
|
|
@@ -54,7 +59,6 @@ export declare class AjvSchema<T = unknown> {
|
|
|
54
59
|
*/
|
|
55
60
|
static create<T>(schema: JsonSchemaBuilder<T> | JsonSchema<T> | AjvSchema<T>, cfg?: Partial<AjvSchemaCfg>): AjvSchema<T>;
|
|
56
61
|
readonly cfg: AjvSchemaCfg;
|
|
57
|
-
private getValidateFunction;
|
|
58
62
|
/**
|
|
59
63
|
* It returns the original object just for convenience.
|
|
60
64
|
* Reminder: Ajv will MUTATE your object under 2 circumstances:
|
|
@@ -64,6 +68,8 @@ export declare class AjvSchema<T = unknown> {
|
|
|
64
68
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
65
69
|
*/
|
|
66
70
|
validate(input: T, opt?: AjvValidationOptions): T;
|
|
67
|
-
isValid(input: T): boolean;
|
|
71
|
+
isValid(input: T, opt?: AjvValidationOptions): boolean;
|
|
68
72
|
getValidationResult(input: T, opt?: AjvValidationOptions): ValidationFunctionResult<T, AjvValidationError>;
|
|
73
|
+
getValidationFunction(): ValidationFunction<T, AjvValidationError>;
|
|
74
|
+
private getAJVValidateFunction;
|
|
69
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:
|
|
@@ -74,32 +62,46 @@ export class AjvSchema {
|
|
|
74
62
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
75
63
|
*/
|
|
76
64
|
validate(input, opt = {}) {
|
|
77
|
-
const [
|
|
78
|
-
if (
|
|
79
|
-
throw
|
|
65
|
+
const [err, output] = this.getValidationResult(input, opt);
|
|
66
|
+
if (err)
|
|
67
|
+
throw err;
|
|
80
68
|
return output;
|
|
81
69
|
}
|
|
82
|
-
isValid(input) {
|
|
83
|
-
|
|
70
|
+
isValid(input, opt) {
|
|
71
|
+
const [err] = this.getValidationResult(input, opt);
|
|
72
|
+
return !err;
|
|
84
73
|
}
|
|
85
74
|
getValidationResult(input, opt = {}) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
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
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
|
}));
|
|
102
|
-
return [err,
|
|
94
|
+
return [err, item];
|
|
103
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));
|
|
104
106
|
}
|
|
105
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,6 +1,7 @@
|
|
|
1
|
-
import { type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
1
|
+
import { type ValidationFunction, type ValidationFunctionResult } from '@naturalcycles/js-lib';
|
|
2
2
|
import type { AnySchema, ValidationOptions } from 'joi';
|
|
3
3
|
import { JoiValidationError } from './joi.validation.error.js';
|
|
4
|
+
export declare function getJoiValidationFunction<T>(schema: AnySchema<T>): ValidationFunction<T, JoiValidationError>;
|
|
4
5
|
/**
|
|
5
6
|
* Validates with Joi.
|
|
6
7
|
* Throws JoiValidationError if invalid.
|
|
@@ -8,7 +9,7 @@ import { JoiValidationError } from './joi.validation.error.js';
|
|
|
8
9
|
*
|
|
9
10
|
* If `schema` is undefined - returns value as is.
|
|
10
11
|
*/
|
|
11
|
-
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;
|
|
12
13
|
/**
|
|
13
14
|
* Validates with Joi.
|
|
14
15
|
* Returns JoiValidationResult with converted value and error (if any).
|
|
@@ -18,7 +19,7 @@ export declare function validate<T>(input: any, schema?: AnySchema<T>, objectNam
|
|
|
18
19
|
*
|
|
19
20
|
* If `schema` is undefined - returns value as is.
|
|
20
21
|
*/
|
|
21
|
-
export declare function getValidationResult<T>(input: T, schema?: AnySchema<T>,
|
|
22
|
+
export declare function getValidationResult<T>(input: T, schema?: AnySchema<T>, inputName?: string, options?: ValidationOptions): ValidationFunctionResult<T, JoiValidationError>;
|
|
22
23
|
/**
|
|
23
24
|
* Convenience function that returns true if !error.
|
|
24
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,8 +45,8 @@ const defaultOptions = {
|
|
|
38
45
|
*
|
|
39
46
|
* If `schema` is undefined - returns value as is.
|
|
40
47
|
*/
|
|
41
|
-
export function validate(input, schema,
|
|
42
|
-
const [error, returnValue] = getValidationResult(input, schema,
|
|
48
|
+
export function validate(input, schema, inputName, opt = {}) {
|
|
49
|
+
const [error, returnValue] = getValidationResult(input, schema, inputName, opt);
|
|
43
50
|
if (error)
|
|
44
51
|
throw error;
|
|
45
52
|
return returnValue;
|
|
@@ -53,14 +60,14 @@ export function validate(input, schema, objectName, opt = {}) {
|
|
|
53
60
|
*
|
|
54
61
|
* If `schema` is undefined - returns value as is.
|
|
55
62
|
*/
|
|
56
|
-
export function getValidationResult(input, schema,
|
|
63
|
+
export function getValidationResult(input, schema, inputName, options = {}) {
|
|
57
64
|
if (!schema)
|
|
58
65
|
return [null, input];
|
|
59
66
|
const { value, error } = schema.validate(input, {
|
|
60
67
|
...defaultOptions,
|
|
61
68
|
...options,
|
|
62
69
|
});
|
|
63
|
-
const err = error ? createError(input, error,
|
|
70
|
+
const err = error ? createError(input, error, inputName) : null;
|
|
64
71
|
return [err, value];
|
|
65
72
|
}
|
|
66
73
|
/**
|
|
@@ -89,12 +96,12 @@ export function convert(input, schema) {
|
|
|
89
96
|
const { value } = schema.validate(input, defaultOptions);
|
|
90
97
|
return value;
|
|
91
98
|
}
|
|
92
|
-
function createError(value, err,
|
|
99
|
+
function createError(value, err, inputName) {
|
|
93
100
|
const tokens = [];
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
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('.'));
|
|
98
105
|
}
|
|
99
106
|
const annotation = err.annotate(stripColors);
|
|
100
107
|
if (annotation.length > 100) {
|
|
@@ -117,8 +124,8 @@ function createError(value, err, objectName) {
|
|
|
117
124
|
const msg = tokens.join('\n');
|
|
118
125
|
const data = {
|
|
119
126
|
joiValidationErrorItems: err.details,
|
|
120
|
-
...(
|
|
121
|
-
...(
|
|
127
|
+
...(inputName && { joiValidationInputName: inputName }),
|
|
128
|
+
...(inputId && { joiValidationInputId: inputId }),
|
|
122
129
|
};
|
|
123
130
|
// Make annotation non-enumerable, to not get it automatically printed,
|
|
124
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": "19.
|
|
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:
|
|
@@ -125,31 +120,37 @@ export class AjvSchema<T = unknown> {
|
|
|
125
120
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
126
121
|
*/
|
|
127
122
|
validate(input: T, opt: AjvValidationOptions = {}): T {
|
|
128
|
-
const [
|
|
129
|
-
if (
|
|
123
|
+
const [err, output] = this.getValidationResult(input, opt)
|
|
124
|
+
if (err) throw err
|
|
130
125
|
return output
|
|
131
126
|
}
|
|
132
127
|
|
|
133
|
-
isValid(input: T): boolean {
|
|
134
|
-
|
|
128
|
+
isValid(input: T, opt?: AjvValidationOptions): boolean {
|
|
129
|
+
const [err] = this.getValidationResult(input, opt)
|
|
130
|
+
return !err
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
getValidationResult(
|
|
138
134
|
input: T,
|
|
139
135
|
opt: AjvValidationOptions = {},
|
|
140
136
|
): ValidationFunctionResult<T, AjvValidationError> {
|
|
141
|
-
|
|
137
|
+
const fn = this.getAJVValidateFunction()
|
|
138
|
+
|
|
139
|
+
const item = opt.mutateInput || typeof input !== 'object' ? input : _deepCopy(input)
|
|
142
140
|
|
|
143
|
-
const
|
|
141
|
+
const valid = fn(item) // mutates item
|
|
142
|
+
if (valid) return [null, item]
|
|
143
|
+
|
|
144
|
+
const errors = fn.errors!
|
|
144
145
|
|
|
145
146
|
const {
|
|
146
|
-
|
|
147
|
-
|
|
147
|
+
inputId = _isObject(input) ? (input['id' as keyof T] as any) : undefined,
|
|
148
|
+
inputName = this.cfg.inputName || 'Object',
|
|
148
149
|
} = opt
|
|
149
|
-
const
|
|
150
|
+
const dataVar = [inputName, inputId].filter(Boolean).join('.')
|
|
150
151
|
|
|
151
152
|
let message = this.cfg.ajv.errorsText(errors, {
|
|
152
|
-
dataVar
|
|
153
|
+
dataVar,
|
|
153
154
|
separator,
|
|
154
155
|
})
|
|
155
156
|
|
|
@@ -160,12 +161,24 @@ export class AjvSchema<T = unknown> {
|
|
|
160
161
|
message,
|
|
161
162
|
_filterNullishValues({
|
|
162
163
|
errors,
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
inputName,
|
|
165
|
+
inputId,
|
|
165
166
|
}),
|
|
166
167
|
)
|
|
167
|
-
return [err,
|
|
168
|
+
return [err, item]
|
|
168
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))
|
|
169
182
|
}
|
|
170
183
|
|
|
171
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,7 +6,13 @@
|
|
|
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'
|
|
@@ -37,6 +43,15 @@ const defaultOptions: ValidationOptions = {
|
|
|
37
43
|
// }
|
|
38
44
|
}
|
|
39
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
|
+
|
|
40
55
|
/**
|
|
41
56
|
* Validates with Joi.
|
|
42
57
|
* Throws JoiValidationError if invalid.
|
|
@@ -47,10 +62,10 @@ const defaultOptions: ValidationOptions = {
|
|
|
47
62
|
export function validate<T>(
|
|
48
63
|
input: any,
|
|
49
64
|
schema?: AnySchema<T>,
|
|
50
|
-
|
|
65
|
+
inputName?: string,
|
|
51
66
|
opt: ValidationOptions = {},
|
|
52
67
|
): T {
|
|
53
|
-
const [error, returnValue] = getValidationResult(input, schema,
|
|
68
|
+
const [error, returnValue] = getValidationResult(input, schema, inputName, opt)
|
|
54
69
|
if (error) throw error
|
|
55
70
|
return returnValue
|
|
56
71
|
}
|
|
@@ -67,7 +82,7 @@ export function validate<T>(
|
|
|
67
82
|
export function getValidationResult<T>(
|
|
68
83
|
input: T,
|
|
69
84
|
schema?: AnySchema<T>,
|
|
70
|
-
|
|
85
|
+
inputName?: string,
|
|
71
86
|
options: ValidationOptions = {},
|
|
72
87
|
): ValidationFunctionResult<T, JoiValidationError> {
|
|
73
88
|
if (!schema) return [null, input]
|
|
@@ -77,7 +92,7 @@ export function getValidationResult<T>(
|
|
|
77
92
|
...options,
|
|
78
93
|
})
|
|
79
94
|
|
|
80
|
-
const err = error ? createError(input, error,
|
|
95
|
+
const err = error ? createError(input, error, inputName) : null
|
|
81
96
|
return [err, value]
|
|
82
97
|
}
|
|
83
98
|
|
|
@@ -110,15 +125,15 @@ export function convert<T>(input: any, schema?: AnySchema<T>): T {
|
|
|
110
125
|
return value
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
function createError(value: any, err: ValidationError,
|
|
128
|
+
function createError(value: any, err: ValidationError, inputName?: string): JoiValidationError {
|
|
114
129
|
const tokens: string[] = []
|
|
115
130
|
|
|
116
|
-
const
|
|
131
|
+
const inputId = _isObject(value) ? (value['id'] as string) : undefined
|
|
117
132
|
|
|
118
|
-
if (
|
|
119
|
-
|
|
133
|
+
if (inputId || inputName) {
|
|
134
|
+
inputName ||= value?.constructor?.name
|
|
120
135
|
|
|
121
|
-
tokens.push('Invalid ' + [
|
|
136
|
+
tokens.push('Invalid ' + [inputName, inputId].filter(Boolean).join('.'))
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
const annotation = err.annotate(stripColors)
|
|
@@ -151,8 +166,8 @@ function createError(value: any, err: ValidationError, objectName?: string): Joi
|
|
|
151
166
|
|
|
152
167
|
const data: JoiValidationErrorData = {
|
|
153
168
|
joiValidationErrorItems: err.details,
|
|
154
|
-
...(
|
|
155
|
-
...(
|
|
169
|
+
...(inputName && { joiValidationInputName: inputName }),
|
|
170
|
+
...(inputId && { joiValidationInputId: inputId }),
|
|
156
171
|
}
|
|
157
172
|
|
|
158
173
|
// Make annotation non-enumerable, to not get it automatically printed,
|