@cleverbrush/schema 0.0.8 → 0.0.11

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
@@ -23,6 +23,9 @@ export declare type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'>
23
23
  properties?: Partial<{
24
24
  [S in keyof T]: Schema<PropType<T, S>>;
25
25
  }>;
26
+ preprocessors?: Partial<{
27
+ [S in keyof T]: ((value: unknown) => PropType<T, S> | Promise<PropType<T, S>>) | string;
28
+ }>;
26
29
  };
27
30
  export declare type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
28
31
  type: 'alias';
@@ -53,6 +56,7 @@ export declare type StringSchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, '
53
56
  };
54
57
  export declare type ArraySchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 'type'> & {
55
58
  type: 'array';
59
+ preprocessor?: ((value: unknown) => unknown | Promise<unknown>) | string;
56
60
  ofType?: Schema<any>;
57
61
  minLength?: number;
58
62
  maxLength?: number;
@@ -70,6 +74,8 @@ export interface ISchemasProvider<T = Record<string, never>> {
70
74
  };
71
75
  }
72
76
  export interface ISchemaValidator<T = Record<string, never>> {
77
+ get preprocessors(): Map<string, (value: unknown) => unknown | Promise<unknown>>;
78
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
73
79
  addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
74
80
  [key in keyof K]: typeof schema;
75
81
  }>;
@@ -2,6 +2,9 @@ import { ISchemasProvider, Schema, ISchemaActions, ObjectSchemaDefinitionParam,
2
2
  export default class SchemaValidator<T = Record<string, never>> implements ISchemasProvider<T>, ISchemaValidator<T> {
3
3
  private _schemasMap;
4
4
  private _schemasCache;
5
+ private _preprocessorsMap;
6
+ get preprocessors(): Map<string, (value: unknown) => unknown>;
7
+ addPreprocessor(name: string, preprocessor: (value: unknown) => unknown | Promise<unknown>): SchemaValidator<T>;
5
8
  addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
6
9
  [key in keyof K]: L;
7
10
  }>;
@@ -37,6 +37,16 @@ const isDefaultType = (name) => defaultSchemaNames.indexOf(name) !== -1;
37
37
  class SchemaValidator {
38
38
  _schemasMap = new Map();
39
39
  _schemasCache = null;
40
+ _preprocessorsMap = new Map();
41
+ get preprocessors() {
42
+ return this._preprocessorsMap;
43
+ }
44
+ addPreprocessor(name, preprocessor) {
45
+ if (this._preprocessorsMap.has(name))
46
+ throw new Error(`Preprocessor '${name}' is already registered`);
47
+ this._preprocessorsMap.set(name, preprocessor);
48
+ return this;
49
+ }
40
50
  addSchemaType(name, schema) {
41
51
  if (typeof name !== 'string' || !name)
42
52
  throw new Error('Name is required');
@@ -174,8 +184,15 @@ class SchemaValidator {
174
184
  else if (schema.type === 'alias') {
175
185
  const alias = this.schemas[schema.schemaName]
176
186
  .schema;
177
- if (Array.isArray(alias))
178
- throw new Error('it is impossible to use alternative schema alias as "type" field');
187
+ if (Array.isArray(alias)) {
188
+ if ((!schema.isRequired && typeof obj === 'undefined') ||
189
+ (schema.isNullable && obj === null)) {
190
+ return {
191
+ valid: true
192
+ };
193
+ }
194
+ return await this.validate(alias, obj);
195
+ }
179
196
  if (typeof alias !== 'object')
180
197
  throw new Error('it is only possible to use a full schema schema definition as alias');
181
198
  const schemaToMerge = { ...schema };
@@ -14,6 +14,17 @@ const validateArray = async (obj, schema, validator) => {
14
14
  valid: false,
15
15
  errors: ['expected type array']
16
16
  };
17
+ if (schema.preprocessor) {
18
+ const preprocessor = typeof schema.preprocessor === 'function'
19
+ ? schema.preprocessor
20
+ : validator.preprocessors.get(schema.preprocessor);
21
+ if (typeof preprocessor !== 'function') {
22
+ throw new Error(`unknown preprocessor '${schema.preprocessor}'`);
23
+ }
24
+ for (let i = 0; i < obj.length; i++) {
25
+ obj[i] = await Promise.resolve(preprocessor(obj[i]));
26
+ }
27
+ }
17
28
  if (typeof schema.minLength === 'number' && obj.length < schema.minLength) {
18
29
  return {
19
30
  valid: false,
@@ -17,6 +17,20 @@ const validateObject = async (obj, schema, validator) => {
17
17
  ]
18
18
  };
19
19
  }
20
+ if (typeof schema.preprocessors === 'object' && schema.preprocessors) {
21
+ await Promise.all(Object.keys(schema.preprocessors).map(async (key) => {
22
+ if (typeof schema.preprocessors[key] === 'function') {
23
+ obj[key] = await Promise.resolve(schema.preprocessors[key](obj[key]));
24
+ }
25
+ else {
26
+ const preprocessor = validator.preprocessors.get(schema.preprocessors[key]);
27
+ if (typeof preprocessor !== 'function') {
28
+ throw new Error(`preprocessor '${schema.preprocessors[key]}' is unknown`);
29
+ }
30
+ obj[key] = await Promise.resolve(preprocessor(obj[key]));
31
+ }
32
+ }));
33
+ }
20
34
  if (typeof schema.properties === 'object' && schema.properties) {
21
35
  const errors = (await Promise.all(Object.entries(schema.properties).map(async ([name, schema]) => {
22
36
  const result = await validator.validate(schema, obj[name]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleverbrush/schema",
3
- "version": "0.0.8",
3
+ "version": "0.0.11",
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.8"
22
+ "@cleverbrush/deep": "0.0.11"
23
23
  },
24
24
  "types": "./dist/index.d.ts"
25
25
  }
package/src/index.ts CHANGED
@@ -39,6 +39,11 @@ export type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
39
39
  properties?: Partial<{
40
40
  [S in keyof T]: Schema<PropType<T, S>>;
41
41
  }>;
42
+ preprocessors?: Partial<{
43
+ [S in keyof T]:
44
+ | ((value: unknown) => PropType<T, S> | Promise<PropType<T, S>>)
45
+ | string;
46
+ }>;
42
47
  };
43
48
 
44
49
  export type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
@@ -91,6 +96,7 @@ export type ArraySchemaDefinition<TObj> = Omit<
91
96
  'type'
92
97
  > & {
93
98
  type: 'array';
99
+ preprocessor?: ((value: unknown) => unknown | Promise<unknown>) | string;
94
100
  ofType?: Schema<any>;
95
101
  minLength?: number;
96
102
  maxLength?: number;
@@ -129,6 +135,15 @@ export interface ISchemasProvider<T = Record<string, never>> {
129
135
  }
130
136
 
131
137
  export interface ISchemaValidator<T = Record<string, never>> {
138
+ get preprocessors(): Map<
139
+ string,
140
+ (value: unknown) => unknown | Promise<unknown>
141
+ >;
142
+ addPreprocessor(
143
+ name: string,
144
+ preprocessor: (value: unknown) => unknown | Promise<unknown>
145
+ ): SchemaValidator<T>;
146
+
132
147
  addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
133
148
  name: keyof K,
134
149
  schema: L | Array<Schema<any>>
@@ -1152,3 +1152,214 @@ test('Validate schema - 2', async () => {
1152
1152
 
1153
1153
  expect(result).toHaveProperty('valid', true);
1154
1154
  });
1155
+
1156
+ test('Not required alternative schema alias', async () => {
1157
+ const validator = new SchemaValidator()
1158
+ .addSchemaType('Alternate1', {
1159
+ properties: {
1160
+ a1: 'string'
1161
+ }
1162
+ })
1163
+ .addSchemaType('Alternate2', {
1164
+ properties: {
1165
+ a2: 'string'
1166
+ }
1167
+ })
1168
+ .addSchemaType('Alternate', ['Alternate1', 'Alternate2']);
1169
+
1170
+ let result = await validator.validate(
1171
+ {
1172
+ type: 'alias',
1173
+ isRequired: false,
1174
+ schemaName: 'Alternate'
1175
+ },
1176
+ undefined
1177
+ );
1178
+
1179
+ expect(result).toHaveProperty('valid', true);
1180
+
1181
+ result = await validator.validate(
1182
+ {
1183
+ type: 'alias',
1184
+ isRequired: false,
1185
+ schemaName: 'Alternate'
1186
+ },
1187
+ {
1188
+ a2: 'something'
1189
+ }
1190
+ );
1191
+
1192
+ expect(result).toHaveProperty('valid', true);
1193
+
1194
+ result = await validator.validate(
1195
+ {
1196
+ type: 'alias',
1197
+ isRequired: false,
1198
+ schemaName: 'Alternate'
1199
+ },
1200
+ 'invalid'
1201
+ );
1202
+
1203
+ expect(result).toHaveProperty('valid', false);
1204
+ });
1205
+
1206
+ test('Preprocessors - 1', async () => {
1207
+ const validator = new SchemaValidator().addSchemaType('Date', {
1208
+ validators: [
1209
+ (value) =>
1210
+ value instanceof Date && !Number.isNaN(value)
1211
+ ? {
1212
+ valid: true
1213
+ }
1214
+ : {
1215
+ valid: false,
1216
+ errors: ['should be a valid Date object']
1217
+ }
1218
+ ]
1219
+ });
1220
+
1221
+ const schema: Schema<{ bornAt: Date }> = {
1222
+ type: 'object',
1223
+ properties: {
1224
+ bornAt: 'Date'
1225
+ },
1226
+ preprocessors: {
1227
+ bornAt: (value: unknown): Date => {
1228
+ const time = Date.parse(value.toString());
1229
+ if (Number.isNaN(time)) return undefined;
1230
+ return new Date(time);
1231
+ }
1232
+ }
1233
+ };
1234
+
1235
+ const result = await validator.validate(schema, {
1236
+ bornAt: new Date().toJSON()
1237
+ });
1238
+ expect(result).toHaveProperty('valid', true);
1239
+ });
1240
+
1241
+ test('Preprocessors - 2', async () => {
1242
+ const validator = new SchemaValidator().addSchemaType('Date', {
1243
+ validators: [
1244
+ (value) =>
1245
+ value instanceof Date && !Number.isNaN(value)
1246
+ ? {
1247
+ valid: true
1248
+ }
1249
+ : {
1250
+ valid: false,
1251
+ errors: ['should be a valid Date object']
1252
+ }
1253
+ ]
1254
+ });
1255
+
1256
+ let schema: Schema<{ bornAt: Date; diedAt?: Date }> = {
1257
+ type: 'object',
1258
+ properties: {
1259
+ bornAt: 'Date'
1260
+ },
1261
+ preprocessors: {
1262
+ bornAt: 'StringToDate'
1263
+ }
1264
+ };
1265
+
1266
+ validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1267
+ const time = Date.parse(value.toString());
1268
+ if (Number.isNaN(time)) return undefined;
1269
+ return new Date(time);
1270
+ });
1271
+
1272
+ expect(() =>
1273
+ validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1274
+ const time = Date.parse(value.toString());
1275
+ if (Number.isNaN(time)) return undefined;
1276
+ return new Date(time);
1277
+ })
1278
+ ).toThrow();
1279
+
1280
+ const result = await validator.validate(schema, {
1281
+ bornAt: new Date().toJSON()
1282
+ });
1283
+ expect(result).toHaveProperty('valid', true);
1284
+
1285
+ schema = {
1286
+ type: 'object',
1287
+ properties: {
1288
+ bornAt: 'Date',
1289
+ diedAt: 'Date'
1290
+ },
1291
+ preprocessors: {
1292
+ bornAt: 'StringToDate',
1293
+ diedAt: 'Unregistered'
1294
+ }
1295
+ };
1296
+ expect(async () => {
1297
+ await validator.validate(schema, {
1298
+ bornAt: new Date().toJSON()
1299
+ });
1300
+ }).rejects.toBeInstanceOf(Error);
1301
+ });
1302
+
1303
+ test('Preprocessors - 3', async () => {
1304
+ const validator = new SchemaValidator().addSchemaType('Date', {
1305
+ validators: [
1306
+ (value) =>
1307
+ value instanceof Date && !Number.isNaN(value)
1308
+ ? {
1309
+ valid: true
1310
+ }
1311
+ : {
1312
+ valid: false,
1313
+ errors: ['should be a valid Date object']
1314
+ }
1315
+ ]
1316
+ });
1317
+
1318
+ validator.addPreprocessor('StringToDate', (value: unknown): Date => {
1319
+ const time = Date.parse(value.toString());
1320
+ if (Number.isNaN(time)) return undefined;
1321
+ return new Date(time);
1322
+ });
1323
+
1324
+ let obj = [new Date().toJSON()];
1325
+
1326
+ let result = await validator.validate(
1327
+ {
1328
+ preprocessor: 'StringToDate',
1329
+ type: 'array',
1330
+ ofType: 'Date'
1331
+ },
1332
+ obj
1333
+ );
1334
+ expect(result).toHaveProperty('valid', true);
1335
+
1336
+ obj = [new Date().toJSON()];
1337
+ result = await validator.validate(
1338
+ {
1339
+ preprocessor: (value: unknown): Date => {
1340
+ const time = Date.parse(value.toString());
1341
+ if (Number.isNaN(time)) return undefined;
1342
+ return new Date(time);
1343
+ },
1344
+ type: 'array',
1345
+ ofType: 'Date'
1346
+ },
1347
+ obj
1348
+ );
1349
+ expect(result).toHaveProperty('valid', true);
1350
+
1351
+ obj = ['sdfsdf12', new Date().toJSON()];
1352
+ result = await validator.validate(
1353
+ {
1354
+ preprocessor: (value: unknown): Date => {
1355
+ const time = Date.parse(value.toString());
1356
+ if (Number.isNaN(time)) return undefined;
1357
+ return new Date(time);
1358
+ },
1359
+ type: 'array',
1360
+ ofType: 'Date'
1361
+ },
1362
+ obj
1363
+ );
1364
+ expect(result).toHaveProperty('valid', false);
1365
+ });
@@ -70,6 +70,24 @@ export default class SchemaValidator<T = Record<string, never>>
70
70
  {
71
71
  private _schemasMap = new Map<string, Schema<any>>();
72
72
  private _schemasCache: { [K in keyof T]: ISchemaActions<T, K> } = null;
73
+ private _preprocessorsMap = new Map<
74
+ string,
75
+ (value: unknown) => unknown | Promise<unknown>
76
+ >();
77
+
78
+ public get preprocessors(): Map<string, (value: unknown) => unknown> {
79
+ return this._preprocessorsMap;
80
+ }
81
+
82
+ public addPreprocessor(
83
+ name: string,
84
+ preprocessor: (value: unknown) => unknown | Promise<unknown>
85
+ ): SchemaValidator<T> {
86
+ if (this._preprocessorsMap.has(name))
87
+ throw new Error(`Preprocessor '${name}' is already registered`);
88
+ this._preprocessorsMap.set(name, preprocessor);
89
+ return this;
90
+ }
73
91
 
74
92
  public addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
75
93
  name: keyof K,
@@ -259,10 +277,17 @@ export default class SchemaValidator<T = Record<string, never>>
259
277
  } else if (schema.type === 'alias') {
260
278
  const alias = this.schemas[schema.schemaName]
261
279
  .schema as Schema<any>;
262
- if (Array.isArray(alias))
263
- throw new Error(
264
- 'it is impossible to use alternative schema alias as "type" field'
265
- );
280
+ if (Array.isArray(alias)) {
281
+ if (
282
+ (!schema.isRequired && typeof obj === 'undefined') ||
283
+ (schema.isNullable && obj === null)
284
+ ) {
285
+ return {
286
+ valid: true
287
+ };
288
+ }
289
+ return await this.validate(alias, obj);
290
+ }
266
291
  if (typeof alias !== 'object')
267
292
  throw new Error(
268
293
  'it is only possible to use a full schema schema definition as alias'
@@ -24,6 +24,19 @@ export const validateArray = async (
24
24
  errors: ['expected type array']
25
25
  };
26
26
 
27
+ if (schema.preprocessor) {
28
+ const preprocessor =
29
+ typeof schema.preprocessor === 'function'
30
+ ? schema.preprocessor
31
+ : validator.preprocessors.get(schema.preprocessor);
32
+ if (typeof preprocessor !== 'function') {
33
+ throw new Error(`unknown preprocessor '${schema.preprocessor}'`);
34
+ }
35
+ for (let i = 0; i < obj.length; i++) {
36
+ obj[i] = await Promise.resolve(preprocessor(obj[i]));
37
+ }
38
+ }
39
+
27
40
  if (typeof schema.minLength === 'number' && obj.length < schema.minLength) {
28
41
  return {
29
42
  valid: false,
@@ -28,6 +28,30 @@ export const validateObject = async (
28
28
  };
29
29
  }
30
30
 
31
+ if (typeof schema.preprocessors === 'object' && schema.preprocessors) {
32
+ await Promise.all(
33
+ Object.keys(schema.preprocessors).map(async (key: string) => {
34
+ if (typeof schema.preprocessors[key] === 'function') {
35
+ obj[key] = await Promise.resolve(
36
+ (schema.preprocessors[key] as (unknown) => unknown)(
37
+ obj[key]
38
+ )
39
+ );
40
+ } else {
41
+ const preprocessor = validator.preprocessors.get(
42
+ schema.preprocessors[key] as string
43
+ );
44
+ if (typeof preprocessor !== 'function') {
45
+ throw new Error(
46
+ `preprocessor '${schema.preprocessors[key]}' is unknown`
47
+ );
48
+ }
49
+ obj[key] = await Promise.resolve(preprocessor(obj[key]));
50
+ }
51
+ })
52
+ );
53
+ }
54
+
31
55
  if (typeof schema.properties === 'object' && schema.properties) {
32
56
  const errors = (
33
57
  await Promise.all(