@cleverbrush/schema 0.0.13 → 0.0.16

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/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
- export declare type DefaultSchemaType = 'object' | 'function' | 'number' | 'string' | 'array' | 'alias';
3
+ export declare type DefaultSchemaType = 'object' | 'boolean' | 'function' | 'number' | 'string' | 'array' | 'alias';
3
4
  export declare type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];
4
5
  export declare type DefaultPropertyDefinition = {
5
6
  isRequired: boolean;
@@ -24,7 +25,7 @@ export declare type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'>
24
25
  [S in keyof T]: Schema<PropType<T, S>>;
25
26
  }>;
26
27
  preprocessors?: Partial<{
27
- [S in keyof T | '*']: S extends keyof T ? ((value: unknown) => PropType<T, S> | Promise<PropType<T, S>>) | string : (value: T) => void | Promise<void>;
28
+ [S in keyof T | '*']: S extends keyof T ? ((value: unknown) => undefined | PropType<T, S> | Promise<PropType<T, S>>) | string : (value: T) => void | Promise<void>;
28
29
  }>;
29
30
  };
30
31
  export declare type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
@@ -39,6 +40,10 @@ export declare type FunctionSchemaDefinition<T extends (...args: any) => any> =
39
40
  type: 'function';
40
41
  params?: ParamsValidators<T>;
41
42
  };
43
+ export declare type BooleanSchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 'type'> & {
44
+ type: 'boolean';
45
+ equals?: boolean;
46
+ };
42
47
  export declare type NumberSchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 'type'> & {
43
48
  type: 'number';
44
49
  min?: number;
@@ -61,25 +66,29 @@ export declare type ArraySchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 't
61
66
  minLength?: number;
62
67
  maxLength?: number;
63
68
  };
64
- export declare type CompositeSchema<TObj> = TObj extends (...args: any) => any ? FunctionSchemaDefinition<TObj> : TObj extends number ? NumberSchemaDefinition<TObj> : TObj extends string ? StringSchemaDefinition<TObj> | 'string' : NumberSchemaDefinition<TObj> | StringSchemaDefinition<TObj> | ArraySchemaDefinition<TObj> | ObjectSchemaDefinition<TObj> | AliasSchemaDefinition<TObj>;
69
+ export declare type CompositeSchema<TObj> = TObj extends (...args: any) => any ? FunctionSchemaDefinition<TObj> : TObj extends number ? NumberSchemaDefinition<TObj> : TObj extends string ? StringSchemaDefinition<TObj> | 'string' : BooleanSchemaDefinition<TObj> | NumberSchemaDefinition<TObj> | StringSchemaDefinition<TObj> | ArraySchemaDefinition<TObj> | ObjectSchemaDefinition<TObj> | AliasSchemaDefinition<TObj>;
65
70
  export declare type SingleSchema<TObj = Record<string, never>> = number | string | DefaultSchemaType | CompositeSchema<TObj>;
66
71
  export declare type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
67
- export interface ISchemaActions<K, T extends keyof K> {
72
+ export declare type Cons<H, T extends unknown[] = []> = T['length'] extends 0 ? [H] : ((h: H, ...t: T) => void) extends (...r: infer R) => void ? R : never;
73
+ export interface ISchemaActions<S> {
68
74
  validate(value: any): Promise<ValidationResult>;
69
- schema: PropType<K, T>;
75
+ schema: S;
70
76
  }
71
- export interface ISchemasProvider<T = Record<string, never>> {
72
- schemas: {
73
- [K in keyof T]: ISchemaActions<T, K>;
74
- };
77
+ export interface ISchemasProvider<T extends unknown[]> {
78
+ schemas: Merge<T>;
75
79
  }
76
- export interface ISchemaValidator<T = Record<string, never>> {
80
+ export interface ISchemaValidator<T extends Record<string, never> = Record<string, never>, SchemaTypesStructures extends unknown[] = []> {
77
81
  get preprocessors(): Map<string, (value: unknown) => unknown | Promise<unknown>>;
78
- addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
79
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
82
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T, SchemaTypesStructures>;
83
+ addSchemaType<K, L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
80
84
  [key in keyof K]: typeof schema;
81
- }>;
85
+ }, Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>>;
82
86
  validate(schema: keyof T | DefaultSchemaType | Schema<any>, obj: any): Promise<ValidationResult>;
83
87
  }
88
+ export declare type Unfold<T, K> = T extends string ? T extends `${infer F}.${infer L}` ? {
89
+ [k in F]: Unfold<L, K>;
90
+ } : {
91
+ [k in T]: K;
92
+ } : never;
84
93
  export { SchemaValidator };
85
94
  export default SchemaValidator;
@@ -1,16 +1,15 @@
1
- import { ISchemasProvider, Schema, ISchemaActions, ObjectSchemaDefinitionParam, ValidationResult, ISchemaValidator, DefaultSchemaType } from './index';
2
- export default class SchemaValidator<T = Record<string, never>> implements ISchemasProvider<T>, ISchemaValidator<T> {
1
+ import { Merge } from '@cleverbrush/deep';
2
+ import { ISchemasProvider, Schema, ISchemaActions, ObjectSchemaDefinitionParam, ValidationResult, ISchemaValidator, DefaultSchemaType, Cons, Unfold } from './index';
3
+ export default class SchemaValidator<T extends Record<string, never> = Record<string, never>, SchemaTypesStructures extends unknown[] = []> implements ISchemasProvider<SchemaTypesStructures>, ISchemaValidator<T, SchemaTypesStructures> {
3
4
  private _schemasMap;
4
5
  private _schemasCache;
5
6
  private _preprocessorsMap;
6
7
  get preprocessors(): Map<string, (value: unknown) => unknown>;
7
- addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
8
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
8
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T, SchemaTypesStructures>;
9
+ addSchemaType<K, L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
9
10
  [key in keyof K]: L;
10
- }>;
11
- get schemas(): {
12
- [K in keyof T]: ISchemaActions<T, K>;
13
- };
11
+ }, Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>>;
12
+ get schemas(): Merge<SchemaTypesStructures>;
14
13
  private validateDefaultType;
15
14
  private checkValidators;
16
15
  validate<K>(schema: keyof T | DefaultSchemaType | Schema<K>, obj: any): Promise<ValidationResult>;
@@ -2,10 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const deep_1 = require("@cleverbrush/deep");
4
4
  const validateNumber_1 = require("./validators/validateNumber");
5
+ const validateBoolean_1 = require("./validators/validateBoolean");
5
6
  const validateString_1 = require("./validators/validateString");
6
7
  const validateArray_1 = require("./validators/validateArray");
7
8
  const validateObject_1 = require("./validators/validateObject");
8
- const defaultSchemaNames = ['string', 'number', 'date', 'array'];
9
+ const defaultSchemaNames = ['string', 'boolean', 'number', 'date', 'array'];
9
10
  const defaultSchemas = {
10
11
  number: {
11
12
  type: 'number',
@@ -14,6 +15,11 @@ const defaultSchemas = {
14
15
  ensureNotNaN: true,
15
16
  ensureIsFinite: true
16
17
  },
18
+ boolean: {
19
+ type: 'boolean',
20
+ isRequired: true,
21
+ isNullable: false
22
+ },
17
23
  string: {
18
24
  type: 'string',
19
25
  isNullable: false,
@@ -30,6 +36,7 @@ const defaultSchemas = {
30
36
  };
31
37
  const defaultSchemasValidationStrategies = {
32
38
  number: (obj, schema, validator) => (0, validateNumber_1.validateNumber)(obj, schema, validator),
39
+ boolean: (obj, schema, validator) => (0, validateBoolean_1.validateBoolean)(obj, schema, validator),
33
40
  string: (obj, schema, validator) => (0, validateString_1.validateString)(obj, schema, validator),
34
41
  array: (obj, schema, validator) => (0, validateArray_1.validateArray)(obj, schema, validator)
35
42
  };
@@ -74,10 +81,22 @@ class SchemaValidator {
74
81
  return this._schemasCache;
75
82
  const res = {};
76
83
  for (const key of this._schemasMap.keys()) {
77
- res[key] = {
78
- validate: (value) => this.validate(this._schemasMap.get(key), value),
79
- schema: this._schemasMap.get(key)
80
- };
84
+ const parts = key.split('.');
85
+ let curr = res;
86
+ for (let i = 0; i < parts.length; i++) {
87
+ if (i === parts.length - 1) {
88
+ curr[parts[i]] = {
89
+ validate: (value) => this.validate(this._schemasMap.get(key), value),
90
+ schema: this._schemasMap.get(key)
91
+ };
92
+ }
93
+ else {
94
+ if (typeof curr[parts[i]] === 'undefined') {
95
+ curr[parts[i]] = {};
96
+ }
97
+ }
98
+ curr = curr[parts[i]];
99
+ }
81
100
  }
82
101
  this._schemasCache = res;
83
102
  return this._schemasCache;
@@ -142,12 +161,11 @@ class SchemaValidator {
142
161
  if (isDefaultType(schema)) {
143
162
  return await this.validateDefaultType(schema, obj);
144
163
  }
145
- else if (typeof this.schemas[schema] !== 'undefined') {
146
- if (Array.isArray(this.schemas[schema].schema)) {
147
- return this.validate(this.schemas[schema]
148
- .schema, obj);
164
+ else if (typeof this._schemasMap.get(schema) !== 'undefined') {
165
+ if (Array.isArray(this._schemasMap.get(schema))) {
166
+ return this.validate(this._schemasMap.get(schema), obj);
149
167
  }
150
- const objSchema = (0, deep_1.deepExtend)(defaultSchemas.object, this.schemas[schema].schema);
168
+ const objSchema = (0, deep_1.deepExtend)(defaultSchemas.object, this._schemasMap.get(schema));
151
169
  const res = await (0, validateObject_1.validateObject)(obj, objSchema, this);
152
170
  if (!res.valid)
153
171
  return res;
@@ -182,8 +200,10 @@ class SchemaValidator {
182
200
  return await this.validateDefaultType(schema.type, obj, schema);
183
201
  }
184
202
  else if (schema.type === 'alias') {
185
- const alias = this.schemas[schema.schemaName]
186
- .schema;
203
+ const alias = this._schemasMap.get(schema.schemaName);
204
+ if (typeof alias === 'undefined') {
205
+ throw new Error(`Unknown schema alias - ${schema.schemaName}`);
206
+ }
187
207
  if (Array.isArray(alias)) {
188
208
  if ((!schema.isRequired && typeof obj === 'undefined') ||
189
209
  (schema.isNullable && obj === null)) {
@@ -0,0 +1,2 @@
1
+ import { ValidationResult, BooleanSchemaDefinition, ISchemaValidator } from '../index';
2
+ export declare const validateBoolean: (obj: any, schema: BooleanSchemaDefinition<any>, validator: ISchemaValidator<any>) => Promise<ValidationResult>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateBoolean = void 0;
4
+ const validateBoolean = async (obj, schema, validator) => {
5
+ if (typeof obj === 'undefined' &&
6
+ typeof schema === 'object' &&
7
+ schema.isRequired === false) {
8
+ return {
9
+ valid: true
10
+ };
11
+ }
12
+ if (typeof obj !== 'boolean')
13
+ return {
14
+ valid: false,
15
+ errors: [`expected type boolean, but saw ${typeof obj}`]
16
+ };
17
+ if (typeof schema === 'boolean') {
18
+ return { valid: true };
19
+ }
20
+ const bool = obj;
21
+ if (typeof schema.equals === 'boolean') {
22
+ return schema.equals === bool
23
+ ? { valid: true }
24
+ : {
25
+ valid: false,
26
+ errors: [`must be equal to ${schema.equals}`]
27
+ };
28
+ }
29
+ return {
30
+ valid: true
31
+ };
32
+ };
33
+ exports.validateBoolean = validateBoolean;
@@ -1,2 +1,2 @@
1
1
  import { ValidationResult, ObjectSchemaDefinition, ISchemaValidator } from '../index';
2
- export declare const validateObject: (obj: any, schema: ObjectSchemaDefinition<any>, validator: ISchemaValidator<any>) => Promise<ValidationResult>;
2
+ export declare const validateObject: (obj: any, schema: ObjectSchemaDefinition<any>, validator: ISchemaValidator<any, unknown[]>) => Promise<ValidationResult>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleverbrush/schema",
3
- "version": "0.0.13",
3
+ "version": "0.0.16",
4
4
  "keywords": [
5
5
  "object schema validator",
6
6
  "schema",
@@ -19,7 +19,7 @@
19
19
  "build": "tsc"
20
20
  },
21
21
  "dependencies": {
22
- "@cleverbrush/deep": "0.0.13"
22
+ "@cleverbrush/deep": "0.0.16"
23
23
  },
24
24
  "types": "./dist/index.d.ts"
25
25
  }
package/src/index.ts CHANGED
@@ -1,7 +1,9 @@
1
+ import { Merge } from '@cleverbrush/deep';
1
2
  import SchemaValidator from './schemaValidator';
2
3
 
3
4
  export type DefaultSchemaType =
4
5
  | 'object'
6
+ | 'boolean'
5
7
  | 'function'
6
8
  | 'number'
7
9
  | 'string'
@@ -44,7 +46,7 @@ export type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
44
46
  ?
45
47
  | ((
46
48
  value: unknown
47
- ) => PropType<T, S> | Promise<PropType<T, S>>)
49
+ ) => undefined | PropType<T, S> | Promise<PropType<T, S>>)
48
50
  | string
49
51
  : (value: T) => void | Promise<void>;
50
52
  }>;
@@ -72,6 +74,14 @@ export type FunctionSchemaDefinition<T extends (...args: any) => any> = Omit<
72
74
  params?: ParamsValidators<T>;
73
75
  };
74
76
 
77
+ export type BooleanSchemaDefinition<TObj> = Omit<
78
+ SchemaDefintion<TObj>,
79
+ 'type'
80
+ > & {
81
+ type: 'boolean';
82
+ equals?: boolean;
83
+ };
84
+
75
85
  export type NumberSchemaDefinition<TObj> = Omit<
76
86
  SchemaDefintion<TObj>,
77
87
  'type'
@@ -113,6 +123,7 @@ export type CompositeSchema<TObj> = TObj extends (...args: any) => any
113
123
  : TObj extends string
114
124
  ? StringSchemaDefinition<TObj> | 'string'
115
125
  :
126
+ | BooleanSchemaDefinition<TObj>
116
127
  | NumberSchemaDefinition<TObj>
117
128
  | StringSchemaDefinition<TObj>
118
129
  | ArraySchemaDefinition<TObj>
@@ -127,18 +138,25 @@ export type SingleSchema<TObj = Record<string, never>> =
127
138
 
128
139
  export type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
129
140
 
130
- export interface ISchemaActions<K, T extends keyof K> {
141
+ export type Cons<H, T extends unknown[] = []> = T['length'] extends 0
142
+ ? [H]
143
+ : ((h: H, ...t: T) => void) extends (...r: infer R) => void
144
+ ? R
145
+ : never;
146
+
147
+ export interface ISchemaActions<S> {
131
148
  validate(value: any): Promise<ValidationResult>;
132
- schema: PropType<K, T>;
149
+ schema: S;
133
150
  }
134
151
 
135
- export interface ISchemasProvider<T = Record<string, never>> {
136
- schemas: {
137
- [K in keyof T]: ISchemaActions<T, K>;
138
- };
152
+ export interface ISchemasProvider<T extends unknown[]> {
153
+ schemas: Merge<T>;
139
154
  }
140
155
 
141
- export interface ISchemaValidator<T = Record<string, never>> {
156
+ export interface ISchemaValidator<
157
+ T extends Record<string, never> = Record<string, never>,
158
+ SchemaTypesStructures extends unknown[] = []
159
+ > {
142
160
  get preprocessors(): Map<
143
161
  string,
144
162
  (value: unknown) => unknown | Promise<unknown>
@@ -146,12 +164,19 @@ export interface ISchemaValidator<T = Record<string, never>> {
146
164
  addPreprocessor(
147
165
  name: string,
148
166
  preprocessor: (value: unknown) => unknown | Promise<unknown>
149
- ): SchemaValidator<T>;
167
+ ): SchemaValidator<T, SchemaTypesStructures>;
150
168
 
151
- addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
169
+ addSchemaType<
170
+ K,
171
+ L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>,
172
+ M = any
173
+ >(
152
174
  name: keyof K,
153
- schema: L | Array<Schema<any>>
154
- ): SchemaValidator<T & { [key in keyof K]: typeof schema }>;
175
+ schema: L
176
+ ): SchemaValidator<
177
+ T & { [key in keyof K]: typeof schema },
178
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
179
+ >;
155
180
 
156
181
  validate(
157
182
  schema: keyof T | DefaultSchemaType | Schema<any>,
@@ -159,5 +184,15 @@ export interface ISchemaValidator<T = Record<string, never>> {
159
184
  ): Promise<ValidationResult>;
160
185
  }
161
186
 
187
+ export type Unfold<T, K> = T extends string
188
+ ? T extends `${infer F}.${infer L}`
189
+ ? {
190
+ [k in F]: Unfold<L, K>;
191
+ }
192
+ : {
193
+ [k in T]: K;
194
+ }
195
+ : never;
196
+
162
197
  export { SchemaValidator };
163
198
  export default SchemaValidator;
@@ -170,7 +170,7 @@ test('Validate - no schema', async () => {
170
170
  const validator = new SchemaValidator();
171
171
  const cth = jest.fn();
172
172
  validator
173
- .validate(null, 10)
173
+ .validate(null as any, 10)
174
174
  .catch(cth)
175
175
  .then(() => {
176
176
  expect(cth).toBeCalled();
@@ -554,6 +554,82 @@ test('Validate - number by schema - custom validators - 2', async () => {
554
554
  expect(result).toHaveProperty('valid', false);
555
555
  });
556
556
 
557
+ test('Validate - boolean - 1', async () => {
558
+ const validator = new SchemaValidator();
559
+ const result = await validator.validate(
560
+ {
561
+ type: 'boolean'
562
+ },
563
+ 300
564
+ );
565
+
566
+ expect(result).toHaveProperty('valid', false);
567
+ });
568
+
569
+ test('Validate - boolean - 2', async () => {
570
+ const validator = new SchemaValidator();
571
+ const result = await validator.validate(
572
+ {
573
+ type: 'boolean'
574
+ },
575
+ true
576
+ );
577
+
578
+ expect(result).toHaveProperty('valid', true);
579
+ });
580
+
581
+ test('Validate - boolean - 3', async () => {
582
+ const validator = new SchemaValidator();
583
+ const result = await validator.validate(
584
+ {
585
+ type: 'boolean'
586
+ },
587
+ false
588
+ );
589
+
590
+ expect(result).toHaveProperty('valid', true);
591
+ });
592
+
593
+ test('Validate - boolean - 4', async () => {
594
+ const validator = new SchemaValidator();
595
+ const result = await validator.validate(
596
+ {
597
+ type: 'boolean',
598
+ equals: true
599
+ },
600
+ false
601
+ );
602
+
603
+ expect(result).toHaveProperty('valid', false);
604
+ });
605
+
606
+ test('Validate - boolean - 5', async () => {
607
+ const validator = new SchemaValidator();
608
+ const result = await validator.validate(
609
+ {
610
+ type: 'boolean',
611
+ equals: false
612
+ },
613
+ false
614
+ );
615
+
616
+ expect(result).toHaveProperty('valid', true);
617
+ });
618
+
619
+ test('Validate - boolean - 6', async () => {
620
+ const validator = new SchemaValidator();
621
+ const result = await validator.validate('boolean', false);
622
+
623
+ expect(result).toHaveProperty('valid', true);
624
+ });
625
+
626
+ test('Validate - boolean - 7', async () => {
627
+ const validator = new SchemaValidator();
628
+ const result = await validator.validate('boolean', '123');
629
+
630
+ expect(result).toHaveProperty('valid', false);
631
+ });
632
+
557
633
  test('Validate - string - 1', async () => {
558
634
  const validator = new SchemaValidator();
559
635
  const result = await validator.validate('string', '12345');
@@ -868,7 +944,7 @@ test('Validate - object - 2', async () => {
868
944
  };
869
945
  }
870
946
  ]
871
- })
947
+ }) as any as ObjectSchemaDefinition<any>
872
948
  );
873
949
  const user: User = {
874
950
  id: 1,
@@ -1123,19 +1199,30 @@ test('Validate schema - 1', async () => {
1123
1199
  });
1124
1200
 
1125
1201
  test('Validate schema - 2', async () => {
1126
- const validator = new SchemaValidator().addSchemaType('Date', {
1127
- validators: [
1128
- (value) =>
1129
- value instanceof Date && !Number.isNaN(value)
1130
- ? {
1131
- valid: true
1132
- }
1133
- : {
1134
- valid: false,
1135
- errors: ['should be a valid Date object']
1136
- }
1137
- ]
1138
- });
1202
+ const validator = new SchemaValidator()
1203
+ .addSchemaType('Date', {
1204
+ validators: [
1205
+ (value) =>
1206
+ value instanceof Date && !Number.isNaN(value)
1207
+ ? {
1208
+ valid: true
1209
+ }
1210
+ : {
1211
+ valid: false,
1212
+ errors: ['should be a valid Date object']
1213
+ }
1214
+ ]
1215
+ })
1216
+ .addSchemaType('Module.Schema1', {
1217
+ properties: {
1218
+ a: 'string'
1219
+ }
1220
+ })
1221
+ .addSchemaType('Module.Schema2', {
1222
+ properties: {
1223
+ b: 'number'
1224
+ }
1225
+ });
1139
1226
 
1140
1227
  const result = await validator.validate(
1141
1228
  {
@@ -1225,8 +1312,10 @@ test('Preprocessors - 1', async () => {
1225
1312
  bornAt: 'Date'
1226
1313
  },
1227
1314
  preprocessors: {
1228
- bornAt: (value: unknown): Date => {
1229
- const time = Date.parse(value.toString());
1315
+ bornAt: (value: unknown): Date | undefined => {
1316
+ const time = Date.parse(
1317
+ (value as Record<string, unknown>).toString()
1318
+ );
1230
1319
  if (Number.isNaN(time)) return undefined;
1231
1320
  return new Date(time);
1232
1321
  }
@@ -1264,18 +1353,28 @@ test('Preprocessors - 2', async () => {
1264
1353
  }
1265
1354
  };
1266
1355
 
1267
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1268
- const time = Date.parse(value.toString());
1269
- if (Number.isNaN(time)) return undefined;
1270
- return new Date(time);
1271
- });
1272
-
1273
- expect(() =>
1274
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1275
- const time = Date.parse(value.toString());
1356
+ validator.addPreprocessor(
1357
+ 'StringToDate',
1358
+ (value: unknown): Date | undefined => {
1359
+ const time = Date.parse(
1360
+ (value as Record<string, unknown>).toString()
1361
+ );
1276
1362
  if (Number.isNaN(time)) return undefined;
1277
1363
  return new Date(time);
1278
- })
1364
+ }
1365
+ );
1366
+
1367
+ expect(() =>
1368
+ validator.addPreprocessor(
1369
+ 'StringToDate',
1370
+ (value: unknown): Date | undefined => {
1371
+ const time = Date.parse(
1372
+ (value as Record<string, unknown>).toString()
1373
+ );
1374
+ if (Number.isNaN(time)) return undefined;
1375
+ return new Date(time);
1376
+ }
1377
+ )
1279
1378
  ).toThrow();
1280
1379
 
1281
1380
  const result = await validator.validate(schema, {
@@ -1316,11 +1415,16 @@ test('Preprocessors - 3', async () => {
1316
1415
  ]
1317
1416
  });
1318
1417
 
1319
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1320
- const time = Date.parse(value.toString());
1321
- if (Number.isNaN(time)) return undefined;
1322
- return new Date(time);
1323
- });
1418
+ validator.addPreprocessor(
1419
+ 'StringToDate',
1420
+ (value: unknown): Date | undefined => {
1421
+ const time = Date.parse(
1422
+ (value as Record<string, unknown>).toString()
1423
+ );
1424
+ if (Number.isNaN(time)) return undefined;
1425
+ return new Date(time);
1426
+ }
1427
+ );
1324
1428
 
1325
1429
  let obj = [new Date().toJSON()];
1326
1430
 
@@ -1337,8 +1441,10 @@ test('Preprocessors - 3', async () => {
1337
1441
  obj = [new Date().toJSON()];
1338
1442
  result = await validator.validate(
1339
1443
  {
1340
- preprocessor: (value: unknown): Date => {
1341
- const time = Date.parse(value.toString());
1444
+ preprocessor: (value: unknown): Date | undefined => {
1445
+ const time = Date.parse(
1446
+ (value as Record<string, unknown>).toString()
1447
+ );
1342
1448
  if (Number.isNaN(time)) return undefined;
1343
1449
  return new Date(time);
1344
1450
  },
@@ -1352,8 +1458,10 @@ test('Preprocessors - 3', async () => {
1352
1458
  obj = ['sdfsdf12', new Date().toJSON()];
1353
1459
  result = await validator.validate(
1354
1460
  {
1355
- preprocessor: (value: unknown): Date => {
1356
- const time = Date.parse(value.toString());
1461
+ preprocessor: (value: unknown): Date | undefined => {
1462
+ const time = Date.parse(
1463
+ (value as Record<string, unknown>).toString()
1464
+ );
1357
1465
  if (Number.isNaN(time)) return undefined;
1358
1466
  return new Date(time);
1359
1467
  },
@@ -1409,11 +1517,16 @@ test('Preprocessors - 4', async () => {
1409
1517
  ]
1410
1518
  });
1411
1519
 
1412
- validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1413
- const time = Date.parse(value.toString());
1414
- if (Number.isNaN(time)) return undefined;
1415
- return new Date(time);
1416
- });
1520
+ validator.addPreprocessor(
1521
+ 'StringToDate',
1522
+ (value: unknown): Date | undefined => {
1523
+ const time = Date.parse(
1524
+ (value as Record<string, unknown>).toString()
1525
+ );
1526
+ if (Number.isNaN(time)) return undefined;
1527
+ return new Date(time);
1528
+ }
1529
+ );
1417
1530
 
1418
1531
  let obj = [new Date().toJSON()];
1419
1532
 
@@ -1430,8 +1543,10 @@ test('Preprocessors - 4', async () => {
1430
1543
  obj = [new Date().toJSON()];
1431
1544
  result = await validator.validate(
1432
1545
  {
1433
- preprocessor: (value: unknown): Date => {
1434
- const time = Date.parse(value.toString());
1546
+ preprocessor: (value: unknown): Date | undefined => {
1547
+ const time = Date.parse(
1548
+ (value as Record<string, unknown>).toString()
1549
+ );
1435
1550
  if (Number.isNaN(time)) return undefined;
1436
1551
  return new Date(time);
1437
1552
  },
@@ -1445,8 +1560,10 @@ test('Preprocessors - 4', async () => {
1445
1560
  obj = ['sdfsdf12', new Date().toJSON()];
1446
1561
  result = await validator.validate(
1447
1562
  {
1448
- preprocessor: (value: unknown): Date => {
1449
- const time = Date.parse(value.toString());
1563
+ preprocessor: (value: unknown): Date | undefined => {
1564
+ const time = Date.parse(
1565
+ (value as Record<string, unknown>).toString()
1566
+ );
1450
1567
  if (Number.isNaN(time)) return undefined;
1451
1568
  return new Date(time);
1452
1569
  },
@@ -1457,3 +1574,83 @@ test('Preprocessors - 4', async () => {
1457
1574
  );
1458
1575
  expect(result).toHaveProperty('valid', false);
1459
1576
  });
1577
+
1578
+ test('Submodules - 1', async () => {
1579
+ const validator = new SchemaValidator().addSchemaType(
1580
+ 'Module1.Schema1',
1581
+ {}
1582
+ );
1583
+
1584
+ const result = validator.schemas;
1585
+
1586
+ expect(result).toHaveProperty('Module1');
1587
+ expect(result).toHaveProperty('Module1.Schema1');
1588
+ });
1589
+
1590
+ test('Submodules - 2', async () => {
1591
+ const validator = new SchemaValidator()
1592
+ .addSchemaType('Module1.Schema1', {})
1593
+ .addSchemaType('Module1.Schema2', {
1594
+ properties: {
1595
+ a: 'number'
1596
+ }
1597
+ });
1598
+
1599
+ const result = validator.schemas;
1600
+
1601
+ expect(result).toHaveProperty('Module1');
1602
+ expect(result).toHaveProperty('Module1.Schema1');
1603
+ expect(result).toHaveProperty('Module1.Schema2');
1604
+
1605
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1606
+ a: 234
1607
+ });
1608
+ expect(result2).toHaveProperty('valid', true);
1609
+ });
1610
+
1611
+ test('Submodules - 3', async () => {
1612
+ const validator = new SchemaValidator()
1613
+ .addSchemaType('Module1.Schema1', {
1614
+ properties: {
1615
+ b: 'number'
1616
+ }
1617
+ })
1618
+ .addSchemaType('Module1.Schema2', {
1619
+ properties: {
1620
+ a: {
1621
+ type: 'alias',
1622
+ schemaName: 'Module1.Schema1'
1623
+ }
1624
+ }
1625
+ });
1626
+
1627
+ const result2 = await validator.schemas.Module1.Schema2.validate({
1628
+ a: {
1629
+ b: 20
1630
+ }
1631
+ });
1632
+ expect(result2).toHaveProperty('valid', true);
1633
+ });
1634
+
1635
+ test('Submodules - 4', async () => {
1636
+ const validator = new SchemaValidator()
1637
+ .addSchemaType('Module1.Schema1', {
1638
+ properties: {
1639
+ b: 'number'
1640
+ }
1641
+ })
1642
+ .addSchemaType('Module1.Schema2', {
1643
+ properties: {
1644
+ a: {
1645
+ type: 'alias',
1646
+ schemaName: 'Module1.Schema3'
1647
+ }
1648
+ }
1649
+ });
1650
+
1651
+ await validator.schemas.Module1.Schema2.validate({
1652
+ a: {
1653
+ b: 20
1654
+ }
1655
+ }).catch((e) => expect(e).toBeInstanceOf(Error));
1656
+ });
@@ -1,4 +1,4 @@
1
- import { deepExtend } from '@cleverbrush/deep';
1
+ import { deepExtend, Merge } from '@cleverbrush/deep';
2
2
  import {
3
3
  ISchemasProvider,
4
4
  Schema,
@@ -13,14 +13,17 @@ import {
13
13
  ISchemaValidator,
14
14
  DefaultSchemaType,
15
15
  ObjectSchemaDefinition,
16
- SchemaDefintion
16
+ BooleanSchemaDefinition,
17
+ Cons,
18
+ Unfold
17
19
  } from './index';
18
20
  import { validateNumber } from './validators/validateNumber';
21
+ import { validateBoolean } from './validators/validateBoolean';
19
22
  import { validateString } from './validators/validateString';
20
23
  import { validateArray } from './validators/validateArray';
21
24
  import { validateObject } from './validators/validateObject';
22
25
 
23
- const defaultSchemaNames = ['string', 'number', 'date', 'array'];
26
+ const defaultSchemaNames = ['string', 'boolean', 'number', 'date', 'array'];
24
27
 
25
28
  const defaultSchemas: { [key in DefaultSchemaType]?: Schema<any> } = {
26
29
  number: {
@@ -30,6 +33,11 @@ const defaultSchemas: { [key in DefaultSchemaType]?: Schema<any> } = {
30
33
  ensureNotNaN: true,
31
34
  ensureIsFinite: true
32
35
  },
36
+ boolean: {
37
+ type: 'boolean',
38
+ isRequired: true,
39
+ isNullable: false
40
+ },
33
41
  string: {
34
42
  type: 'string',
35
43
  isNullable: false,
@@ -49,31 +57,40 @@ const defaultSchemasValidationStrategies = {
49
57
  number: (
50
58
  obj: any,
51
59
  schema: NumberSchemaDefinition<any>,
52
- validator: ISchemaValidator<any>
60
+ validator: ISchemaValidator<any, unknown[]>
53
61
  ): Promise<ValidationResult> => validateNumber(obj, schema, validator),
62
+ boolean: (
63
+ obj: any,
64
+ schema: BooleanSchemaDefinition<any>,
65
+ validator: ISchemaValidator<any, unknown[]>
66
+ ): Promise<ValidationResult> => validateBoolean(obj, schema, validator),
54
67
  string: (
55
68
  obj: any,
56
69
  schema: StringSchemaDefinition<any>,
57
- validator: ISchemaValidator<any>
70
+ validator: ISchemaValidator<any, unknown[]>
58
71
  ): Promise<ValidationResult> => validateString(obj, schema, validator),
59
72
  array: (
60
73
  obj: any,
61
74
  schema: ArraySchemaDefinition<any>,
62
- validator: ISchemaValidator<any>
75
+ validator: ISchemaValidator<any, unknown[]>
63
76
  ): Promise<ValidationResult> => validateArray(obj, schema, validator)
64
77
  };
65
78
 
66
79
  const isDefaultType = (name: string): boolean =>
67
80
  defaultSchemaNames.indexOf(name) !== -1;
68
81
 
69
- export default class SchemaValidator<T = Record<string, never>>
70
- implements ISchemasProvider<T>, ISchemaValidator<T>
82
+ export default class SchemaValidator<
83
+ T extends Record<string, never> = Record<string, never>,
84
+ SchemaTypesStructures extends unknown[] = []
85
+ > implements
86
+ ISchemasProvider<SchemaTypesStructures>,
87
+ ISchemaValidator<T, SchemaTypesStructures>
71
88
  {
72
89
  private _schemasMap = new Map<string, Schema<any>>();
73
- private _schemasCache: { [K in keyof T]: ISchemaActions<T, K> } = null;
90
+ private _schemasCache: Merge<SchemaTypesStructures> = null;
74
91
  private _preprocessorsMap = new Map<
75
92
  string,
76
- (value: unknown) => unknown | Promise<unknown>
93
+ (value: unknown) => unknown | Promise<unknown> | undefined
77
94
  >();
78
95
 
79
96
  public get preprocessors(): Map<string, (value: unknown) => unknown> {
@@ -83,17 +100,24 @@ export default class SchemaValidator<T = Record<string, never>>
83
100
  public addPreprocessor(
84
101
  name: string,
85
102
  preprocessor: (value: unknown) => unknown | Promise<unknown>
86
- ): SchemaValidator<T> {
103
+ ): SchemaValidator<T, SchemaTypesStructures> {
87
104
  if (this._preprocessorsMap.has(name))
88
105
  throw new Error(`Preprocessor '${name}' is already registered`);
89
106
  this._preprocessorsMap.set(name, preprocessor);
90
107
  return this;
91
108
  }
92
109
 
93
- public addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
110
+ public addSchemaType<
111
+ K,
112
+ L extends ObjectSchemaDefinitionParam<M> | Array<Schema<any>>,
113
+ M = any
114
+ >(
94
115
  name: keyof K,
95
- schema: L | Array<Schema<any>>
96
- ): SchemaValidator<T & { [key in keyof K]: L }> {
116
+ schema: L
117
+ ): SchemaValidator<
118
+ T & { [key in keyof K]: L },
119
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
120
+ > {
97
121
  if (typeof name !== 'string' || !name)
98
122
  throw new Error('Name is required');
99
123
 
@@ -108,7 +132,10 @@ export default class SchemaValidator<T = Record<string, never>>
108
132
  if (Array.isArray(schema)) {
109
133
  this._schemasMap.set(name.toString(), schema as Schema<any>);
110
134
  this._schemasCache = null;
111
- return this as any as SchemaValidator<T & { [key in keyof K]: L }>;
135
+ return this as any as SchemaValidator<
136
+ T & { [key in keyof K]: L },
137
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
138
+ >;
112
139
  }
113
140
 
114
141
  if (typeof schema !== 'object')
@@ -119,20 +146,34 @@ export default class SchemaValidator<T = Record<string, never>>
119
146
  } as Schema<any>);
120
147
  this._schemasCache = null;
121
148
 
122
- return this as any as SchemaValidator<T & { [key in keyof K]: L }>;
149
+ return this as any as SchemaValidator<
150
+ T & { [key in keyof K]: L },
151
+ Cons<Unfold<keyof K, ISchemaActions<L>>, SchemaTypesStructures>
152
+ >;
123
153
  }
124
154
 
125
- public get schemas(): { [K in keyof T]: ISchemaActions<T, K> } {
155
+ public get schemas(): Merge<SchemaTypesStructures> {
126
156
  if (this._schemasCache) return this._schemasCache;
127
157
  const res = {};
128
158
  for (const key of this._schemasMap.keys()) {
129
- res[key] = {
130
- validate: (value: any): Promise<any> =>
131
- this.validate(this._schemasMap.get(key), value),
132
- schema: this._schemasMap.get(key)
133
- };
159
+ const parts = key.split('.');
160
+ let curr = res;
161
+ for (let i = 0; i < parts.length; i++) {
162
+ if (i === parts.length - 1) {
163
+ curr[parts[i]] = {
164
+ validate: (value: any): Promise<any> =>
165
+ this.validate(this._schemasMap.get(key), value),
166
+ schema: this._schemasMap.get(key)
167
+ };
168
+ } else {
169
+ if (typeof curr[parts[i]] === 'undefined') {
170
+ curr[parts[i]] = {};
171
+ }
172
+ }
173
+ curr = curr[parts[i]];
174
+ }
134
175
  }
135
- this._schemasCache = res as { [K in keyof T]: ISchemaActions<T, K> };
176
+ this._schemasCache = res as Merge<SchemaTypesStructures>;
136
177
  return this._schemasCache;
137
178
  }
138
179
 
@@ -144,14 +185,14 @@ export default class SchemaValidator<T = Record<string, never>>
144
185
  const strategy = defaultSchemasValidationStrategies[name] as (
145
186
  obj: any,
146
187
  schema: Schema<any>,
147
- validator: ISchemaValidator<any>
188
+ validator: ISchemaValidator<any, unknown[]>
148
189
  ) => ValidationResult;
149
190
  let finalSchema = defaultSchemas[name] as CompositeSchema<
150
191
  Record<string, never>
151
192
  >;
152
193
  if (strategy) {
153
194
  if (typeof mergeSchema === 'object') {
154
- finalSchema = deepExtend(finalSchema, mergeSchema);
195
+ finalSchema = deepExtend(finalSchema, mergeSchema) as any;
155
196
  }
156
197
  if (typeof mergeSchema === 'number') {
157
198
  finalSchema = {
@@ -161,7 +202,11 @@ export default class SchemaValidator<T = Record<string, never>>
161
202
  mergeSchema;
162
203
  }
163
204
 
164
- let preliminaryResult = await strategy(value, finalSchema, this);
205
+ let preliminaryResult = await strategy(
206
+ value,
207
+ finalSchema,
208
+ this as any
209
+ );
165
210
  if (!preliminaryResult.valid) return preliminaryResult;
166
211
 
167
212
  preliminaryResult = await this.checkValidators(finalSchema, value);
@@ -225,19 +270,15 @@ export default class SchemaValidator<T = Record<string, never>>
225
270
  schema as DefaultSchemaType,
226
271
  obj
227
272
  );
228
- } else if (typeof this.schemas[schema as keyof T] !== 'undefined') {
229
- if (Array.isArray(this.schemas[schema as keyof T].schema)) {
230
- return this.validate(
231
- this.schemas[schema as keyof T]
232
- .schema as any as Schema<any>,
233
- obj
234
- );
273
+ } else if (typeof this._schemasMap.get(schema) !== 'undefined') {
274
+ if (Array.isArray(this._schemasMap.get(schema))) {
275
+ return this.validate(this._schemasMap.get(schema), obj);
235
276
  }
236
277
  const objSchema = deepExtend(
237
278
  defaultSchemas.object,
238
- this.schemas[schema as keyof T].schema
279
+ this._schemasMap.get(schema)
239
280
  ) as ObjectSchemaDefinition<Record<string, never>>;
240
- const res = await validateObject(obj, objSchema, this);
281
+ const res = await validateObject(obj, objSchema, this as any);
241
282
  if (!res.valid) return res;
242
283
  return await this.checkValidators(objSchema, obj);
243
284
  } else {
@@ -276,8 +317,12 @@ export default class SchemaValidator<T = Record<string, never>>
276
317
  schema
277
318
  );
278
319
  } else if (schema.type === 'alias') {
279
- const alias = this.schemas[schema.schemaName]
280
- .schema as Schema<any>;
320
+ const alias = this._schemasMap.get(schema.schemaName);
321
+ if (typeof alias === 'undefined') {
322
+ throw new Error(
323
+ `Unknown schema alias - ${schema.schemaName}`
324
+ );
325
+ }
281
326
  if (Array.isArray(alias)) {
282
327
  if (
283
328
  (!schema.isRequired && typeof obj === 'undefined') ||
@@ -306,7 +351,7 @@ export default class SchemaValidator<T = Record<string, never>>
306
351
  const preliminaryResult = await validateObject(
307
352
  obj,
308
353
  schema,
309
- this
354
+ this as any
310
355
  );
311
356
  if (!preliminaryResult.valid) return preliminaryResult;
312
357
 
@@ -0,0 +1,45 @@
1
+ import {
2
+ ValidationResult,
3
+ BooleanSchemaDefinition,
4
+ ISchemaValidator
5
+ } from '../index';
6
+
7
+ export const validateBoolean = async (
8
+ obj: any,
9
+ schema: BooleanSchemaDefinition<any>,
10
+ validator: ISchemaValidator<any>
11
+ ): Promise<ValidationResult> => {
12
+ if (
13
+ typeof obj === 'undefined' &&
14
+ typeof schema === 'object' &&
15
+ schema.isRequired === false
16
+ ) {
17
+ return {
18
+ valid: true
19
+ };
20
+ }
21
+ if (typeof obj !== 'boolean')
22
+ return {
23
+ valid: false,
24
+ errors: [`expected type boolean, but saw ${typeof obj}`]
25
+ };
26
+
27
+ if (typeof schema === 'boolean') {
28
+ return { valid: true };
29
+ }
30
+
31
+ const bool = obj as boolean;
32
+
33
+ if (typeof schema.equals === 'boolean') {
34
+ return schema.equals === bool
35
+ ? { valid: true }
36
+ : {
37
+ valid: false,
38
+ errors: [`must be equal to ${schema.equals}`]
39
+ };
40
+ }
41
+
42
+ return {
43
+ valid: true
44
+ };
45
+ };
@@ -8,7 +8,7 @@ import {
8
8
  export const validateObject = async (
9
9
  obj: any,
10
10
  schema: ObjectSchemaDefinition<any>,
11
- validator: ISchemaValidator<any>
11
+ validator: ISchemaValidator<any, unknown[]>
12
12
  ): Promise<ValidationResult> => {
13
13
  if (
14
14
  typeof obj === 'undefined' &&