@naturalcycles/nodejs-lib 15.47.0 → 15.47.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/validation/ajv/getAjv.d.ts +7 -0
- package/dist/validation/ajv/getAjv.js +11 -0
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +2 -1
- package/dist/validation/ajv/jsonSchemaBuilder.js +20 -4
- package/dist/validation/ajv/jsonSchemaBuilder.util.d.ts +2 -0
- package/dist/validation/ajv/jsonSchemaBuilder.util.js +14 -0
- package/package.json +1 -1
- package/src/validation/ajv/getAjv.ts +13 -0
- package/src/validation/ajv/jsonSchemaBuilder.ts +26 -4
- package/src/validation/ajv/jsonSchemaBuilder.util.ts +14 -0
|
@@ -13,6 +13,13 @@ export declare const getAjv: any;
|
|
|
13
13
|
* and are not interested in transforming the data.
|
|
14
14
|
*/
|
|
15
15
|
export declare const getNonMutatingAjv: any;
|
|
16
|
+
/**
|
|
17
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
18
|
+
*
|
|
19
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
20
|
+
* typically: request path params and request query params.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getCoercingAjv: any;
|
|
16
23
|
/**
|
|
17
24
|
* Create Ajv with modified defaults.
|
|
18
25
|
*
|
|
@@ -19,6 +19,10 @@ const AJV_NON_MUTATING_OPTIONS = {
|
|
|
19
19
|
removeAdditional: false,
|
|
20
20
|
useDefaults: false,
|
|
21
21
|
};
|
|
22
|
+
const AJV_MUTATING_COERCING_OPTIONS = {
|
|
23
|
+
...AJV_OPTIONS,
|
|
24
|
+
coerceTypes: true,
|
|
25
|
+
};
|
|
22
26
|
/**
|
|
23
27
|
* Return cached instance of Ajv with default (recommended) options.
|
|
24
28
|
*
|
|
@@ -33,6 +37,13 @@ export const getAjv = _lazyValue(createAjv);
|
|
|
33
37
|
* and are not interested in transforming the data.
|
|
34
38
|
*/
|
|
35
39
|
export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS));
|
|
40
|
+
/**
|
|
41
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
42
|
+
*
|
|
43
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
44
|
+
* typically: request path params and request query params.
|
|
45
|
+
*/
|
|
46
|
+
export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS));
|
|
36
47
|
/**
|
|
37
48
|
* Create Ajv with modified defaults.
|
|
38
49
|
*
|
|
@@ -208,9 +208,10 @@ export declare class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder<string
|
|
|
208
208
|
constructor();
|
|
209
209
|
}
|
|
210
210
|
export declare class JsonSchemaEnumBuilder<IN extends string | number | boolean | null, OUT extends IN = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
211
|
-
constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt);
|
|
211
|
+
constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt);
|
|
212
212
|
branded<B extends IN>(): JsonSchemaEnumBuilder<B | IN, B, Opt>;
|
|
213
213
|
}
|
|
214
|
+
type EnumBaseType = 'string' | 'number' | 'other';
|
|
214
215
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
215
216
|
readonly in?: IN;
|
|
216
217
|
readonly out?: OUT;
|
|
@@ -6,7 +6,7 @@ import { _assert } from '@naturalcycles/js-lib/error';
|
|
|
6
6
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object';
|
|
7
7
|
import { _objectAssign, JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
8
8
|
import { TIMEZONES } from '../timezones.js';
|
|
9
|
-
import { JSON_SCHEMA_ORDER, mergeJsonSchemaObjects } from './jsonSchemaBuilder.util.js';
|
|
9
|
+
import { isEveryItemNumber, isEveryItemString, JSON_SCHEMA_ORDER, mergeJsonSchemaObjects, } from './jsonSchemaBuilder.util.js';
|
|
10
10
|
export const j = {
|
|
11
11
|
string() {
|
|
12
12
|
return new JsonSchemaStringBuilder();
|
|
@@ -35,20 +35,29 @@ export const j = {
|
|
|
35
35
|
},
|
|
36
36
|
enum(input, opt) {
|
|
37
37
|
let enumValues;
|
|
38
|
+
let baseType = 'other';
|
|
38
39
|
if (Array.isArray(input)) {
|
|
39
40
|
enumValues = input;
|
|
41
|
+
if (isEveryItemNumber(input)) {
|
|
42
|
+
baseType = 'number';
|
|
43
|
+
}
|
|
44
|
+
else if (isEveryItemString(input)) {
|
|
45
|
+
baseType = 'string';
|
|
46
|
+
}
|
|
40
47
|
}
|
|
41
48
|
else if (typeof input === 'object') {
|
|
42
49
|
const enumType = getEnumType(input);
|
|
43
50
|
if (enumType === 'NumberEnum') {
|
|
44
51
|
enumValues = _numberEnumValues(input);
|
|
52
|
+
baseType = 'number';
|
|
45
53
|
}
|
|
46
54
|
else if (enumType === 'StringEnum') {
|
|
47
55
|
enumValues = _stringEnumValues(input);
|
|
56
|
+
baseType = 'string';
|
|
48
57
|
}
|
|
49
58
|
}
|
|
50
59
|
_assert(enumValues, 'Unsupported enum input');
|
|
51
|
-
return new JsonSchemaEnumBuilder(enumValues, opt);
|
|
60
|
+
return new JsonSchemaEnumBuilder(enumValues, baseType, opt);
|
|
52
61
|
},
|
|
53
62
|
oneOf(items) {
|
|
54
63
|
const schemas = items.map(b => b.build());
|
|
@@ -592,8 +601,15 @@ export class JsonSchemaBufferBuilder extends JsonSchemaAnyBuilder {
|
|
|
592
601
|
}
|
|
593
602
|
}
|
|
594
603
|
export class JsonSchemaEnumBuilder extends JsonSchemaAnyBuilder {
|
|
595
|
-
constructor(enumValues, opt) {
|
|
596
|
-
|
|
604
|
+
constructor(enumValues, baseType, opt) {
|
|
605
|
+
const jsonSchema = { enum: enumValues };
|
|
606
|
+
// Specifying the base type helps in cases when we ask Ajv to coerce the types.
|
|
607
|
+
// Having only the `enum` in the schema does not trigger a coercion in Ajv.
|
|
608
|
+
if (baseType === 'string')
|
|
609
|
+
jsonSchema.type = 'string';
|
|
610
|
+
if (baseType === 'number')
|
|
611
|
+
jsonSchema.type = 'number';
|
|
612
|
+
super(jsonSchema);
|
|
597
613
|
if (opt?.name)
|
|
598
614
|
this.setErrorMessage('pattern', `is not a valid ${opt.name}`);
|
|
599
615
|
if (opt?.msg)
|
|
@@ -7,3 +7,5 @@ export declare const JSON_SCHEMA_ORDER: string[];
|
|
|
7
7
|
* API similar to Object.assign(s1, s2)
|
|
8
8
|
*/
|
|
9
9
|
export declare function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObject>(schema1: JsonSchema<T1>, schema2: JsonSchema<T2>): JsonSchema<T1 & T2>;
|
|
10
|
+
export declare function isEveryItemString(arr: any[]): boolean;
|
|
11
|
+
export declare function isEveryItemNumber(arr: any[]): boolean;
|
|
@@ -63,3 +63,17 @@ export function mergeJsonSchemaObjects(schema1, schema2) {
|
|
|
63
63
|
// `additionalProperties` remains the same
|
|
64
64
|
return _filterNullishValues(s1, { mutate: true });
|
|
65
65
|
}
|
|
66
|
+
export function isEveryItemString(arr) {
|
|
67
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
68
|
+
if (typeof arr[i] !== 'string')
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
export function isEveryItemNumber(arr) {
|
|
74
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
75
|
+
if (typeof arr[i] !== 'number')
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
}
|
package/package.json
CHANGED
|
@@ -24,6 +24,11 @@ const AJV_NON_MUTATING_OPTIONS: Options = {
|
|
|
24
24
|
useDefaults: false,
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const AJV_MUTATING_COERCING_OPTIONS: Options = {
|
|
28
|
+
...AJV_OPTIONS,
|
|
29
|
+
coerceTypes: true,
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
/**
|
|
28
33
|
* Return cached instance of Ajv with default (recommended) options.
|
|
29
34
|
*
|
|
@@ -40,6 +45,14 @@ export const getAjv = _lazyValue(createAjv)
|
|
|
40
45
|
*/
|
|
41
46
|
export const getNonMutatingAjv = _lazyValue(() => createAjv(AJV_NON_MUTATING_OPTIONS))
|
|
42
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Returns cached instance of Ajv, which is coercing data.
|
|
50
|
+
*
|
|
51
|
+
* To be used in places where we know that we are going to receive data with the wrong type,
|
|
52
|
+
* typically: request path params and request query params.
|
|
53
|
+
*/
|
|
54
|
+
export const getCoercingAjv = _lazyValue(() => createAjv(AJV_MUTATING_COERCING_OPTIONS))
|
|
55
|
+
|
|
43
56
|
/**
|
|
44
57
|
* Create Ajv with modified defaults.
|
|
45
58
|
*
|
|
@@ -27,7 +27,12 @@ import {
|
|
|
27
27
|
type UnixTimestampMillis,
|
|
28
28
|
} from '@naturalcycles/js-lib/types'
|
|
29
29
|
import { TIMEZONES } from '../timezones.js'
|
|
30
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
isEveryItemNumber,
|
|
32
|
+
isEveryItemString,
|
|
33
|
+
JSON_SCHEMA_ORDER,
|
|
34
|
+
mergeJsonSchemaObjects,
|
|
35
|
+
} from './jsonSchemaBuilder.util.js'
|
|
31
36
|
|
|
32
37
|
export const j = {
|
|
33
38
|
string(): JsonSchemaStringBuilder<string, string, false> {
|
|
@@ -79,20 +84,28 @@ export const j = {
|
|
|
79
84
|
: never
|
|
80
85
|
> {
|
|
81
86
|
let enumValues: readonly (string | number | boolean | null)[] | undefined
|
|
87
|
+
let baseType: EnumBaseType = 'other'
|
|
82
88
|
|
|
83
89
|
if (Array.isArray(input)) {
|
|
84
90
|
enumValues = input
|
|
91
|
+
if (isEveryItemNumber(input)) {
|
|
92
|
+
baseType = 'number'
|
|
93
|
+
} else if (isEveryItemString(input)) {
|
|
94
|
+
baseType = 'string'
|
|
95
|
+
}
|
|
85
96
|
} else if (typeof input === 'object') {
|
|
86
97
|
const enumType = getEnumType(input)
|
|
87
98
|
if (enumType === 'NumberEnum') {
|
|
88
99
|
enumValues = _numberEnumValues(input as NumberEnum)
|
|
100
|
+
baseType = 'number'
|
|
89
101
|
} else if (enumType === 'StringEnum') {
|
|
90
102
|
enumValues = _stringEnumValues(input as StringEnum)
|
|
103
|
+
baseType = 'string'
|
|
91
104
|
}
|
|
92
105
|
}
|
|
93
106
|
|
|
94
107
|
_assert(enumValues, 'Unsupported enum input')
|
|
95
|
-
return new JsonSchemaEnumBuilder(enumValues as any, opt)
|
|
108
|
+
return new JsonSchemaEnumBuilder(enumValues as any, baseType, opt)
|
|
96
109
|
},
|
|
97
110
|
|
|
98
111
|
oneOf<
|
|
@@ -851,8 +864,15 @@ export class JsonSchemaEnumBuilder<
|
|
|
851
864
|
OUT extends IN = IN,
|
|
852
865
|
Opt extends boolean = false,
|
|
853
866
|
> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
854
|
-
constructor(enumValues: readonly IN[], opt?: JsonBuilderRuleOpt) {
|
|
855
|
-
|
|
867
|
+
constructor(enumValues: readonly IN[], baseType: EnumBaseType, opt?: JsonBuilderRuleOpt) {
|
|
868
|
+
const jsonSchema: JsonSchema = { enum: enumValues }
|
|
869
|
+
// Specifying the base type helps in cases when we ask Ajv to coerce the types.
|
|
870
|
+
// Having only the `enum` in the schema does not trigger a coercion in Ajv.
|
|
871
|
+
if (baseType === 'string') jsonSchema.type = 'string'
|
|
872
|
+
if (baseType === 'number') jsonSchema.type = 'number'
|
|
873
|
+
|
|
874
|
+
super(jsonSchema)
|
|
875
|
+
|
|
856
876
|
if (opt?.name) this.setErrorMessage('pattern', `is not a valid ${opt.name}`)
|
|
857
877
|
if (opt?.msg) this.setErrorMessage('enum', opt.msg)
|
|
858
878
|
}
|
|
@@ -862,6 +882,8 @@ export class JsonSchemaEnumBuilder<
|
|
|
862
882
|
}
|
|
863
883
|
}
|
|
864
884
|
|
|
885
|
+
type EnumBaseType = 'string' | 'number' | 'other'
|
|
886
|
+
|
|
865
887
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
866
888
|
readonly in?: IN
|
|
867
889
|
readonly out?: OUT
|
|
@@ -76,3 +76,17 @@ export function mergeJsonSchemaObjects<T1 extends AnyObject, T2 extends AnyObjec
|
|
|
76
76
|
|
|
77
77
|
return _filterNullishValues(s1, { mutate: true })
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
export function isEveryItemString(arr: any[]): boolean {
|
|
81
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
82
|
+
if (typeof arr[i] !== 'string') return false
|
|
83
|
+
}
|
|
84
|
+
return true
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function isEveryItemNumber(arr: any[]): boolean {
|
|
88
|
+
for (let i = 0; i <= arr.length; ++i) {
|
|
89
|
+
if (typeof arr[i] !== 'number') return false
|
|
90
|
+
}
|
|
91
|
+
return true
|
|
92
|
+
}
|