@cleverbrush/schema 0.0.5 → 0.0.8
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 +8 -4
- package/dist/schemaValidator.d.ts +1 -1
- package/dist/schemaValidator.js +29 -2
- package/package.json +2 -2
- package/src/index.ts +11 -4
- package/src/schemaValidator.test.ts +277 -10
- package/src/schemaValidator.ts +42 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import SchemaValidator from './schemaValidator';
|
|
2
|
-
export declare type DefaultSchemaType = 'object' | 'function' | 'number' | 'string' | 'array';
|
|
2
|
+
export declare type DefaultSchemaType = 'object' | 'function' | 'number' | 'string' | 'array' | 'alias';
|
|
3
3
|
export declare type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];
|
|
4
4
|
export declare type DefaultPropertyDefinition = {
|
|
5
5
|
isRequired: boolean;
|
|
@@ -24,6 +24,10 @@ export declare type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'>
|
|
|
24
24
|
[S in keyof T]: Schema<PropType<T, S>>;
|
|
25
25
|
}>;
|
|
26
26
|
};
|
|
27
|
+
export declare type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
|
|
28
|
+
type: 'alias';
|
|
29
|
+
schemaName: string;
|
|
30
|
+
};
|
|
27
31
|
export declare type ObjectSchemaDefinitionParam<T> = Omit<ObjectSchemaDefinition<T>, 'type'>;
|
|
28
32
|
export declare type ParamsValidators<TFunc extends (...args: any) => any> = {
|
|
29
33
|
[S in keyof Parameters<TFunc>]: SingleSchema<Parameters<TFunc>[S]>;
|
|
@@ -53,7 +57,7 @@ export declare type ArraySchemaDefinition<TObj> = Omit<SchemaDefintion<TObj>, 't
|
|
|
53
57
|
minLength?: number;
|
|
54
58
|
maxLength?: number;
|
|
55
59
|
};
|
|
56
|
-
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>;
|
|
60
|
+
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>;
|
|
57
61
|
export declare type SingleSchema<TObj = Record<string, never>> = number | string | DefaultSchemaType | CompositeSchema<TObj>;
|
|
58
62
|
export declare type Schema<TObj> = SingleSchema<TObj> | Array<SingleSchema<TObj>>;
|
|
59
63
|
export interface ISchemaActions<K, T extends keyof K> {
|
|
@@ -66,8 +70,8 @@ export interface ISchemasProvider<T = Record<string, never>> {
|
|
|
66
70
|
};
|
|
67
71
|
}
|
|
68
72
|
export interface ISchemaValidator<T = Record<string, never>> {
|
|
69
|
-
addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
|
|
70
|
-
[key in keyof K]:
|
|
73
|
+
addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
|
|
74
|
+
[key in keyof K]: typeof schema;
|
|
71
75
|
}>;
|
|
72
76
|
validate(schema: keyof T | DefaultSchemaType | Schema<any>, obj: any): Promise<ValidationResult>;
|
|
73
77
|
}
|
|
@@ -2,7 +2,7 @@ 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
|
-
addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L): SchemaValidator<T & {
|
|
5
|
+
addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(name: keyof K, schema: L | Array<Schema<any>>): SchemaValidator<T & {
|
|
6
6
|
[key in keyof K]: L;
|
|
7
7
|
}>;
|
|
8
8
|
get schemas(): {
|
package/dist/schemaValidator.js
CHANGED
|
@@ -40,13 +40,18 @@ class SchemaValidator {
|
|
|
40
40
|
addSchemaType(name, schema) {
|
|
41
41
|
if (typeof name !== 'string' || !name)
|
|
42
42
|
throw new Error('Name is required');
|
|
43
|
-
if (typeof schema !== 'object')
|
|
44
|
-
throw new Error('Object is required');
|
|
45
43
|
if (isDefaultType(name.toString()))
|
|
46
44
|
throw new Error(`You can't add a schema named "${name}" because it's a name of a default schema, please consider another name to be used`);
|
|
47
45
|
if (this._schemasMap.has(name.toString())) {
|
|
48
46
|
throw new Error(`Schema "${name}" already exists`);
|
|
49
47
|
}
|
|
48
|
+
if (Array.isArray(schema)) {
|
|
49
|
+
this._schemasMap.set(name.toString(), schema);
|
|
50
|
+
this._schemasCache = null;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
if (typeof schema !== 'object')
|
|
54
|
+
throw new Error('Array or Object is required');
|
|
50
55
|
this._schemasMap.set(name.toString(), {
|
|
51
56
|
...schema,
|
|
52
57
|
type: 'object'
|
|
@@ -90,6 +95,11 @@ class SchemaValidator {
|
|
|
90
95
|
throw new Error('not implemented');
|
|
91
96
|
}
|
|
92
97
|
async checkValidators(schema, value) {
|
|
98
|
+
if (!schema.isRequired && typeof value === 'undefined') {
|
|
99
|
+
return {
|
|
100
|
+
valid: true
|
|
101
|
+
};
|
|
102
|
+
}
|
|
93
103
|
if (Array.isArray(schema.validators)) {
|
|
94
104
|
const validatorsResults = await Promise.allSettled(schema.validators.map((v) => Promise.resolve(v(value))));
|
|
95
105
|
const rejections = validatorsResults
|
|
@@ -123,6 +133,10 @@ class SchemaValidator {
|
|
|
123
133
|
return await this.validateDefaultType(schema, obj);
|
|
124
134
|
}
|
|
125
135
|
else if (typeof this.schemas[schema] !== 'undefined') {
|
|
136
|
+
if (Array.isArray(this.schemas[schema].schema)) {
|
|
137
|
+
return this.validate(this.schemas[schema]
|
|
138
|
+
.schema, obj);
|
|
139
|
+
}
|
|
126
140
|
const objSchema = (0, deep_1.deepExtend)(defaultSchemas.object, this.schemas[schema].schema);
|
|
127
141
|
const res = await (0, validateObject_1.validateObject)(obj, objSchema, this);
|
|
128
142
|
if (!res.valid)
|
|
@@ -157,6 +171,19 @@ class SchemaValidator {
|
|
|
157
171
|
if (isDefaultType(schema.type)) {
|
|
158
172
|
return await this.validateDefaultType(schema.type, obj, schema);
|
|
159
173
|
}
|
|
174
|
+
else if (schema.type === 'alias') {
|
|
175
|
+
const alias = this.schemas[schema.schemaName]
|
|
176
|
+
.schema;
|
|
177
|
+
if (Array.isArray(alias))
|
|
178
|
+
throw new Error('it is impossible to use alternative schema alias as "type" field');
|
|
179
|
+
if (typeof alias !== 'object')
|
|
180
|
+
throw new Error('it is only possible to use a full schema schema definition as alias');
|
|
181
|
+
const schemaToMerge = { ...schema };
|
|
182
|
+
delete schemaToMerge.type;
|
|
183
|
+
delete schemaToMerge.schemaName;
|
|
184
|
+
const finalSchema = (0, deep_1.deepExtend)(alias, schemaToMerge);
|
|
185
|
+
return await this.validate(finalSchema, obj);
|
|
186
|
+
}
|
|
160
187
|
else if (schema.type === 'object') {
|
|
161
188
|
const preliminaryResult = await (0, validateObject_1.validateObject)(obj, schema, this);
|
|
162
189
|
if (!preliminaryResult.valid)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cleverbrush/schema",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.8",
|
|
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.
|
|
22
|
+
"@cleverbrush/deep": "0.0.8"
|
|
23
23
|
},
|
|
24
24
|
"types": "./dist/index.d.ts"
|
|
25
25
|
}
|
package/src/index.ts
CHANGED
|
@@ -5,7 +5,8 @@ export type DefaultSchemaType =
|
|
|
5
5
|
| 'function'
|
|
6
6
|
| 'number'
|
|
7
7
|
| 'string'
|
|
8
|
-
| 'array'
|
|
8
|
+
| 'array'
|
|
9
|
+
| 'alias';
|
|
9
10
|
|
|
10
11
|
export type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];
|
|
11
12
|
|
|
@@ -40,6 +41,11 @@ export type ObjectSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
|
|
|
40
41
|
}>;
|
|
41
42
|
};
|
|
42
43
|
|
|
44
|
+
export type AliasSchemaDefinition<T> = Omit<SchemaDefintion<T>, 'type'> & {
|
|
45
|
+
type: 'alias';
|
|
46
|
+
schemaName: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
43
49
|
export type ObjectSchemaDefinitionParam<T> = Omit<
|
|
44
50
|
ObjectSchemaDefinition<T>,
|
|
45
51
|
'type'
|
|
@@ -100,7 +106,8 @@ export type CompositeSchema<TObj> = TObj extends (...args: any) => any
|
|
|
100
106
|
| NumberSchemaDefinition<TObj>
|
|
101
107
|
| StringSchemaDefinition<TObj>
|
|
102
108
|
| ArraySchemaDefinition<TObj>
|
|
103
|
-
| ObjectSchemaDefinition<TObj
|
|
109
|
+
| ObjectSchemaDefinition<TObj>
|
|
110
|
+
| AliasSchemaDefinition<TObj>;
|
|
104
111
|
|
|
105
112
|
export type SingleSchema<TObj = Record<string, never>> =
|
|
106
113
|
| number
|
|
@@ -124,8 +131,8 @@ export interface ISchemasProvider<T = Record<string, never>> {
|
|
|
124
131
|
export interface ISchemaValidator<T = Record<string, never>> {
|
|
125
132
|
addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
|
|
126
133
|
name: keyof K,
|
|
127
|
-
schema: L
|
|
128
|
-
): SchemaValidator<T & { [key in keyof K]:
|
|
134
|
+
schema: L | Array<Schema<any>>
|
|
135
|
+
): SchemaValidator<T & { [key in keyof K]: typeof schema }>;
|
|
129
136
|
|
|
130
137
|
validate(
|
|
131
138
|
schema: keyof T | DefaultSchemaType | Schema<any>,
|
|
@@ -97,39 +97,39 @@ test('Schemas property is updated after adding a new schema', () => {
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
test('Trows when trying to add a schema with name = "number"', () => {
|
|
100
|
-
|
|
100
|
+
const validator = new SchemaValidator();
|
|
101
101
|
expect(() => validator.addSchemaType('number', {})).toThrow();
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
test('Trows when trying to add a schema with name = "array"', () => {
|
|
105
|
-
|
|
105
|
+
const validator = new SchemaValidator();
|
|
106
106
|
expect(() => validator.addSchemaType('array', {})).toThrow();
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
test('Trows when trying to add a schema with name = "date"', () => {
|
|
110
|
-
|
|
110
|
+
const validator = new SchemaValidator();
|
|
111
111
|
expect(() => validator.addSchemaType('date', {})).toThrow();
|
|
112
112
|
});
|
|
113
113
|
|
|
114
114
|
test('Trows when trying to add a schema with name = "string"', () => {
|
|
115
|
-
|
|
115
|
+
const validator = new SchemaValidator();
|
|
116
116
|
expect(() => validator.addSchemaType('string', {})).toThrow();
|
|
117
117
|
});
|
|
118
118
|
|
|
119
119
|
test('Throws if schema name is empty', () => {
|
|
120
|
-
|
|
120
|
+
const validator = new SchemaValidator();
|
|
121
121
|
expect(() => validator.addSchemaType('', {})).toThrow();
|
|
122
122
|
});
|
|
123
123
|
|
|
124
124
|
test('Throws if schema name is not a string', () => {
|
|
125
|
-
|
|
125
|
+
const validator = new SchemaValidator();
|
|
126
126
|
expect(() =>
|
|
127
127
|
validator.addSchemaType(new Date() as any as string, {})
|
|
128
128
|
).toThrow();
|
|
129
129
|
});
|
|
130
130
|
|
|
131
131
|
test('Throws if schema is not an object', () => {
|
|
132
|
-
|
|
132
|
+
const validator = new SchemaValidator();
|
|
133
133
|
expect(() =>
|
|
134
134
|
validator.addSchemaType(
|
|
135
135
|
'string',
|
|
@@ -138,6 +138,33 @@ test('Throws if schema is not an object', () => {
|
|
|
138
138
|
).toThrow();
|
|
139
139
|
});
|
|
140
140
|
|
|
141
|
+
test('Array as schema type', async () => {
|
|
142
|
+
const validator = new SchemaValidator()
|
|
143
|
+
.addSchemaType('name', {
|
|
144
|
+
properties: {
|
|
145
|
+
name: 'string'
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
.addSchemaType('number_or_string', ['number', 'string', 'name']);
|
|
149
|
+
|
|
150
|
+
let result = await validator.schemas.number_or_string.validate(123);
|
|
151
|
+
|
|
152
|
+
expect(result).toHaveProperty('valid', true);
|
|
153
|
+
|
|
154
|
+
result = await validator.schemas.number_or_string.validate('some string');
|
|
155
|
+
|
|
156
|
+
expect(result).toHaveProperty('valid', true);
|
|
157
|
+
|
|
158
|
+
result = await validator.schemas.number_or_string.validate({});
|
|
159
|
+
|
|
160
|
+
expect(result).toHaveProperty('valid', false);
|
|
161
|
+
|
|
162
|
+
result = await validator.schemas.number_or_string.validate({
|
|
163
|
+
name: 'some name'
|
|
164
|
+
});
|
|
165
|
+
expect(result).toHaveProperty('valid', true);
|
|
166
|
+
});
|
|
167
|
+
|
|
141
168
|
test('Validate - no schema', async () => {
|
|
142
169
|
const validator = new SchemaValidator();
|
|
143
170
|
const cth = jest.fn();
|
|
@@ -741,7 +768,7 @@ test('Validate - array - ofType - 1', async () => {
|
|
|
741
768
|
});
|
|
742
769
|
|
|
743
770
|
test('Validate - object - 1', async () => {
|
|
744
|
-
|
|
771
|
+
const validator = new SchemaValidator().addSchemaType(
|
|
745
772
|
'user',
|
|
746
773
|
getUserSchema()
|
|
747
774
|
);
|
|
@@ -824,7 +851,7 @@ test('Validate - object - 1', async () => {
|
|
|
824
851
|
});
|
|
825
852
|
|
|
826
853
|
test('Validate - object - 2', async () => {
|
|
827
|
-
|
|
854
|
+
const validator = new SchemaValidator().addSchemaType(
|
|
828
855
|
'user',
|
|
829
856
|
deepExtend(getUserSchema(), {
|
|
830
857
|
validators: [
|
|
@@ -853,7 +880,7 @@ test('Validate - object - 2', async () => {
|
|
|
853
880
|
aliases: []
|
|
854
881
|
};
|
|
855
882
|
|
|
856
|
-
|
|
883
|
+
const result = await validator.validate('user', user);
|
|
857
884
|
expect(result).toHaveProperty('valid', false);
|
|
858
885
|
});
|
|
859
886
|
|
|
@@ -885,3 +912,243 @@ test('Validate - one of - 1', async () => {
|
|
|
885
912
|
);
|
|
886
913
|
expect(result).toHaveProperty('valid', true);
|
|
887
914
|
});
|
|
915
|
+
|
|
916
|
+
test('Validate schema - 1', async () => {
|
|
917
|
+
const authorsReportSpecificationSchema = {
|
|
918
|
+
properties: {
|
|
919
|
+
type: {
|
|
920
|
+
type: 'string',
|
|
921
|
+
equals: 'author'
|
|
922
|
+
},
|
|
923
|
+
start: 'Date',
|
|
924
|
+
end: 'Date',
|
|
925
|
+
selectionStart: 'Date',
|
|
926
|
+
selectionEnd: 'Date',
|
|
927
|
+
granularity: ['day', 'week', 'month'],
|
|
928
|
+
metrics: {
|
|
929
|
+
type: 'array',
|
|
930
|
+
ofType: [
|
|
931
|
+
'articlesPublished',
|
|
932
|
+
'searchReferrers',
|
|
933
|
+
'socialReferrers',
|
|
934
|
+
'views',
|
|
935
|
+
'visitors',
|
|
936
|
+
'newVisitors'
|
|
937
|
+
]
|
|
938
|
+
},
|
|
939
|
+
filters: {
|
|
940
|
+
type: 'object',
|
|
941
|
+
properties: {
|
|
942
|
+
author: 'AuthorFilter'
|
|
943
|
+
// publication: 'PublicationFilter',
|
|
944
|
+
// article: 'ArticleFilter'
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
},
|
|
948
|
+
validators: [
|
|
949
|
+
(value) =>
|
|
950
|
+
value.start <= value.end &&
|
|
951
|
+
value.selectionStart <= value.selectionEnd &&
|
|
952
|
+
value.selectionStart >= value.start &&
|
|
953
|
+
value.selectionStart <= value.end &&
|
|
954
|
+
value.selectionEnd >= value.start &&
|
|
955
|
+
value.selectionEnd <= value.end
|
|
956
|
+
? {
|
|
957
|
+
valid: true
|
|
958
|
+
}
|
|
959
|
+
: {
|
|
960
|
+
valid: false,
|
|
961
|
+
errors: [
|
|
962
|
+
'selectionStart <=> selectionEnd should be inside the start <=> end interval'
|
|
963
|
+
]
|
|
964
|
+
}
|
|
965
|
+
]
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
const validator = new SchemaValidator()
|
|
969
|
+
.addSchemaType('Date', {
|
|
970
|
+
validators: [
|
|
971
|
+
(value) =>
|
|
972
|
+
value instanceof Date && !Number.isNaN(value)
|
|
973
|
+
? {
|
|
974
|
+
valid: true
|
|
975
|
+
}
|
|
976
|
+
: {
|
|
977
|
+
valid: false,
|
|
978
|
+
errors: ['should be a valid Date object']
|
|
979
|
+
}
|
|
980
|
+
]
|
|
981
|
+
})
|
|
982
|
+
.addSchemaType('EqualsFilterCondition', {
|
|
983
|
+
properties: {
|
|
984
|
+
operation: {
|
|
985
|
+
type: 'string',
|
|
986
|
+
equals: 'equals'
|
|
987
|
+
},
|
|
988
|
+
value: ['object', 'string', 'number']
|
|
989
|
+
}
|
|
990
|
+
})
|
|
991
|
+
.addSchemaType('GreaterThanFilterCondition', {
|
|
992
|
+
properties: {
|
|
993
|
+
operation: {
|
|
994
|
+
type: 'string',
|
|
995
|
+
equals: 'greater_than'
|
|
996
|
+
},
|
|
997
|
+
value: 'number'
|
|
998
|
+
}
|
|
999
|
+
})
|
|
1000
|
+
.addSchemaType('LessThanFilterCondition', {
|
|
1001
|
+
properties: {
|
|
1002
|
+
operation: {
|
|
1003
|
+
type: 'string',
|
|
1004
|
+
equals: 'less_than'
|
|
1005
|
+
},
|
|
1006
|
+
value: 'number'
|
|
1007
|
+
}
|
|
1008
|
+
})
|
|
1009
|
+
.addSchemaType('ContainsFilterCondition', {
|
|
1010
|
+
properties: {
|
|
1011
|
+
operation: {
|
|
1012
|
+
type: 'string',
|
|
1013
|
+
equals: 'contains'
|
|
1014
|
+
},
|
|
1015
|
+
value: [
|
|
1016
|
+
'string',
|
|
1017
|
+
'number',
|
|
1018
|
+
{
|
|
1019
|
+
type: 'array',
|
|
1020
|
+
ofType: ['string', 'number']
|
|
1021
|
+
}
|
|
1022
|
+
]
|
|
1023
|
+
}
|
|
1024
|
+
})
|
|
1025
|
+
.addSchemaType('LikeFilterCondition', {
|
|
1026
|
+
properties: {
|
|
1027
|
+
operation: 'like',
|
|
1028
|
+
value: 'string'
|
|
1029
|
+
}
|
|
1030
|
+
})
|
|
1031
|
+
.addSchemaType('BetweenFilterCondition', {
|
|
1032
|
+
properties: {
|
|
1033
|
+
operation: 'between',
|
|
1034
|
+
from: 'number',
|
|
1035
|
+
to: 'number'
|
|
1036
|
+
},
|
|
1037
|
+
validators: [
|
|
1038
|
+
(value) =>
|
|
1039
|
+
value.from <= value.to
|
|
1040
|
+
? { valid: true }
|
|
1041
|
+
: { valid: false, error: ['from must be <= to'] }
|
|
1042
|
+
]
|
|
1043
|
+
})
|
|
1044
|
+
.addSchemaType('StringFilterCondition', [
|
|
1045
|
+
'EqualsFilterCondition',
|
|
1046
|
+
'LikeFilterCondition'
|
|
1047
|
+
])
|
|
1048
|
+
.addSchemaType('AuthorFilter', {
|
|
1049
|
+
properties: {
|
|
1050
|
+
fullName: 'StringFilterCondition'
|
|
1051
|
+
}
|
|
1052
|
+
})
|
|
1053
|
+
.addSchemaType(
|
|
1054
|
+
'AuthorsReportSpecification',
|
|
1055
|
+
authorsReportSpecificationSchema as ObjectSchemaDefinitionParam<any>
|
|
1056
|
+
);
|
|
1057
|
+
|
|
1058
|
+
/**
|
|
1059
|
+
* @type {import('smg-iq/editorial-analytics.reports').AuthorsReportSpecification}
|
|
1060
|
+
*/
|
|
1061
|
+
let reportSpec = {
|
|
1062
|
+
type: 'author',
|
|
1063
|
+
start: new Date(2021, 0, 1),
|
|
1064
|
+
end: new Date(),
|
|
1065
|
+
selectionStart: new Date(2022, 0, 1),
|
|
1066
|
+
selectionEnd: new Date(2022, 2, 1),
|
|
1067
|
+
granularity: 'day',
|
|
1068
|
+
metrics: [
|
|
1069
|
+
'articlesPublished',
|
|
1070
|
+
'searchReferrers',
|
|
1071
|
+
'socialReferrers',
|
|
1072
|
+
'views',
|
|
1073
|
+
'visitors',
|
|
1074
|
+
'newVisitors'
|
|
1075
|
+
],
|
|
1076
|
+
filters: {
|
|
1077
|
+
author: {
|
|
1078
|
+
fullName: {
|
|
1079
|
+
operation: 'between',
|
|
1080
|
+
value: 'some name'
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
let result = await validator.schemas.AuthorsReportSpecification.validate(
|
|
1087
|
+
reportSpec
|
|
1088
|
+
);
|
|
1089
|
+
|
|
1090
|
+
expect(result).toHaveProperty('valid', false);
|
|
1091
|
+
|
|
1092
|
+
reportSpec = {
|
|
1093
|
+
type: 'author',
|
|
1094
|
+
start: new Date(2021, 0, 1),
|
|
1095
|
+
end: new Date(),
|
|
1096
|
+
selectionStart: new Date(2022, 0, 1),
|
|
1097
|
+
selectionEnd: new Date(2022, 2, 1),
|
|
1098
|
+
granularity: 'day',
|
|
1099
|
+
metrics: [
|
|
1100
|
+
'articlesPublished',
|
|
1101
|
+
'searchReferrers',
|
|
1102
|
+
'socialReferrers',
|
|
1103
|
+
'views',
|
|
1104
|
+
'visitors',
|
|
1105
|
+
'newVisitors'
|
|
1106
|
+
],
|
|
1107
|
+
filters: {
|
|
1108
|
+
author: {
|
|
1109
|
+
fullName: {
|
|
1110
|
+
operation: 'equals',
|
|
1111
|
+
value: 'some name'
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
|
|
1117
|
+
result = await validator.schemas.AuthorsReportSpecification.validate(
|
|
1118
|
+
reportSpec
|
|
1119
|
+
);
|
|
1120
|
+
|
|
1121
|
+
expect(result).toHaveProperty('valid', true);
|
|
1122
|
+
});
|
|
1123
|
+
|
|
1124
|
+
test('Validate schema - 2', async () => {
|
|
1125
|
+
const validator = new SchemaValidator().addSchemaType('Date', {
|
|
1126
|
+
validators: [
|
|
1127
|
+
(value) =>
|
|
1128
|
+
value instanceof Date && !Number.isNaN(value)
|
|
1129
|
+
? {
|
|
1130
|
+
valid: true
|
|
1131
|
+
}
|
|
1132
|
+
: {
|
|
1133
|
+
valid: false,
|
|
1134
|
+
errors: ['should be a valid Date object']
|
|
1135
|
+
}
|
|
1136
|
+
]
|
|
1137
|
+
});
|
|
1138
|
+
|
|
1139
|
+
const result = await validator.validate(
|
|
1140
|
+
{
|
|
1141
|
+
type: 'object',
|
|
1142
|
+
properties: {
|
|
1143
|
+
date: {
|
|
1144
|
+
type: 'alias',
|
|
1145
|
+
schemaName: 'Date',
|
|
1146
|
+
isRequired: false
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
{}
|
|
1151
|
+
);
|
|
1152
|
+
|
|
1153
|
+
expect(result).toHaveProperty('valid', true);
|
|
1154
|
+
});
|
package/src/schemaValidator.ts
CHANGED
|
@@ -73,11 +73,11 @@ export default class SchemaValidator<T = Record<string, never>>
|
|
|
73
73
|
|
|
74
74
|
public addSchemaType<K, L extends ObjectSchemaDefinitionParam<M>, M = any>(
|
|
75
75
|
name: keyof K,
|
|
76
|
-
schema: L
|
|
76
|
+
schema: L | Array<Schema<any>>
|
|
77
77
|
): SchemaValidator<T & { [key in keyof K]: L }> {
|
|
78
78
|
if (typeof name !== 'string' || !name)
|
|
79
79
|
throw new Error('Name is required');
|
|
80
|
-
|
|
80
|
+
|
|
81
81
|
if (isDefaultType(name.toString()))
|
|
82
82
|
throw new Error(
|
|
83
83
|
`You can't add a schema named "${name}" because it's a name of a default schema, please consider another name to be used`
|
|
@@ -86,6 +86,14 @@ export default class SchemaValidator<T = Record<string, never>>
|
|
|
86
86
|
throw new Error(`Schema "${name}" already exists`);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
if (Array.isArray(schema)) {
|
|
90
|
+
this._schemasMap.set(name.toString(), schema as Schema<any>);
|
|
91
|
+
this._schemasCache = null;
|
|
92
|
+
return this as any as SchemaValidator<T & { [key in keyof K]: L }>;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (typeof schema !== 'object')
|
|
96
|
+
throw new Error('Array or Object is required');
|
|
89
97
|
this._schemasMap.set(name.toString(), {
|
|
90
98
|
...schema,
|
|
91
99
|
type: 'object'
|
|
@@ -148,6 +156,11 @@ export default class SchemaValidator<T = Record<string, never>>
|
|
|
148
156
|
schema: CompositeSchema<Record<string, never>>,
|
|
149
157
|
value: any
|
|
150
158
|
): Promise<ValidationResult> {
|
|
159
|
+
if (!schema.isRequired && typeof value === 'undefined') {
|
|
160
|
+
return {
|
|
161
|
+
valid: true
|
|
162
|
+
};
|
|
163
|
+
}
|
|
151
164
|
if (Array.isArray(schema.validators)) {
|
|
152
165
|
const validatorsResults = await Promise.allSettled(
|
|
153
166
|
schema.validators.map((v) => Promise.resolve(v(value)))
|
|
@@ -194,6 +207,13 @@ export default class SchemaValidator<T = Record<string, never>>
|
|
|
194
207
|
obj
|
|
195
208
|
);
|
|
196
209
|
} else if (typeof this.schemas[schema as keyof T] !== 'undefined') {
|
|
210
|
+
if (Array.isArray(this.schemas[schema as keyof T].schema)) {
|
|
211
|
+
return this.validate(
|
|
212
|
+
this.schemas[schema as keyof T]
|
|
213
|
+
.schema as any as Schema<any>,
|
|
214
|
+
obj
|
|
215
|
+
);
|
|
216
|
+
}
|
|
197
217
|
const objSchema = deepExtend(
|
|
198
218
|
defaultSchemas.object,
|
|
199
219
|
this.schemas[schema as keyof T].schema
|
|
@@ -236,6 +256,26 @@ export default class SchemaValidator<T = Record<string, never>>
|
|
|
236
256
|
obj,
|
|
237
257
|
schema
|
|
238
258
|
);
|
|
259
|
+
} else if (schema.type === 'alias') {
|
|
260
|
+
const alias = this.schemas[schema.schemaName]
|
|
261
|
+
.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
|
+
);
|
|
266
|
+
if (typeof alias !== 'object')
|
|
267
|
+
throw new Error(
|
|
268
|
+
'it is only possible to use a full schema schema definition as alias'
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const schemaToMerge = { ...schema };
|
|
272
|
+
delete schemaToMerge.type;
|
|
273
|
+
delete schemaToMerge.schemaName;
|
|
274
|
+
const finalSchema: Schema<any> = deepExtend(
|
|
275
|
+
alias,
|
|
276
|
+
schemaToMerge
|
|
277
|
+
);
|
|
278
|
+
return await this.validate(finalSchema, obj);
|
|
239
279
|
} else if (schema.type === 'object') {
|
|
240
280
|
const preliminaryResult = await validateObject(
|
|
241
281
|
obj,
|