@naturalcycles/nodejs-lib 15.57.1 → 15.59.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/validation/ajv/ajvSchema.d.ts +14 -4
- package/dist/validation/ajv/ajvSchema.js +3 -2
- package/dist/validation/ajv/getAjv.js +21 -0
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +38 -1
- package/dist/validation/ajv/jsonSchemaBuilder.js +55 -0
- package/package.json +1 -1
- package/src/validation/ajv/ajvSchema.ts +17 -6
- package/src/validation/ajv/getAjv.ts +26 -0
- package/src/validation/ajv/jsonSchemaBuilder.ts +101 -1
|
@@ -39,9 +39,9 @@ export declare class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
39
39
|
*
|
|
40
40
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
41
41
|
*/
|
|
42
|
-
validate(input: IN, opt?: AjvValidationOptions): OUT;
|
|
43
|
-
isValid(input: IN, opt?: AjvValidationOptions): boolean;
|
|
44
|
-
getValidationResult(input: IN, opt?: AjvValidationOptions): ValidationFunctionResult<OUT, AjvValidationError>;
|
|
42
|
+
validate(input: IN, opt?: AjvValidationOptions<IN>): OUT;
|
|
43
|
+
isValid(input: IN, opt?: AjvValidationOptions<IN>): boolean;
|
|
44
|
+
getValidationResult(input: IN, opt?: AjvValidationOptions<IN>): ValidationFunctionResult<OUT, AjvValidationError>;
|
|
45
45
|
getValidationFunction(): ValidationFunction<IN, OUT, AjvValidationError>;
|
|
46
46
|
static isSchemaWithCachedAjvSchema<Base, IN, OUT>(schema: Base): schema is WithCachedAjvSchema<Base, IN, OUT>;
|
|
47
47
|
static cacheAjvSchema<Base extends AnyObject, IN, OUT>(schema: Base, ajvSchema: AjvSchema<IN, OUT>): WithCachedAjvSchema<Base, IN, OUT>;
|
|
@@ -54,7 +54,7 @@ export declare const HIDDEN_AJV_SCHEMA: unique symbol;
|
|
|
54
54
|
export type WithCachedAjvSchema<Base, IN, OUT> = Base & {
|
|
55
55
|
[HIDDEN_AJV_SCHEMA]: AjvSchema<IN, OUT>;
|
|
56
56
|
};
|
|
57
|
-
export interface AjvValidationOptions {
|
|
57
|
+
export interface AjvValidationOptions<IN> {
|
|
58
58
|
/**
|
|
59
59
|
* Defaults to true,
|
|
60
60
|
* because that's how AJV works by default,
|
|
@@ -73,6 +73,16 @@ export interface AjvValidationOptions {
|
|
|
73
73
|
mutateInput?: boolean;
|
|
74
74
|
inputName?: string;
|
|
75
75
|
inputId?: string;
|
|
76
|
+
/**
|
|
77
|
+
* Function that returns "original input".
|
|
78
|
+
* What is original input?
|
|
79
|
+
* It's an input in its original non-mutated form.
|
|
80
|
+
* Why is it needed?
|
|
81
|
+
* Because we mutates the Input here. And after its been mutated - we no longer
|
|
82
|
+
* can include it "how it was" in an error message. So, for that reason we'll use
|
|
83
|
+
* `getOriginalInput()`, if it's provided.
|
|
84
|
+
*/
|
|
85
|
+
getOriginalInput?: () => IN;
|
|
76
86
|
}
|
|
77
87
|
export interface AjvSchemaCfg {
|
|
78
88
|
/**
|
|
@@ -108,7 +108,7 @@ export class AjvSchema {
|
|
|
108
108
|
const item = opt.mutateInput !== false || typeof input !== 'object'
|
|
109
109
|
? input // mutate
|
|
110
110
|
: _deepCopy(input); // not mutate
|
|
111
|
-
const valid = fn(item); // mutates item
|
|
111
|
+
const valid = fn(item); // mutates item, but not input
|
|
112
112
|
_typeCast(item);
|
|
113
113
|
if (valid)
|
|
114
114
|
return [null, item];
|
|
@@ -122,7 +122,8 @@ export class AjvSchema {
|
|
|
122
122
|
});
|
|
123
123
|
// Note: if we mutated the input already, e.g stripped unknown properties,
|
|
124
124
|
// the error message Input would contain already mutated object print, such as Input: {}
|
|
125
|
-
|
|
125
|
+
// Unless `getOriginalInput` function is provided - then it will be used to preserve the Input pureness.
|
|
126
|
+
const inputStringified = _inspect(opt.getOriginalInput?.() || input, { maxLen: 4000 });
|
|
126
127
|
message = [message, 'Input: ' + inputStringified].join(separator);
|
|
127
128
|
const err = new AjvValidationError(message, _filterNullishValues({
|
|
128
129
|
errors,
|
|
@@ -374,6 +374,27 @@ export function createAjv(opt) {
|
|
|
374
374
|
return true;
|
|
375
375
|
},
|
|
376
376
|
});
|
|
377
|
+
ajv.addKeyword({
|
|
378
|
+
keyword: 'keySchema',
|
|
379
|
+
type: 'object',
|
|
380
|
+
modifying: true,
|
|
381
|
+
errors: false,
|
|
382
|
+
schemaType: 'object',
|
|
383
|
+
compile(innerSchema, _parentSchema, _it) {
|
|
384
|
+
const isValidKeyFn = ajv.compile(innerSchema);
|
|
385
|
+
function validate(data, _ctx) {
|
|
386
|
+
if (typeof data !== 'object' || data === null)
|
|
387
|
+
return true;
|
|
388
|
+
for (const key of Object.keys(data)) {
|
|
389
|
+
if (!isValidKeyFn(key)) {
|
|
390
|
+
delete data[key];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
return validate;
|
|
396
|
+
},
|
|
397
|
+
});
|
|
377
398
|
return ajv;
|
|
378
399
|
}
|
|
379
400
|
const monthLengths = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
@@ -9,7 +9,40 @@ export declare const j: {
|
|
|
9
9
|
infer: typeof objectInfer;
|
|
10
10
|
any(): JsonSchemaObjectBuilder<AnyObject, AnyObject, false>;
|
|
11
11
|
stringMap<S extends JsonSchemaTerminal<any, any, any>>(schema: S): JsonSchemaObjectBuilder<StringMap<SchemaIn<S>>, StringMap<SchemaOut<S>>>;
|
|
12
|
+
/**
|
|
13
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
14
|
+
*
|
|
15
|
+
* For Record<K, V> type of validations.
|
|
16
|
+
* ```ts
|
|
17
|
+
* const schema = j.object
|
|
18
|
+
* .record(
|
|
19
|
+
* j
|
|
20
|
+
* .string()
|
|
21
|
+
* .regex(/^\d{3,4}$/)
|
|
22
|
+
* .branded<B>(),
|
|
23
|
+
* j.number().nullable(),
|
|
24
|
+
* )
|
|
25
|
+
* .isOfType<Record<B, number | null>>()
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
29
|
+
*
|
|
30
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
31
|
+
*
|
|
32
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
33
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
34
|
+
*/
|
|
35
|
+
record: typeof record;
|
|
36
|
+
/**
|
|
37
|
+
* For Record<ENUM, V> type of validations.
|
|
38
|
+
*
|
|
39
|
+
* When the keys of the Record are values from an Enum,
|
|
40
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
41
|
+
*
|
|
42
|
+
*
|
|
43
|
+
*/
|
|
12
44
|
withEnumKeys: typeof withEnumKeys;
|
|
45
|
+
withRegexKeys: typeof withRegexKeys;
|
|
13
46
|
};
|
|
14
47
|
array<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaArrayBuilder<IN, OUT, Opt>;
|
|
15
48
|
set<IN, OUT, Opt>(itemSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>): JsonSchemaSet2Builder<IN, OUT, Opt>;
|
|
@@ -205,6 +238,7 @@ export declare class JsonSchemaObjectBuilder<IN extends AnyObject, OUT extends A
|
|
|
205
238
|
interface JsonSchemaObjectBuilderOpts {
|
|
206
239
|
hasIsOfTypeCheck?: false;
|
|
207
240
|
patternProperties?: StringMap<JsonSchema<any, any>>;
|
|
241
|
+
keySchema?: JsonSchema;
|
|
208
242
|
}
|
|
209
243
|
export declare class JsonSchemaObjectInferringBuilder<PROPS extends Record<string, JsonSchemaAnyBuilder<any, any, any>>, Opt extends boolean = false> extends JsonSchemaAnyBuilder<Expand<{
|
|
210
244
|
[K in keyof PROPS as PROPS[K] extends JsonSchemaAnyBuilder<any, any, infer IsOpt> ? IsOpt extends true ? never : K : never]: PROPS[K] extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
@@ -316,6 +350,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
316
350
|
};
|
|
317
351
|
errorMessages?: StringMap<string>;
|
|
318
352
|
optionalValues?: (string | number | boolean)[];
|
|
353
|
+
keySchema?: JsonSchema;
|
|
319
354
|
}
|
|
320
355
|
declare function object(props: AnyObject): never;
|
|
321
356
|
declare function object<IN extends AnyObject>(props: {
|
|
@@ -338,6 +373,8 @@ declare function objectDbEntity<IN extends BaseDBEntity & AnyObject, EXTRA_KEYS
|
|
|
338
373
|
} : {
|
|
339
374
|
updated: BuilderFor<IN['updated']>;
|
|
340
375
|
})): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
376
|
+
declare function record<KS extends JsonSchemaAnyBuilder<any, any, any>, VS extends JsonSchemaAnyBuilder<any, any, any>, Opt extends boolean = SchemaOpt<VS>>(keySchema: KS, valueSchema: VS): JsonSchemaObjectBuilder<Opt extends true ? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>> : Record<SchemaIn<KS>, SchemaIn<VS>>, Opt extends true ? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>> : Record<SchemaOut<KS>, SchemaOut<VS>>, false>;
|
|
377
|
+
declare function withRegexKeys<S extends JsonSchemaAnyBuilder<any, any, any>, Opt extends boolean = SchemaOpt<S>>(keyRegex: RegExp | string, schema: S): JsonSchemaObjectBuilder<Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>, Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>, false>;
|
|
341
378
|
/**
|
|
342
379
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
343
380
|
*/
|
|
@@ -378,5 +415,5 @@ interface JsonBuilderRuleOpt {
|
|
|
378
415
|
type EnumKeyUnion<T> = T extends readonly (infer U)[] ? U : T extends StringEnum | NumberEnum ? T[keyof T] : never;
|
|
379
416
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never;
|
|
380
417
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never;
|
|
381
|
-
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? Opt : false;
|
|
418
|
+
type SchemaOpt<S> = S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? (Opt extends true ? true : false) : false;
|
|
382
419
|
export {};
|
|
@@ -33,7 +33,40 @@ export const j = {
|
|
|
33
33
|
},
|
|
34
34
|
});
|
|
35
35
|
},
|
|
36
|
+
/**
|
|
37
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
38
|
+
*
|
|
39
|
+
* For Record<K, V> type of validations.
|
|
40
|
+
* ```ts
|
|
41
|
+
* const schema = j.object
|
|
42
|
+
* .record(
|
|
43
|
+
* j
|
|
44
|
+
* .string()
|
|
45
|
+
* .regex(/^\d{3,4}$/)
|
|
46
|
+
* .branded<B>(),
|
|
47
|
+
* j.number().nullable(),
|
|
48
|
+
* )
|
|
49
|
+
* .isOfType<Record<B, number | null>>()
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
53
|
+
*
|
|
54
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
55
|
+
*
|
|
56
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
57
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
58
|
+
*/
|
|
59
|
+
record,
|
|
60
|
+
/**
|
|
61
|
+
* For Record<ENUM, V> type of validations.
|
|
62
|
+
*
|
|
63
|
+
* When the keys of the Record are values from an Enum,
|
|
64
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
65
|
+
*
|
|
66
|
+
*
|
|
67
|
+
*/
|
|
36
68
|
withEnumKeys,
|
|
69
|
+
withRegexKeys,
|
|
37
70
|
}),
|
|
38
71
|
array(itemSchema) {
|
|
39
72
|
return new JsonSchemaArrayBuilder(itemSchema);
|
|
@@ -527,6 +560,7 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
527
560
|
additionalProperties: false,
|
|
528
561
|
hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
|
|
529
562
|
patternProperties: opt?.patternProperties ?? undefined,
|
|
563
|
+
keySchema: opt?.keySchema ?? undefined,
|
|
530
564
|
});
|
|
531
565
|
if (props)
|
|
532
566
|
this.addProperties(props);
|
|
@@ -715,6 +749,27 @@ function objectDbEntity(props) {
|
|
|
715
749
|
...props,
|
|
716
750
|
});
|
|
717
751
|
}
|
|
752
|
+
function record(keySchema, valueSchema) {
|
|
753
|
+
const keyJsonSchema = keySchema.build();
|
|
754
|
+
const valueJsonSchema = valueSchema.build();
|
|
755
|
+
return new JsonSchemaObjectBuilder([], {
|
|
756
|
+
hasIsOfTypeCheck: false,
|
|
757
|
+
keySchema: keyJsonSchema,
|
|
758
|
+
patternProperties: {
|
|
759
|
+
['^.*$']: valueJsonSchema,
|
|
760
|
+
},
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
function withRegexKeys(keyRegex, schema) {
|
|
764
|
+
const pattern = keyRegex instanceof RegExp ? keyRegex.source : keyRegex;
|
|
765
|
+
const jsonSchema = schema.build();
|
|
766
|
+
return new JsonSchemaObjectBuilder([], {
|
|
767
|
+
hasIsOfTypeCheck: false,
|
|
768
|
+
patternProperties: {
|
|
769
|
+
[pattern]: jsonSchema,
|
|
770
|
+
},
|
|
771
|
+
});
|
|
772
|
+
}
|
|
718
773
|
/**
|
|
719
774
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
720
775
|
*/
|
package/package.json
CHANGED
|
@@ -120,13 +120,13 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
120
120
|
*
|
|
121
121
|
* Returned object is always the same object (`===`) that was passed, so it is returned just for convenience.
|
|
122
122
|
*/
|
|
123
|
-
validate(input: IN, opt: AjvValidationOptions = {}): OUT {
|
|
123
|
+
validate(input: IN, opt: AjvValidationOptions<IN> = {}): OUT {
|
|
124
124
|
const [err, output] = this.getValidationResult(input, opt)
|
|
125
125
|
if (err) throw err
|
|
126
126
|
return output
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
isValid(input: IN, opt?: AjvValidationOptions): boolean {
|
|
129
|
+
isValid(input: IN, opt?: AjvValidationOptions<IN>): boolean {
|
|
130
130
|
// todo: we can make it both fast and non-mutating by using Ajv
|
|
131
131
|
// with "removeAdditional" and "useDefaults" disabled.
|
|
132
132
|
const [err] = this.getValidationResult(input, opt)
|
|
@@ -135,7 +135,7 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
135
135
|
|
|
136
136
|
getValidationResult(
|
|
137
137
|
input: IN,
|
|
138
|
-
opt: AjvValidationOptions = {},
|
|
138
|
+
opt: AjvValidationOptions<IN> = {},
|
|
139
139
|
): ValidationFunctionResult<OUT, AjvValidationError> {
|
|
140
140
|
const fn = this.getAJVValidateFunction()
|
|
141
141
|
|
|
@@ -144,7 +144,7 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
144
144
|
? input // mutate
|
|
145
145
|
: _deepCopy(input) // not mutate
|
|
146
146
|
|
|
147
|
-
const valid = fn(item) // mutates item
|
|
147
|
+
const valid = fn(item) // mutates item, but not input
|
|
148
148
|
_typeCast<OUT>(item)
|
|
149
149
|
if (valid) return [null, item]
|
|
150
150
|
|
|
@@ -165,7 +165,8 @@ export class AjvSchema<IN = unknown, OUT = IN> {
|
|
|
165
165
|
|
|
166
166
|
// Note: if we mutated the input already, e.g stripped unknown properties,
|
|
167
167
|
// the error message Input would contain already mutated object print, such as Input: {}
|
|
168
|
-
|
|
168
|
+
// Unless `getOriginalInput` function is provided - then it will be used to preserve the Input pureness.
|
|
169
|
+
const inputStringified = _inspect(opt.getOriginalInput?.() || input, { maxLen: 4000 })
|
|
169
170
|
message = [message, 'Input: ' + inputStringified].join(separator)
|
|
170
171
|
|
|
171
172
|
const err = new AjvValidationError(
|
|
@@ -245,7 +246,7 @@ export type WithCachedAjvSchema<Base, IN, OUT> = Base & {
|
|
|
245
246
|
[HIDDEN_AJV_SCHEMA]: AjvSchema<IN, OUT>
|
|
246
247
|
}
|
|
247
248
|
|
|
248
|
-
export interface AjvValidationOptions {
|
|
249
|
+
export interface AjvValidationOptions<IN> {
|
|
249
250
|
/**
|
|
250
251
|
* Defaults to true,
|
|
251
252
|
* because that's how AJV works by default,
|
|
@@ -264,6 +265,16 @@ export interface AjvValidationOptions {
|
|
|
264
265
|
mutateInput?: boolean
|
|
265
266
|
inputName?: string
|
|
266
267
|
inputId?: string
|
|
268
|
+
/**
|
|
269
|
+
* Function that returns "original input".
|
|
270
|
+
* What is original input?
|
|
271
|
+
* It's an input in its original non-mutated form.
|
|
272
|
+
* Why is it needed?
|
|
273
|
+
* Because we mutates the Input here. And after its been mutated - we no longer
|
|
274
|
+
* can include it "how it was" in an error message. So, for that reason we'll use
|
|
275
|
+
* `getOriginalInput()`, if it's provided.
|
|
276
|
+
*/
|
|
277
|
+
getOriginalInput?: () => IN
|
|
267
278
|
}
|
|
268
279
|
|
|
269
280
|
export interface AjvSchemaCfg {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { _lazyValue } from '@naturalcycles/js-lib'
|
|
2
2
|
import { Set2 } from '@naturalcycles/js-lib/object'
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
|
+
import type { AnyObject } from '@naturalcycles/js-lib/types'
|
|
4
5
|
import { Ajv, type Options, type ValidateFunction } from 'ajv'
|
|
5
6
|
import { validTLDs } from '../tlds.js'
|
|
6
7
|
import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
|
|
@@ -429,6 +430,31 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
429
430
|
},
|
|
430
431
|
})
|
|
431
432
|
|
|
433
|
+
ajv.addKeyword({
|
|
434
|
+
keyword: 'keySchema',
|
|
435
|
+
type: 'object',
|
|
436
|
+
modifying: true,
|
|
437
|
+
errors: false,
|
|
438
|
+
schemaType: 'object',
|
|
439
|
+
compile(innerSchema, _parentSchema, _it) {
|
|
440
|
+
const isValidKeyFn: ValidateFunction = ajv.compile(innerSchema)
|
|
441
|
+
|
|
442
|
+
function validate(data: AnyObject, _ctx: any): boolean {
|
|
443
|
+
if (typeof data !== 'object' || data === null) return true
|
|
444
|
+
|
|
445
|
+
for (const key of Object.keys(data)) {
|
|
446
|
+
if (!isValidKeyFn(key)) {
|
|
447
|
+
delete data[key]
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return validate
|
|
455
|
+
},
|
|
456
|
+
})
|
|
457
|
+
|
|
432
458
|
return ajv
|
|
433
459
|
}
|
|
434
460
|
|
|
@@ -82,7 +82,41 @@ export const j = {
|
|
|
82
82
|
)
|
|
83
83
|
},
|
|
84
84
|
|
|
85
|
+
/**
|
|
86
|
+
* @experimental Look around, maybe you find a rule that is better for your use-case.
|
|
87
|
+
*
|
|
88
|
+
* For Record<K, V> type of validations.
|
|
89
|
+
* ```ts
|
|
90
|
+
* const schema = j.object
|
|
91
|
+
* .record(
|
|
92
|
+
* j
|
|
93
|
+
* .string()
|
|
94
|
+
* .regex(/^\d{3,4}$/)
|
|
95
|
+
* .branded<B>(),
|
|
96
|
+
* j.number().nullable(),
|
|
97
|
+
* )
|
|
98
|
+
* .isOfType<Record<B, number | null>>()
|
|
99
|
+
* ```
|
|
100
|
+
*
|
|
101
|
+
* When the keys of the Record are values from an Enum, prefer `j.object.withEnumKeys`!
|
|
102
|
+
*
|
|
103
|
+
* Non-matching keys will be stripped from the object, i.e. they will not cause an error.
|
|
104
|
+
*
|
|
105
|
+
* Caveat: This rule first validates values of every properties of the object, and only then validates the keys.
|
|
106
|
+
* A consequence of that is that the validation will throw when there is an unexpected property with a value not matching the value schema.
|
|
107
|
+
*/
|
|
108
|
+
record,
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* For Record<ENUM, V> type of validations.
|
|
112
|
+
*
|
|
113
|
+
* When the keys of the Record are values from an Enum,
|
|
114
|
+
* this helper is more performant and behaves in a more conventional manner than `j.object.record` would.
|
|
115
|
+
*
|
|
116
|
+
*
|
|
117
|
+
*/
|
|
85
118
|
withEnumKeys,
|
|
119
|
+
withRegexKeys,
|
|
86
120
|
}),
|
|
87
121
|
|
|
88
122
|
array<IN, OUT, Opt>(
|
|
@@ -749,6 +783,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
749
783
|
additionalProperties: false,
|
|
750
784
|
hasIsOfTypeCheck: opt?.hasIsOfTypeCheck ?? true,
|
|
751
785
|
patternProperties: opt?.patternProperties ?? undefined,
|
|
786
|
+
keySchema: opt?.keySchema ?? undefined,
|
|
752
787
|
})
|
|
753
788
|
|
|
754
789
|
if (props) this.addProperties(props)
|
|
@@ -820,6 +855,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
820
855
|
interface JsonSchemaObjectBuilderOpts {
|
|
821
856
|
hasIsOfTypeCheck?: false
|
|
822
857
|
patternProperties?: StringMap<JsonSchema<any, any>>
|
|
858
|
+
keySchema?: JsonSchema
|
|
823
859
|
}
|
|
824
860
|
|
|
825
861
|
export class JsonSchemaObjectInferringBuilder<
|
|
@@ -1107,6 +1143,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
1107
1143
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
1108
1144
|
errorMessages?: StringMap<string>
|
|
1109
1145
|
optionalValues?: (string | number | boolean)[]
|
|
1146
|
+
keySchema?: JsonSchema
|
|
1110
1147
|
}
|
|
1111
1148
|
|
|
1112
1149
|
function object(props: AnyObject): never
|
|
@@ -1156,6 +1193,68 @@ function objectDbEntity(props: AnyObject): any {
|
|
|
1156
1193
|
})
|
|
1157
1194
|
}
|
|
1158
1195
|
|
|
1196
|
+
function record<
|
|
1197
|
+
KS extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1198
|
+
VS extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1199
|
+
Opt extends boolean = SchemaOpt<VS>,
|
|
1200
|
+
>(
|
|
1201
|
+
keySchema: KS,
|
|
1202
|
+
valueSchema: VS,
|
|
1203
|
+
): JsonSchemaObjectBuilder<
|
|
1204
|
+
Opt extends true
|
|
1205
|
+
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
1206
|
+
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
1207
|
+
Opt extends true
|
|
1208
|
+
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
1209
|
+
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
1210
|
+
false
|
|
1211
|
+
> {
|
|
1212
|
+
const keyJsonSchema = keySchema.build()
|
|
1213
|
+
const valueJsonSchema = valueSchema.build()
|
|
1214
|
+
|
|
1215
|
+
return new JsonSchemaObjectBuilder<
|
|
1216
|
+
Opt extends true
|
|
1217
|
+
? Partial<Record<SchemaIn<KS>, SchemaIn<VS>>>
|
|
1218
|
+
: Record<SchemaIn<KS>, SchemaIn<VS>>,
|
|
1219
|
+
Opt extends true
|
|
1220
|
+
? Partial<Record<SchemaOut<KS>, SchemaOut<VS>>>
|
|
1221
|
+
: Record<SchemaOut<KS>, SchemaOut<VS>>,
|
|
1222
|
+
false
|
|
1223
|
+
>([], {
|
|
1224
|
+
hasIsOfTypeCheck: false,
|
|
1225
|
+
keySchema: keyJsonSchema,
|
|
1226
|
+
patternProperties: {
|
|
1227
|
+
['^.*$']: valueJsonSchema,
|
|
1228
|
+
},
|
|
1229
|
+
})
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
function withRegexKeys<
|
|
1233
|
+
S extends JsonSchemaAnyBuilder<any, any, any>,
|
|
1234
|
+
Opt extends boolean = SchemaOpt<S>,
|
|
1235
|
+
>(
|
|
1236
|
+
keyRegex: RegExp | string,
|
|
1237
|
+
schema: S,
|
|
1238
|
+
): JsonSchemaObjectBuilder<
|
|
1239
|
+
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
1240
|
+
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
1241
|
+
false
|
|
1242
|
+
> {
|
|
1243
|
+
const pattern = keyRegex instanceof RegExp ? keyRegex.source : keyRegex
|
|
1244
|
+
const jsonSchema = schema.build()
|
|
1245
|
+
|
|
1246
|
+
return new JsonSchemaObjectBuilder<
|
|
1247
|
+
Opt extends true ? StringMap<SchemaIn<S>> : StringMap<SchemaIn<S>>,
|
|
1248
|
+
Opt extends true ? StringMap<SchemaOut<S>> : StringMap<SchemaOut<S>>,
|
|
1249
|
+
false
|
|
1250
|
+
>([], {
|
|
1251
|
+
hasIsOfTypeCheck: false,
|
|
1252
|
+
patternProperties: {
|
|
1253
|
+
[pattern]: jsonSchema,
|
|
1254
|
+
},
|
|
1255
|
+
})
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1159
1258
|
/**
|
|
1160
1259
|
* Builds the object schema with the indicated `keys` and uses the `schema` for their validation.
|
|
1161
1260
|
*/
|
|
@@ -1245,4 +1344,5 @@ type EnumKeyUnion<T> =
|
|
|
1245
1344
|
|
|
1246
1345
|
type SchemaIn<S> = S extends JsonSchemaAnyBuilder<infer IN, any, any> ? IN : never
|
|
1247
1346
|
type SchemaOut<S> = S extends JsonSchemaAnyBuilder<any, infer OUT, any> ? OUT : never
|
|
1248
|
-
type SchemaOpt<S> =
|
|
1347
|
+
type SchemaOpt<S> =
|
|
1348
|
+
S extends JsonSchemaAnyBuilder<any, any, infer Opt> ? (Opt extends true ? true : false) : false
|