@naturalcycles/nodejs-lib 15.46.2 → 15.47.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/getAjv.js +59 -9
- package/dist/validation/ajv/jsonSchemaBuilder.d.ts +29 -5
- package/dist/validation/ajv/jsonSchemaBuilder.js +77 -38
- package/package.json +1 -1
- package/src/security/id.util.ts +1 -0
- package/src/validation/ajv/getAjv.ts +71 -10
- package/src/validation/ajv/jsonSchemaBuilder.ts +102 -41
|
@@ -233,17 +233,67 @@ export function createAjv(opt) {
|
|
|
233
233
|
type: 'string',
|
|
234
234
|
modifying: false,
|
|
235
235
|
errors: true,
|
|
236
|
-
schemaType: '
|
|
237
|
-
validate: function validate(
|
|
236
|
+
schemaType: 'object',
|
|
237
|
+
validate: function validate(opt, data, _schema, ctx) {
|
|
238
|
+
const hasOptions = Object.keys(opt).length > 0;
|
|
238
239
|
const isValid = isIsoDateValid(data);
|
|
239
|
-
if (isValid)
|
|
240
|
+
if (!isValid) {
|
|
241
|
+
;
|
|
242
|
+
validate.errors = [
|
|
243
|
+
{
|
|
244
|
+
instancePath: ctx?.instancePath ?? '',
|
|
245
|
+
message: `is an invalid IsoDate`,
|
|
246
|
+
},
|
|
247
|
+
];
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (!hasOptions)
|
|
240
251
|
return true;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
const { before, sameOrBefore, after, sameOrAfter } = opt;
|
|
253
|
+
const errors = [];
|
|
254
|
+
if (before) {
|
|
255
|
+
const isParamValid = isIsoDateValid(before);
|
|
256
|
+
const isRuleValid = isParamValid && before > data;
|
|
257
|
+
if (!isRuleValid) {
|
|
258
|
+
errors.push({
|
|
259
|
+
instancePath: ctx?.instancePath ?? '',
|
|
260
|
+
message: `is not before ${before}`,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (sameOrBefore) {
|
|
265
|
+
const isParamValid = isIsoDateValid(sameOrBefore);
|
|
266
|
+
const isRuleValid = isParamValid && sameOrBefore >= data;
|
|
267
|
+
if (!isRuleValid) {
|
|
268
|
+
errors.push({
|
|
269
|
+
instancePath: ctx?.instancePath ?? '',
|
|
270
|
+
message: `is not the same or before ${sameOrBefore}`,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (after) {
|
|
275
|
+
const isParamValid = isIsoDateValid(after);
|
|
276
|
+
const isRuleValid = isParamValid && after < data;
|
|
277
|
+
if (!isRuleValid) {
|
|
278
|
+
errors.push({
|
|
279
|
+
instancePath: ctx?.instancePath ?? '',
|
|
280
|
+
message: `is not after ${after}`,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (sameOrAfter) {
|
|
285
|
+
const isParamValid = isIsoDateValid(sameOrAfter);
|
|
286
|
+
const isRuleValid = isParamValid && sameOrAfter <= data;
|
|
287
|
+
if (!isRuleValid) {
|
|
288
|
+
errors.push({
|
|
289
|
+
instancePath: ctx?.instancePath ?? '',
|
|
290
|
+
message: `is not the same or after ${sameOrAfter}`,
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (errors.length === 0)
|
|
295
|
+
return true;
|
|
296
|
+
validate.errors = errors;
|
|
247
297
|
return false;
|
|
248
298
|
},
|
|
249
299
|
});
|
|
@@ -75,7 +75,13 @@ export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = I
|
|
|
75
75
|
toUpperCase(): this;
|
|
76
76
|
truncate(toLength: number): this;
|
|
77
77
|
branded<B extends string>(): JsonSchemaStringBuilder<B, B, Opt>;
|
|
78
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
|
|
80
|
+
*
|
|
81
|
+
* All previous expectations in the schema chain are dropped - including `.optional()` -
|
|
82
|
+
* because this call effectively starts a new schema chain.
|
|
83
|
+
*/
|
|
84
|
+
isoDate(): JsonSchemaIsoDateBuilder;
|
|
79
85
|
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt>;
|
|
80
86
|
/**
|
|
81
87
|
* Validates the string format to be JWT.
|
|
@@ -102,6 +108,20 @@ export declare class JsonSchemaStringBuilder<IN extends string = string, OUT = I
|
|
|
102
108
|
export interface JsonSchemaStringEmailOptions {
|
|
103
109
|
checkTLD: boolean;
|
|
104
110
|
}
|
|
111
|
+
export declare class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<string | IsoDate, IsoDate, Opt> {
|
|
112
|
+
constructor();
|
|
113
|
+
before(date: string): this;
|
|
114
|
+
sameOrBefore(date: string): this;
|
|
115
|
+
after(date: string): this;
|
|
116
|
+
sameOrAfter(date: string): this;
|
|
117
|
+
between(fromDate: string, toDate: string, incl: Inclusiveness): this;
|
|
118
|
+
}
|
|
119
|
+
export interface JsonSchemaIsoDateOptions {
|
|
120
|
+
before?: string;
|
|
121
|
+
sameOrBefore?: string;
|
|
122
|
+
after?: string;
|
|
123
|
+
sameOrAfter?: string;
|
|
124
|
+
}
|
|
105
125
|
export declare class JsonSchemaNumberBuilder<IN extends number = number, OUT = IN, Opt extends boolean = false> extends JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
106
126
|
constructor();
|
|
107
127
|
integer(): this;
|
|
@@ -177,7 +197,7 @@ export declare class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyB
|
|
|
177
197
|
maxLength(maxItems: number): this;
|
|
178
198
|
length(minItems: number, maxItems: number): this;
|
|
179
199
|
exactLength(length: number): this;
|
|
180
|
-
unique(
|
|
200
|
+
unique(): this;
|
|
181
201
|
}
|
|
182
202
|
export declare class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<Iterable<IN>, Set2<OUT>, Opt> {
|
|
183
203
|
constructor(itemsSchema: JsonSchemaAnyBuilder<IN, OUT, Opt>);
|
|
@@ -194,7 +214,7 @@ export declare class JsonSchemaEnumBuilder<IN extends string | number | boolean
|
|
|
194
214
|
export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
195
215
|
readonly in?: IN;
|
|
196
216
|
readonly out?: OUT;
|
|
197
|
-
$schema?:
|
|
217
|
+
$schema?: string;
|
|
198
218
|
$id?: string;
|
|
199
219
|
title?: string;
|
|
200
220
|
description?: string;
|
|
@@ -232,10 +252,14 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
232
252
|
exclusiveMinimum?: number;
|
|
233
253
|
maximum?: number;
|
|
234
254
|
exclusiveMaximum?: number;
|
|
255
|
+
minItems?: number;
|
|
256
|
+
maxItems?: number;
|
|
257
|
+
uniqueItems?: boolean;
|
|
235
258
|
enum?: any;
|
|
259
|
+
email?: JsonSchemaStringEmailOptions;
|
|
236
260
|
Set2?: JsonSchema;
|
|
237
261
|
Buffer?: true;
|
|
238
|
-
IsoDate?:
|
|
262
|
+
IsoDate?: JsonSchemaIsoDateOptions;
|
|
239
263
|
IsoDateTime?: true;
|
|
240
264
|
instanceof?: string | string[];
|
|
241
265
|
transform?: {
|
|
@@ -249,7 +273,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
249
273
|
}
|
|
250
274
|
declare function object(props: AnyObject): never;
|
|
251
275
|
declare function object<IN extends AnyObject>(props: {
|
|
252
|
-
[
|
|
276
|
+
[K in keyof Required<IN>]-?: JsonSchemaAnyBuilder<any, IN[K], any>;
|
|
253
277
|
}): JsonSchemaObjectBuilder<IN, IN, false>;
|
|
254
278
|
declare function objectInfer<P extends Record<string, JsonSchemaAnyBuilder<any, any, any>>>(props: P): JsonSchemaObjectInferringBuilder<P, false>;
|
|
255
279
|
declare function objectDbEntity(props: AnyObject): never;
|
|
@@ -4,7 +4,7 @@ import { _isUndefined, _numberEnumValues, _stringEnumValues, getEnumType, } from
|
|
|
4
4
|
import { _uniq } from '@naturalcycles/js-lib/array';
|
|
5
5
|
import { _assert } from '@naturalcycles/js-lib/error';
|
|
6
6
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object';
|
|
7
|
-
import { JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
7
|
+
import { _objectAssign, JWT_REGEX, } from '@naturalcycles/js-lib/types';
|
|
8
8
|
import { TIMEZONES } from '../timezones.js';
|
|
9
9
|
import { JSON_SCHEMA_ORDER, mergeJsonSchemaObjects } from './jsonSchemaBuilder.util.js';
|
|
10
10
|
export const j = {
|
|
@@ -94,14 +94,14 @@ export class JsonSchemaAnyBuilder {
|
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
96
|
isOfType() {
|
|
97
|
-
|
|
97
|
+
_objectAssign(this.schema, { hasIsOfTypeCheck: true });
|
|
98
98
|
return this;
|
|
99
99
|
}
|
|
100
100
|
getSchema() {
|
|
101
101
|
return this.schema;
|
|
102
102
|
}
|
|
103
103
|
$schema($schema) {
|
|
104
|
-
|
|
104
|
+
_objectAssign(this.schema, { $schema });
|
|
105
105
|
return this;
|
|
106
106
|
}
|
|
107
107
|
$schemaDraft7() {
|
|
@@ -109,31 +109,31 @@ export class JsonSchemaAnyBuilder {
|
|
|
109
109
|
return this;
|
|
110
110
|
}
|
|
111
111
|
$id($id) {
|
|
112
|
-
|
|
112
|
+
_objectAssign(this.schema, { $id });
|
|
113
113
|
return this;
|
|
114
114
|
}
|
|
115
115
|
title(title) {
|
|
116
|
-
|
|
116
|
+
_objectAssign(this.schema, { title });
|
|
117
117
|
return this;
|
|
118
118
|
}
|
|
119
119
|
description(description) {
|
|
120
|
-
|
|
120
|
+
_objectAssign(this.schema, { description });
|
|
121
121
|
return this;
|
|
122
122
|
}
|
|
123
123
|
deprecated(deprecated = true) {
|
|
124
|
-
|
|
124
|
+
_objectAssign(this.schema, { deprecated });
|
|
125
125
|
return this;
|
|
126
126
|
}
|
|
127
127
|
type(type) {
|
|
128
|
-
|
|
128
|
+
_objectAssign(this.schema, { type });
|
|
129
129
|
return this;
|
|
130
130
|
}
|
|
131
131
|
default(v) {
|
|
132
|
-
|
|
132
|
+
_objectAssign(this.schema, { default: v });
|
|
133
133
|
return this;
|
|
134
134
|
}
|
|
135
135
|
instanceof(of) {
|
|
136
|
-
|
|
136
|
+
_objectAssign(this.schema, { type: 'object', instanceof: of });
|
|
137
137
|
return this;
|
|
138
138
|
}
|
|
139
139
|
optional() {
|
|
@@ -182,53 +182,59 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
182
182
|
this.setErrorMessage('pattern', `is not a valid ${opt.name}`);
|
|
183
183
|
if (opt?.msg)
|
|
184
184
|
this.setErrorMessage('pattern', opt.msg);
|
|
185
|
-
|
|
185
|
+
_objectAssign(this.schema, { pattern });
|
|
186
186
|
return this;
|
|
187
187
|
}
|
|
188
188
|
minLength(minLength) {
|
|
189
|
-
|
|
189
|
+
_objectAssign(this.schema, { minLength });
|
|
190
190
|
return this;
|
|
191
191
|
}
|
|
192
192
|
maxLength(maxLength) {
|
|
193
|
-
|
|
193
|
+
_objectAssign(this.schema, { maxLength });
|
|
194
194
|
return this;
|
|
195
195
|
}
|
|
196
196
|
length(minLength, maxLength) {
|
|
197
|
-
|
|
197
|
+
_objectAssign(this.schema, { minLength, maxLength });
|
|
198
198
|
return this;
|
|
199
199
|
}
|
|
200
200
|
email(opt) {
|
|
201
201
|
const defaultOptions = { checkTLD: true };
|
|
202
|
-
|
|
202
|
+
_objectAssign(this.schema, { email: { ...defaultOptions, ...opt } });
|
|
203
203
|
// from `ajv-formats`
|
|
204
204
|
const regex = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i;
|
|
205
205
|
return this.regex(regex, { msg: 'is not a valid email address' }).trim().toLowerCase();
|
|
206
206
|
}
|
|
207
207
|
trim() {
|
|
208
|
-
|
|
208
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, trim: true } });
|
|
209
209
|
return this;
|
|
210
210
|
}
|
|
211
211
|
toLowerCase() {
|
|
212
|
-
|
|
212
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } });
|
|
213
213
|
return this;
|
|
214
214
|
}
|
|
215
215
|
toUpperCase() {
|
|
216
|
-
|
|
216
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } });
|
|
217
217
|
return this;
|
|
218
218
|
}
|
|
219
219
|
truncate(toLength) {
|
|
220
|
-
|
|
220
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } });
|
|
221
221
|
return this;
|
|
222
222
|
}
|
|
223
223
|
branded() {
|
|
224
224
|
return this;
|
|
225
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
|
|
228
|
+
*
|
|
229
|
+
* All previous expectations in the schema chain are dropped - including `.optional()` -
|
|
230
|
+
* because this call effectively starts a new schema chain.
|
|
231
|
+
*/
|
|
226
232
|
isoDate() {
|
|
227
|
-
|
|
228
|
-
return
|
|
233
|
+
_objectAssign(this.schema, { IsoDate: {} });
|
|
234
|
+
return new JsonSchemaIsoDateBuilder();
|
|
229
235
|
}
|
|
230
236
|
isoDateTime() {
|
|
231
|
-
|
|
237
|
+
_objectAssign(this.schema, { IsoDateTime: true });
|
|
232
238
|
return this.branded();
|
|
233
239
|
}
|
|
234
240
|
/**
|
|
@@ -289,6 +295,39 @@ export class JsonSchemaStringBuilder extends JsonSchemaAnyBuilder {
|
|
|
289
295
|
return j.enum(TIMEZONES, { msg: 'is an invalid IANA timezone' }).branded();
|
|
290
296
|
}
|
|
291
297
|
}
|
|
298
|
+
export class JsonSchemaIsoDateBuilder extends JsonSchemaAnyBuilder {
|
|
299
|
+
constructor() {
|
|
300
|
+
super({
|
|
301
|
+
type: 'string',
|
|
302
|
+
IsoDate: {},
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
before(date) {
|
|
306
|
+
_objectAssign(this.schema.IsoDate, { before: date });
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
sameOrBefore(date) {
|
|
310
|
+
_objectAssign(this.schema.IsoDate, { sameOrBefore: date });
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
after(date) {
|
|
314
|
+
_objectAssign(this.schema.IsoDate, { after: date });
|
|
315
|
+
return this;
|
|
316
|
+
}
|
|
317
|
+
sameOrAfter(date) {
|
|
318
|
+
_objectAssign(this.schema.IsoDate, { sameOrAfter: date });
|
|
319
|
+
return this;
|
|
320
|
+
}
|
|
321
|
+
between(fromDate, toDate, incl) {
|
|
322
|
+
if (incl === '[)') {
|
|
323
|
+
_objectAssign(this.schema.IsoDate, { sameOrAfter: fromDate, before: toDate });
|
|
324
|
+
}
|
|
325
|
+
else if (incl === '[]') {
|
|
326
|
+
_objectAssign(this.schema.IsoDate, { sameOrAfter: fromDate, sameOrBefore: toDate });
|
|
327
|
+
}
|
|
328
|
+
return this;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
292
331
|
export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
293
332
|
constructor() {
|
|
294
333
|
super({
|
|
@@ -296,30 +335,30 @@ export class JsonSchemaNumberBuilder extends JsonSchemaAnyBuilder {
|
|
|
296
335
|
});
|
|
297
336
|
}
|
|
298
337
|
integer() {
|
|
299
|
-
|
|
338
|
+
_objectAssign(this.schema, { type: 'integer' });
|
|
300
339
|
return this;
|
|
301
340
|
}
|
|
302
341
|
branded() {
|
|
303
342
|
return this;
|
|
304
343
|
}
|
|
305
344
|
multipleOf(multipleOf) {
|
|
306
|
-
|
|
345
|
+
_objectAssign(this.schema, { multipleOf });
|
|
307
346
|
return this;
|
|
308
347
|
}
|
|
309
348
|
min(minimum) {
|
|
310
|
-
|
|
349
|
+
_objectAssign(this.schema, { minimum });
|
|
311
350
|
return this;
|
|
312
351
|
}
|
|
313
352
|
exclusiveMin(exclusiveMinimum) {
|
|
314
|
-
|
|
353
|
+
_objectAssign(this.schema, { exclusiveMinimum });
|
|
315
354
|
return this;
|
|
316
355
|
}
|
|
317
356
|
max(maximum) {
|
|
318
|
-
|
|
357
|
+
_objectAssign(this.schema, { maximum });
|
|
319
358
|
return this;
|
|
320
359
|
}
|
|
321
360
|
exclusiveMax(exclusiveMaximum) {
|
|
322
|
-
|
|
361
|
+
_objectAssign(this.schema, { exclusiveMaximum });
|
|
323
362
|
return this;
|
|
324
363
|
}
|
|
325
364
|
lessThan(value) {
|
|
@@ -427,12 +466,12 @@ export class JsonSchemaObjectBuilder extends JsonSchemaAnyBuilder {
|
|
|
427
466
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
428
467
|
*/
|
|
429
468
|
allowAdditionalProperties() {
|
|
430
|
-
|
|
469
|
+
_objectAssign(this.schema, { additionalProperties: true });
|
|
431
470
|
return this;
|
|
432
471
|
}
|
|
433
472
|
extend(props) {
|
|
434
473
|
const newBuilder = new JsonSchemaObjectBuilder();
|
|
435
|
-
|
|
474
|
+
_objectAssign(newBuilder.schema, _deepCopy(this.schema));
|
|
436
475
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder(props);
|
|
437
476
|
mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
|
|
438
477
|
return newBuilder;
|
|
@@ -481,12 +520,12 @@ export class JsonSchemaObjectInferringBuilder extends JsonSchemaAnyBuilder {
|
|
|
481
520
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
482
521
|
*/
|
|
483
522
|
allowAdditionalProperties() {
|
|
484
|
-
|
|
523
|
+
_objectAssign(this.schema, { additionalProperties: true });
|
|
485
524
|
return this;
|
|
486
525
|
}
|
|
487
526
|
extend(props) {
|
|
488
527
|
const newBuilder = new JsonSchemaObjectInferringBuilder();
|
|
489
|
-
|
|
528
|
+
_objectAssign(newBuilder.schema, _deepCopy(this.schema));
|
|
490
529
|
const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder(props);
|
|
491
530
|
mergeJsonSchemaObjects(newBuilder.schema, incomingSchemaBuilder.schema);
|
|
492
531
|
return newBuilder;
|
|
@@ -511,11 +550,11 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
|
|
|
511
550
|
});
|
|
512
551
|
}
|
|
513
552
|
minLength(minItems) {
|
|
514
|
-
|
|
553
|
+
_objectAssign(this.schema, { minItems });
|
|
515
554
|
return this;
|
|
516
555
|
}
|
|
517
556
|
maxLength(maxItems) {
|
|
518
|
-
|
|
557
|
+
_objectAssign(this.schema, { maxItems });
|
|
519
558
|
return this;
|
|
520
559
|
}
|
|
521
560
|
length(minItems, maxItems) {
|
|
@@ -524,8 +563,8 @@ export class JsonSchemaArrayBuilder extends JsonSchemaAnyBuilder {
|
|
|
524
563
|
exactLength(length) {
|
|
525
564
|
return this.minLength(length).maxLength(length);
|
|
526
565
|
}
|
|
527
|
-
unique(
|
|
528
|
-
|
|
566
|
+
unique() {
|
|
567
|
+
_objectAssign(this.schema, { uniqueItems: true });
|
|
529
568
|
return this;
|
|
530
569
|
}
|
|
531
570
|
}
|
|
@@ -537,11 +576,11 @@ export class JsonSchemaSet2Builder extends JsonSchemaAnyBuilder {
|
|
|
537
576
|
});
|
|
538
577
|
}
|
|
539
578
|
min(minItems) {
|
|
540
|
-
|
|
579
|
+
_objectAssign(this.schema, { minItems });
|
|
541
580
|
return this;
|
|
542
581
|
}
|
|
543
582
|
max(maxItems) {
|
|
544
|
-
|
|
583
|
+
_objectAssign(this.schema, { maxItems });
|
|
545
584
|
return this;
|
|
546
585
|
}
|
|
547
586
|
}
|
package/package.json
CHANGED
package/src/security/id.util.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { Set2 } from '@naturalcycles/js-lib/object'
|
|
|
3
3
|
import { _substringAfterLast } from '@naturalcycles/js-lib/string'
|
|
4
4
|
import { _, Ajv, type Options, type ValidateFunction } from 'ajv'
|
|
5
5
|
import { validTLDs } from '../tlds.js'
|
|
6
|
-
import type { JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
|
|
6
|
+
import type { JsonSchemaIsoDateOptions, JsonSchemaStringEmailOptions } from './jsonSchemaBuilder.js'
|
|
7
7
|
|
|
8
8
|
/* eslint-disable @typescript-eslint/prefer-string-starts-ends-with */
|
|
9
9
|
// oxlint-disable unicorn/prefer-code-point
|
|
@@ -255,16 +255,77 @@ export function createAjv(opt?: Options): Ajv {
|
|
|
255
255
|
type: 'string',
|
|
256
256
|
modifying: false,
|
|
257
257
|
errors: true,
|
|
258
|
-
schemaType: '
|
|
259
|
-
validate: function validate(
|
|
258
|
+
schemaType: 'object',
|
|
259
|
+
validate: function validate(opt: JsonSchemaIsoDateOptions, data: string, _schema, ctx) {
|
|
260
|
+
const hasOptions = Object.keys(opt).length > 0
|
|
261
|
+
|
|
260
262
|
const isValid = isIsoDateValid(data)
|
|
261
|
-
if (isValid)
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
263
|
+
if (!isValid) {
|
|
264
|
+
;(validate as any).errors = [
|
|
265
|
+
{
|
|
266
|
+
instancePath: ctx?.instancePath ?? '',
|
|
267
|
+
message: `is an invalid IsoDate`,
|
|
268
|
+
},
|
|
269
|
+
]
|
|
270
|
+
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (!hasOptions) return true
|
|
275
|
+
|
|
276
|
+
const { before, sameOrBefore, after, sameOrAfter } = opt
|
|
277
|
+
const errors: any[] = []
|
|
278
|
+
|
|
279
|
+
if (before) {
|
|
280
|
+
const isParamValid = isIsoDateValid(before)
|
|
281
|
+
const isRuleValid = isParamValid && before > data
|
|
282
|
+
|
|
283
|
+
if (!isRuleValid) {
|
|
284
|
+
errors.push({
|
|
285
|
+
instancePath: ctx?.instancePath ?? '',
|
|
286
|
+
message: `is not before ${before}`,
|
|
287
|
+
})
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (sameOrBefore) {
|
|
292
|
+
const isParamValid = isIsoDateValid(sameOrBefore)
|
|
293
|
+
const isRuleValid = isParamValid && sameOrBefore >= data
|
|
294
|
+
|
|
295
|
+
if (!isRuleValid) {
|
|
296
|
+
errors.push({
|
|
297
|
+
instancePath: ctx?.instancePath ?? '',
|
|
298
|
+
message: `is not the same or before ${sameOrBefore}`,
|
|
299
|
+
})
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (after) {
|
|
304
|
+
const isParamValid = isIsoDateValid(after)
|
|
305
|
+
const isRuleValid = isParamValid && after < data
|
|
306
|
+
|
|
307
|
+
if (!isRuleValid) {
|
|
308
|
+
errors.push({
|
|
309
|
+
instancePath: ctx?.instancePath ?? '',
|
|
310
|
+
message: `is not after ${after}`,
|
|
311
|
+
})
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (sameOrAfter) {
|
|
316
|
+
const isParamValid = isIsoDateValid(sameOrAfter)
|
|
317
|
+
const isRuleValid = isParamValid && sameOrAfter <= data
|
|
318
|
+
|
|
319
|
+
if (!isRuleValid) {
|
|
320
|
+
errors.push({
|
|
321
|
+
instancePath: ctx?.instancePath ?? '',
|
|
322
|
+
message: `is not the same or after ${sameOrAfter}`,
|
|
323
|
+
})
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (errors.length === 0) return true
|
|
328
|
+
;(validate as any).errors = errors
|
|
268
329
|
return false
|
|
269
330
|
},
|
|
270
331
|
})
|
|
@@ -12,6 +12,7 @@ import { _assert } from '@naturalcycles/js-lib/error'
|
|
|
12
12
|
import type { Set2 } from '@naturalcycles/js-lib/object'
|
|
13
13
|
import { _deepCopy, _sortObject } from '@naturalcycles/js-lib/object'
|
|
14
14
|
import {
|
|
15
|
+
_objectAssign,
|
|
15
16
|
type AnyObject,
|
|
16
17
|
type BaseDBEntity,
|
|
17
18
|
type IANATimezone,
|
|
@@ -144,7 +145,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
|
144
145
|
* ```
|
|
145
146
|
*/
|
|
146
147
|
isOfType<ExpectedType>(): ExactMatch<ExpectedType, OUT> extends true ? this : never {
|
|
147
|
-
|
|
148
|
+
_objectAssign(this.schema, { hasIsOfTypeCheck: true })
|
|
148
149
|
return this as any
|
|
149
150
|
}
|
|
150
151
|
|
|
@@ -153,7 +154,7 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
$schema($schema: string): this {
|
|
156
|
-
|
|
157
|
+
_objectAssign(this.schema, { $schema })
|
|
157
158
|
return this
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -163,37 +164,37 @@ export class JsonSchemaAnyBuilder<IN, OUT, Opt> {
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
$id($id: string): this {
|
|
166
|
-
|
|
167
|
+
_objectAssign(this.schema, { $id })
|
|
167
168
|
return this
|
|
168
169
|
}
|
|
169
170
|
|
|
170
171
|
title(title: string): this {
|
|
171
|
-
|
|
172
|
+
_objectAssign(this.schema, { title })
|
|
172
173
|
return this
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
description(description: string): this {
|
|
176
|
-
|
|
177
|
+
_objectAssign(this.schema, { description })
|
|
177
178
|
return this
|
|
178
179
|
}
|
|
179
180
|
|
|
180
181
|
deprecated(deprecated = true): this {
|
|
181
|
-
|
|
182
|
+
_objectAssign(this.schema, { deprecated })
|
|
182
183
|
return this
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
type(type: string): this {
|
|
186
|
-
|
|
187
|
+
_objectAssign(this.schema, { type })
|
|
187
188
|
return this
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
default(v: any): this {
|
|
191
|
-
|
|
192
|
+
_objectAssign(this.schema, { default: v })
|
|
192
193
|
return this
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
instanceof(of: string): this {
|
|
196
|
-
|
|
197
|
+
_objectAssign(this.schema, { type: 'object', instanceof: of })
|
|
197
198
|
return this
|
|
198
199
|
}
|
|
199
200
|
|
|
@@ -253,28 +254,28 @@ export class JsonSchemaStringBuilder<
|
|
|
253
254
|
pattern(pattern: string, opt?: JsonBuilderRuleOpt): this {
|
|
254
255
|
if (opt?.name) this.setErrorMessage('pattern', `is not a valid ${opt.name}`)
|
|
255
256
|
if (opt?.msg) this.setErrorMessage('pattern', opt.msg)
|
|
256
|
-
|
|
257
|
+
_objectAssign(this.schema, { pattern })
|
|
257
258
|
return this
|
|
258
259
|
}
|
|
259
260
|
|
|
260
261
|
minLength(minLength: number): this {
|
|
261
|
-
|
|
262
|
+
_objectAssign(this.schema, { minLength })
|
|
262
263
|
return this
|
|
263
264
|
}
|
|
264
265
|
|
|
265
266
|
maxLength(maxLength: number): this {
|
|
266
|
-
|
|
267
|
+
_objectAssign(this.schema, { maxLength })
|
|
267
268
|
return this
|
|
268
269
|
}
|
|
269
270
|
|
|
270
271
|
length(minLength: number, maxLength: number): this {
|
|
271
|
-
|
|
272
|
+
_objectAssign(this.schema, { minLength, maxLength })
|
|
272
273
|
return this
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
email(opt?: Partial<JsonSchemaStringEmailOptions>): this {
|
|
276
277
|
const defaultOptions: JsonSchemaStringEmailOptions = { checkTLD: true }
|
|
277
|
-
|
|
278
|
+
_objectAssign(this.schema, { email: { ...defaultOptions, ...opt } })
|
|
278
279
|
|
|
279
280
|
// from `ajv-formats`
|
|
280
281
|
const regex =
|
|
@@ -283,22 +284,22 @@ export class JsonSchemaStringBuilder<
|
|
|
283
284
|
}
|
|
284
285
|
|
|
285
286
|
trim(): this {
|
|
286
|
-
|
|
287
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, trim: true } })
|
|
287
288
|
return this
|
|
288
289
|
}
|
|
289
290
|
|
|
290
291
|
toLowerCase(): this {
|
|
291
|
-
|
|
292
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, toLowerCase: true } })
|
|
292
293
|
return this
|
|
293
294
|
}
|
|
294
295
|
|
|
295
296
|
toUpperCase(): this {
|
|
296
|
-
|
|
297
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, toUpperCase: true } })
|
|
297
298
|
return this
|
|
298
299
|
}
|
|
299
300
|
|
|
300
301
|
truncate(toLength: number): this {
|
|
301
|
-
|
|
302
|
+
_objectAssign(this.schema, { transform: { ...this.schema.transform, truncate: toLength } })
|
|
302
303
|
return this
|
|
303
304
|
}
|
|
304
305
|
|
|
@@ -306,13 +307,19 @@ export class JsonSchemaStringBuilder<
|
|
|
306
307
|
return this as unknown as JsonSchemaStringBuilder<B, B, Opt>
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
310
|
+
/**
|
|
311
|
+
* Validates that the input is a fully-specified YYYY-MM-DD formatted valid IsoDate value.
|
|
312
|
+
*
|
|
313
|
+
* All previous expectations in the schema chain are dropped - including `.optional()` -
|
|
314
|
+
* because this call effectively starts a new schema chain.
|
|
315
|
+
*/
|
|
316
|
+
isoDate(): JsonSchemaIsoDateBuilder {
|
|
317
|
+
_objectAssign(this.schema, { IsoDate: {} })
|
|
318
|
+
return new JsonSchemaIsoDateBuilder()
|
|
312
319
|
}
|
|
313
320
|
|
|
314
321
|
isoDateTime(): JsonSchemaStringBuilder<IsoDateTime | IN, IsoDateTime, Opt> {
|
|
315
|
-
|
|
322
|
+
_objectAssign(this.schema, { IsoDateTime: true })
|
|
316
323
|
return this.branded<IsoDateTime>()
|
|
317
324
|
}
|
|
318
325
|
|
|
@@ -392,6 +399,56 @@ export interface JsonSchemaStringEmailOptions {
|
|
|
392
399
|
checkTLD: boolean
|
|
393
400
|
}
|
|
394
401
|
|
|
402
|
+
export class JsonSchemaIsoDateBuilder<Opt extends boolean = false> extends JsonSchemaAnyBuilder<
|
|
403
|
+
string | IsoDate,
|
|
404
|
+
IsoDate,
|
|
405
|
+
Opt
|
|
406
|
+
> {
|
|
407
|
+
constructor() {
|
|
408
|
+
super({
|
|
409
|
+
type: 'string',
|
|
410
|
+
IsoDate: {},
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
before(date: string): this {
|
|
415
|
+
_objectAssign(this.schema.IsoDate!, { before: date })
|
|
416
|
+
return this
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
sameOrBefore(date: string): this {
|
|
420
|
+
_objectAssign(this.schema.IsoDate!, { sameOrBefore: date })
|
|
421
|
+
return this
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
after(date: string): this {
|
|
425
|
+
_objectAssign(this.schema.IsoDate!, { after: date })
|
|
426
|
+
return this
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
sameOrAfter(date: string): this {
|
|
430
|
+
_objectAssign(this.schema.IsoDate!, { sameOrAfter: date })
|
|
431
|
+
return this
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
between(fromDate: string, toDate: string, incl: Inclusiveness): this {
|
|
435
|
+
if (incl === '[)') {
|
|
436
|
+
_objectAssign(this.schema.IsoDate!, { sameOrAfter: fromDate, before: toDate })
|
|
437
|
+
} else if (incl === '[]') {
|
|
438
|
+
_objectAssign(this.schema.IsoDate!, { sameOrAfter: fromDate, sameOrBefore: toDate })
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
return this
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export interface JsonSchemaIsoDateOptions {
|
|
446
|
+
before?: string
|
|
447
|
+
sameOrBefore?: string
|
|
448
|
+
after?: string
|
|
449
|
+
sameOrAfter?: string
|
|
450
|
+
}
|
|
451
|
+
|
|
395
452
|
export class JsonSchemaNumberBuilder<
|
|
396
453
|
IN extends number = number,
|
|
397
454
|
OUT = IN,
|
|
@@ -404,7 +461,7 @@ export class JsonSchemaNumberBuilder<
|
|
|
404
461
|
}
|
|
405
462
|
|
|
406
463
|
integer(): this {
|
|
407
|
-
|
|
464
|
+
_objectAssign(this.schema, { type: 'integer' })
|
|
408
465
|
return this
|
|
409
466
|
}
|
|
410
467
|
|
|
@@ -413,27 +470,27 @@ export class JsonSchemaNumberBuilder<
|
|
|
413
470
|
}
|
|
414
471
|
|
|
415
472
|
multipleOf(multipleOf: number): this {
|
|
416
|
-
|
|
473
|
+
_objectAssign(this.schema, { multipleOf })
|
|
417
474
|
return this
|
|
418
475
|
}
|
|
419
476
|
|
|
420
477
|
min(minimum: number): this {
|
|
421
|
-
|
|
478
|
+
_objectAssign(this.schema, { minimum })
|
|
422
479
|
return this
|
|
423
480
|
}
|
|
424
481
|
|
|
425
482
|
exclusiveMin(exclusiveMinimum: number): this {
|
|
426
|
-
|
|
483
|
+
_objectAssign(this.schema, { exclusiveMinimum })
|
|
427
484
|
return this
|
|
428
485
|
}
|
|
429
486
|
|
|
430
487
|
max(maximum: number): this {
|
|
431
|
-
|
|
488
|
+
_objectAssign(this.schema, { maximum })
|
|
432
489
|
return this
|
|
433
490
|
}
|
|
434
491
|
|
|
435
492
|
exclusiveMax(exclusiveMaximum: number): this {
|
|
436
|
-
|
|
493
|
+
_objectAssign(this.schema, { exclusiveMaximum })
|
|
437
494
|
return this
|
|
438
495
|
}
|
|
439
496
|
|
|
@@ -575,7 +632,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
575
632
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
576
633
|
*/
|
|
577
634
|
allowAdditionalProperties(): this {
|
|
578
|
-
|
|
635
|
+
_objectAssign(this.schema, { additionalProperties: true })
|
|
579
636
|
return this
|
|
580
637
|
}
|
|
581
638
|
|
|
@@ -583,7 +640,7 @@ export class JsonSchemaObjectBuilder<
|
|
|
583
640
|
props: AnyObject,
|
|
584
641
|
): JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt> {
|
|
585
642
|
const newBuilder = new JsonSchemaObjectBuilder<IN & IN2, OUT & IN2, Opt>()
|
|
586
|
-
|
|
643
|
+
_objectAssign(newBuilder.schema, _deepCopy(this.schema))
|
|
587
644
|
|
|
588
645
|
const incomingSchemaBuilder = new JsonSchemaObjectBuilder<IN2, IN2, false>(props)
|
|
589
646
|
mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
|
|
@@ -675,7 +732,7 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
675
732
|
* When set, the validation will not strip away properties that are not specified explicitly in the schema.
|
|
676
733
|
*/
|
|
677
734
|
allowAdditionalProperties(): this {
|
|
678
|
-
|
|
735
|
+
_objectAssign(this.schema, { additionalProperties: true })
|
|
679
736
|
return this
|
|
680
737
|
}
|
|
681
738
|
|
|
@@ -692,7 +749,7 @@ export class JsonSchemaObjectInferringBuilder<
|
|
|
692
749
|
Opt
|
|
693
750
|
> {
|
|
694
751
|
const newBuilder = new JsonSchemaObjectInferringBuilder<PROPS, Opt>()
|
|
695
|
-
|
|
752
|
+
_objectAssign(newBuilder.schema, _deepCopy(this.schema))
|
|
696
753
|
|
|
697
754
|
const incomingSchemaBuilder = new JsonSchemaObjectInferringBuilder<NEW_PROPS, false>(props)
|
|
698
755
|
mergeJsonSchemaObjects(newBuilder.schema as any, incomingSchemaBuilder.schema as any)
|
|
@@ -731,12 +788,12 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
731
788
|
}
|
|
732
789
|
|
|
733
790
|
minLength(minItems: number): this {
|
|
734
|
-
|
|
791
|
+
_objectAssign(this.schema, { minItems })
|
|
735
792
|
return this
|
|
736
793
|
}
|
|
737
794
|
|
|
738
795
|
maxLength(maxItems: number): this {
|
|
739
|
-
|
|
796
|
+
_objectAssign(this.schema, { maxItems })
|
|
740
797
|
return this
|
|
741
798
|
}
|
|
742
799
|
|
|
@@ -748,8 +805,8 @@ export class JsonSchemaArrayBuilder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<I
|
|
|
748
805
|
return this.minLength(length).maxLength(length)
|
|
749
806
|
}
|
|
750
807
|
|
|
751
|
-
unique(
|
|
752
|
-
|
|
808
|
+
unique(): this {
|
|
809
|
+
_objectAssign(this.schema, { uniqueItems: true })
|
|
753
810
|
return this
|
|
754
811
|
}
|
|
755
812
|
}
|
|
@@ -767,12 +824,12 @@ export class JsonSchemaSet2Builder<IN, OUT, Opt> extends JsonSchemaAnyBuilder<
|
|
|
767
824
|
}
|
|
768
825
|
|
|
769
826
|
min(minItems: number): this {
|
|
770
|
-
|
|
827
|
+
_objectAssign(this.schema, { minItems })
|
|
771
828
|
return this
|
|
772
829
|
}
|
|
773
830
|
|
|
774
831
|
max(maxItems: number): this {
|
|
775
|
-
|
|
832
|
+
_objectAssign(this.schema, { maxItems })
|
|
776
833
|
return this
|
|
777
834
|
}
|
|
778
835
|
}
|
|
@@ -809,7 +866,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
809
866
|
readonly in?: IN
|
|
810
867
|
readonly out?: OUT
|
|
811
868
|
|
|
812
|
-
$schema?:
|
|
869
|
+
$schema?: string
|
|
813
870
|
$id?: string
|
|
814
871
|
title?: string
|
|
815
872
|
description?: string
|
|
@@ -857,14 +914,18 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
857
914
|
exclusiveMinimum?: number
|
|
858
915
|
maximum?: number
|
|
859
916
|
exclusiveMaximum?: number
|
|
917
|
+
minItems?: number
|
|
918
|
+
maxItems?: number
|
|
919
|
+
uniqueItems?: boolean
|
|
860
920
|
|
|
861
921
|
enum?: any
|
|
862
922
|
|
|
863
923
|
// Below we add custom Ajv keywords
|
|
864
924
|
|
|
925
|
+
email?: JsonSchemaStringEmailOptions
|
|
865
926
|
Set2?: JsonSchema
|
|
866
927
|
Buffer?: true
|
|
867
|
-
IsoDate?:
|
|
928
|
+
IsoDate?: JsonSchemaIsoDateOptions
|
|
868
929
|
IsoDateTime?: true
|
|
869
930
|
instanceof?: string | string[]
|
|
870
931
|
transform?: { trim?: true; toLowerCase?: true; toUpperCase?: true; truncate?: number }
|
|
@@ -874,7 +935,7 @@ export interface JsonSchema<IN = unknown, OUT = IN> {
|
|
|
874
935
|
|
|
875
936
|
function object(props: AnyObject): never
|
|
876
937
|
function object<IN extends AnyObject>(props: {
|
|
877
|
-
[
|
|
938
|
+
[K in keyof Required<IN>]-?: JsonSchemaAnyBuilder<any, IN[K], any>
|
|
878
939
|
}): JsonSchemaObjectBuilder<IN, IN, false>
|
|
879
940
|
|
|
880
941
|
function object<IN extends AnyObject>(props: {
|