@oscarpalmer/jhunal 0.10.0 → 0.12.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/constants.js +20 -6
- package/dist/index.js +3 -2
- package/dist/is.js +4 -4
- package/dist/jhunal.full.js +136 -118
- package/dist/models.js +8 -0
- package/dist/schematic.js +13 -12
- package/dist/validation/property.validation.js +96 -0
- package/dist/validation/value.validation.js +28 -30
- package/package.json +1 -1
- package/src/constants.ts +29 -6
- package/src/index.ts +2 -2
- package/src/is.ts +3 -3
- package/src/models.ts +72 -41
- package/src/schematic.ts +25 -15
- package/src/validation/property.validation.ts +190 -0
- package/src/validation/value.validation.ts +45 -53
- package/types/constants.d.ts +14 -3
- package/types/index.d.ts +2 -2
- package/types/is.d.ts +1 -1
- package/types/models.d.ts +26 -20
- package/types/schematic.d.ts +2 -3
- package/types/validation/property.validation.d.ts +3 -0
- package/types/validation/value.validation.d.ts +2 -3
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/array/compact.js +0 -12
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/is.js +0 -20
- package/dist/node_modules/@oscarpalmer/atoms/dist/internal/string.js +0 -24
- package/dist/node_modules/@oscarpalmer/atoms/dist/value/smush.js +0 -36
- package/dist/validation/schema.validation.js +0 -92
- package/src/validation/schema.validation.ts +0 -192
- package/types/validation/schema.validation.d.ts +0 -2
package/src/constants.ts
CHANGED
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {ValueName} from './models';
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const ERROR_NAME = 'SchematicError';
|
|
4
4
|
|
|
5
5
|
export const EXPRESSION_INDEX = /\.\d+$/;
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const EXPRESSION_KEY_PREFIX = /\.\w+$/;
|
|
8
|
+
|
|
9
|
+
export const EXPRESSION_KEY_VALUE = /^.*\.(\w+)$/;
|
|
10
|
+
|
|
11
|
+
export const EXPRESSION_PROPERTY = /(^|\.)\$(required|type|validators)(\.|$)/;
|
|
12
|
+
|
|
13
|
+
export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
|
|
14
|
+
|
|
15
|
+
export const MESSAGE_SCHEMA_INVALID_EMPTY = 'Schema must have at least one property';
|
|
16
|
+
|
|
17
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED = "'<>.$required' property must be a boolean";
|
|
18
|
+
|
|
19
|
+
export const MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE = "'<>' property must be of a valid type";
|
|
20
|
+
|
|
21
|
+
export const MESSAGE_SCHEMA_INVALID_TYPE = 'Schema must be an object';
|
|
22
|
+
|
|
23
|
+
export const MESSAGE_VALIDATOR_INVALID_KEY = "Validator '<>' does not exist";
|
|
24
|
+
|
|
25
|
+
export const MESSAGE_VALIDATOR_INVALID_TYPE = 'Validators must be an object';
|
|
26
|
+
|
|
27
|
+
export const MESSAGE_VALIDATOR_INVALID_VALUE =
|
|
28
|
+
"Validator '<>' must be a function or an array of functions";
|
|
8
29
|
|
|
9
30
|
export const PROPERTY_REQUIRED = '$required';
|
|
10
31
|
|
|
@@ -14,20 +35,22 @@ export const PROPERTY_VALIDATORS = '$validators';
|
|
|
14
35
|
|
|
15
36
|
export const SCHEMATIC_NAME = '$schematic';
|
|
16
37
|
|
|
38
|
+
export const TEMPLATE_PATTERN = '<>';
|
|
39
|
+
|
|
17
40
|
export const TYPE_OBJECT = 'object';
|
|
18
41
|
|
|
19
42
|
export const TYPE_UNDEFINED = 'undefined';
|
|
20
43
|
|
|
21
|
-
export const
|
|
44
|
+
export const VALIDATABLE_TYPES = new Set<ValueName>([
|
|
22
45
|
'array',
|
|
23
46
|
'bigint',
|
|
24
47
|
'boolean',
|
|
25
48
|
'date',
|
|
26
49
|
'function',
|
|
27
|
-
'null',
|
|
28
50
|
'number',
|
|
29
51
|
'string',
|
|
30
52
|
'symbol',
|
|
31
53
|
TYPE_OBJECT,
|
|
32
|
-
TYPE_UNDEFINED,
|
|
33
54
|
]);
|
|
55
|
+
|
|
56
|
+
export const TYPE_ALL = new Set<ValueName>([...VALIDATABLE_TYPES, 'null', TYPE_UNDEFINED]);
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export type
|
|
1
|
+
export {instanceOf} from './is';
|
|
2
|
+
export {SchematicError, type Schema, type TypedSchema} from './models';
|
|
3
3
|
export {schematic, type Schematic} from './schematic';
|
package/src/is.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
2
|
-
import {SCHEMATIC_NAME} from './constants';
|
|
2
|
+
import {MESSAGE_CONSTRUCTOR, SCHEMATIC_NAME} from './constants';
|
|
3
3
|
import type {Constructor} from './models';
|
|
4
4
|
import type {Schematic} from './schematic';
|
|
5
5
|
|
|
6
|
-
export function
|
|
6
|
+
export function instanceOf<Instance>(
|
|
7
7
|
constructor: Constructor<Instance>,
|
|
8
8
|
): (value: unknown) => value is Instance {
|
|
9
9
|
if (!isConstructor(constructor)) {
|
|
10
|
-
throw new TypeError(
|
|
10
|
+
throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
return (value: unknown): value is Instance => {
|
package/src/models.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type {GenericCallback, PlainObject, Simplify} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {ERROR_NAME} from './constants';
|
|
2
3
|
import type {Schematic} from './schematic';
|
|
3
4
|
|
|
4
5
|
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
@@ -65,17 +66,23 @@ type InferSchemaEntryValue<Value> =
|
|
|
65
66
|
? Model
|
|
66
67
|
: Value extends SchemaProperty
|
|
67
68
|
? InferPropertyType<Value['$type']>
|
|
68
|
-
: Value extends
|
|
69
|
-
?
|
|
70
|
-
: Value extends
|
|
71
|
-
?
|
|
72
|
-
:
|
|
69
|
+
: Value extends NestedSchema
|
|
70
|
+
? Infer<Omit<Value, '$required'>>
|
|
71
|
+
: Value extends ValueName
|
|
72
|
+
? Values[Value & ValueName]
|
|
73
|
+
: Value extends Schema
|
|
74
|
+
? Infer<Value>
|
|
75
|
+
: never;
|
|
73
76
|
|
|
74
77
|
type IsOptionalProperty<Value> = Value extends SchemaProperty
|
|
75
78
|
? Value['$required'] extends false
|
|
76
79
|
? true
|
|
77
80
|
: false
|
|
78
|
-
:
|
|
81
|
+
: Value extends {$required?: boolean}
|
|
82
|
+
? Value extends {$required: false}
|
|
83
|
+
? true
|
|
84
|
+
: false
|
|
85
|
+
: false;
|
|
79
86
|
|
|
80
87
|
type LastOfUnion<Value> =
|
|
81
88
|
UnionToIntersection<Value extends unknown ? () => Value : never> extends () => infer Item
|
|
@@ -95,7 +102,8 @@ type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [infer He
|
|
|
95
102
|
*/
|
|
96
103
|
export type NestedSchema = {
|
|
97
104
|
$required?: boolean;
|
|
98
|
-
|
|
105
|
+
[key: string]: any;
|
|
106
|
+
};
|
|
99
107
|
|
|
100
108
|
type OptionalKeys<Value> = {
|
|
101
109
|
[Key in keyof Value]-?: {} extends Pick<Value, Key> ? Key : never;
|
|
@@ -114,10 +122,16 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
114
122
|
*/
|
|
115
123
|
export type Schema = SchemaIndex;
|
|
116
124
|
|
|
117
|
-
type SchemaEntry =
|
|
125
|
+
type SchemaEntry =
|
|
126
|
+
| Constructor
|
|
127
|
+
| Schema
|
|
128
|
+
| SchemaProperty
|
|
129
|
+
| Schematic<unknown>
|
|
130
|
+
| ValueName
|
|
131
|
+
| ((value: unknown) => boolean);
|
|
118
132
|
|
|
119
133
|
interface SchemaIndex {
|
|
120
|
-
[key: string]: SchemaEntry | SchemaEntry[];
|
|
134
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
121
135
|
}
|
|
122
136
|
|
|
123
137
|
/**
|
|
@@ -129,15 +143,30 @@ export type SchemaProperty = {
|
|
|
129
143
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
130
144
|
};
|
|
131
145
|
|
|
132
|
-
type SchemaPropertyType =
|
|
146
|
+
type SchemaPropertyType =
|
|
147
|
+
| Constructor
|
|
148
|
+
| Schema
|
|
149
|
+
| Schematic<unknown>
|
|
150
|
+
| ValueName
|
|
151
|
+
| ((value: unknown) => boolean);
|
|
152
|
+
|
|
153
|
+
export class SchematicError extends Error {
|
|
154
|
+
constructor(message: string) {
|
|
155
|
+
super(message);
|
|
156
|
+
|
|
157
|
+
this.name = ERROR_NAME;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
133
160
|
|
|
134
161
|
type ToSchemaPropertyType<Value> = UnwrapSingle<
|
|
135
162
|
DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
|
|
136
163
|
>;
|
|
137
164
|
|
|
138
|
-
type ToSchemaPropertyTypeEach<Value> = Value extends
|
|
139
|
-
?
|
|
140
|
-
:
|
|
165
|
+
type ToSchemaPropertyTypeEach<Value> = Value extends NestedSchema
|
|
166
|
+
? Omit<Value, '$required'>
|
|
167
|
+
: Value extends PlainObject
|
|
168
|
+
? TypedSchema<Value>
|
|
169
|
+
: ToValueType<Value>;
|
|
141
170
|
|
|
142
171
|
type ToSchemaType<Value> = UnwrapSingle<DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>>;
|
|
143
172
|
|
|
@@ -149,21 +178,23 @@ type ToValueType<Value> = Value extends unknown[]
|
|
|
149
178
|
? 'boolean'
|
|
150
179
|
: Value extends Date
|
|
151
180
|
? 'date'
|
|
152
|
-
: Value extends
|
|
153
|
-
?
|
|
154
|
-
: Value extends
|
|
155
|
-
? '
|
|
156
|
-
: Value extends
|
|
157
|
-
? '
|
|
158
|
-
: Value extends
|
|
159
|
-
? '
|
|
160
|
-
: Value extends
|
|
161
|
-
? '
|
|
162
|
-
: Value extends
|
|
163
|
-
? '
|
|
164
|
-
: Value extends
|
|
165
|
-
? '
|
|
166
|
-
:
|
|
181
|
+
: Value extends Schematic<any>
|
|
182
|
+
? Value
|
|
183
|
+
: Value extends Function
|
|
184
|
+
? 'function'
|
|
185
|
+
: Value extends null
|
|
186
|
+
? 'null'
|
|
187
|
+
: Value extends number
|
|
188
|
+
? 'number'
|
|
189
|
+
: Value extends object
|
|
190
|
+
? 'object' | ((value: unknown) => value is Value)
|
|
191
|
+
: Value extends string
|
|
192
|
+
? 'string'
|
|
193
|
+
: Value extends symbol
|
|
194
|
+
? 'symbol'
|
|
195
|
+
: Value extends undefined
|
|
196
|
+
? 'undefined'
|
|
197
|
+
: (value: unknown) => value is Value;
|
|
167
198
|
|
|
168
199
|
type TuplePermutations<
|
|
169
200
|
Tuple extends unknown[],
|
|
@@ -223,11 +254,13 @@ export type TypedPropertyRequired<Value> = {
|
|
|
223
254
|
export type TypedSchema<Model extends PlainObject> = Simplify<
|
|
224
255
|
{
|
|
225
256
|
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject
|
|
226
|
-
? TypedSchemaRequired<Model[Key]>
|
|
257
|
+
? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]>
|
|
227
258
|
: ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
228
259
|
} & {
|
|
229
260
|
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject
|
|
230
|
-
?
|
|
261
|
+
?
|
|
262
|
+
| TypedSchemaOptional<Exclude<Model[Key], undefined>>
|
|
263
|
+
| Schematic<Exclude<Model[Key], undefined>>
|
|
231
264
|
: TypedPropertyOptional<Model[Key]>;
|
|
232
265
|
}
|
|
233
266
|
>;
|
|
@@ -257,26 +290,24 @@ type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only]
|
|
|
257
290
|
: Value;
|
|
258
291
|
|
|
259
292
|
export type ValidatedProperty = {
|
|
293
|
+
key: ValidatedPropertyKey;
|
|
260
294
|
required: boolean;
|
|
261
295
|
types: ValidatedPropertyType[];
|
|
262
296
|
validators: ValidatedPropertyValidators;
|
|
263
297
|
};
|
|
264
298
|
|
|
265
|
-
export type
|
|
299
|
+
export type ValidatedPropertyKey = {
|
|
300
|
+
full: string;
|
|
301
|
+
prefix: string | undefined;
|
|
302
|
+
value: string;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValueName;
|
|
266
306
|
|
|
267
307
|
export type ValidatedPropertyValidators = {
|
|
268
308
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
269
309
|
};
|
|
270
310
|
|
|
271
|
-
export type ValidatedSchema = {
|
|
272
|
-
enabled: boolean;
|
|
273
|
-
keys: {
|
|
274
|
-
array: string[];
|
|
275
|
-
set: Set<string>;
|
|
276
|
-
};
|
|
277
|
-
properties: Record<string, ValidatedProperty>;
|
|
278
|
-
};
|
|
279
|
-
|
|
280
311
|
/**
|
|
281
312
|
* Valid type name strings
|
|
282
313
|
*/
|
package/src/schematic.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
1
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
|
-
import {SCHEMATIC_NAME} from './constants';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
|
|
3
|
+
import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
|
|
4
|
+
import {isSchematic} from './is';
|
|
5
|
+
import {
|
|
6
|
+
SchematicError,
|
|
7
|
+
type Infer,
|
|
8
|
+
type Schema,
|
|
9
|
+
type TypedSchema,
|
|
10
|
+
type ValidatedProperty,
|
|
11
|
+
} from './models';
|
|
12
|
+
import {getProperties} from './validation/property.validation';
|
|
13
|
+
import {validateObject} from './validation/value.validation';
|
|
6
14
|
|
|
7
15
|
/**
|
|
8
16
|
* A schematic for validating objects
|
|
@@ -10,27 +18,21 @@ import {validateValue} from './validation/value.validation';
|
|
|
10
18
|
export class Schematic<Model> {
|
|
11
19
|
declare private readonly $schematic: true;
|
|
12
20
|
|
|
13
|
-
#
|
|
21
|
+
#properties: ValidatedProperty[];
|
|
14
22
|
|
|
15
|
-
|
|
16
|
-
return this.#schema.enabled;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
constructor(schema: Model) {
|
|
23
|
+
constructor(properties: ValidatedProperty[]) {
|
|
20
24
|
Object.defineProperty(this, SCHEMATIC_NAME, {
|
|
21
25
|
value: true,
|
|
22
26
|
});
|
|
23
27
|
|
|
24
|
-
this.#
|
|
25
|
-
|
|
26
|
-
this.#schema.enabled = this.#schema.keys.array.length > 0;
|
|
28
|
+
this.#properties = properties;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Does the value match the schema?
|
|
31
33
|
*/
|
|
32
34
|
is(value: unknown): value is Model {
|
|
33
|
-
return
|
|
35
|
+
return validateObject(value, this.#properties);
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -45,5 +47,13 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
|
|
|
45
47
|
export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
46
48
|
|
|
47
49
|
export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
|
|
48
|
-
|
|
50
|
+
if (isSchematic(schema)) {
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!isPlainObject(schema)) {
|
|
55
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return new Schematic<Model>(getProperties(schema));
|
|
49
59
|
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {smush} from '@oscarpalmer/atoms/value/misc';
|
|
4
|
+
import {
|
|
5
|
+
EXPRESSION_INDEX,
|
|
6
|
+
EXPRESSION_KEY_PREFIX,
|
|
7
|
+
EXPRESSION_KEY_VALUE,
|
|
8
|
+
EXPRESSION_PROPERTY,
|
|
9
|
+
MESSAGE_SCHEMA_INVALID_EMPTY,
|
|
10
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
|
|
11
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
|
|
12
|
+
MESSAGE_VALIDATOR_INVALID_KEY,
|
|
13
|
+
MESSAGE_VALIDATOR_INVALID_TYPE,
|
|
14
|
+
MESSAGE_VALIDATOR_INVALID_VALUE,
|
|
15
|
+
PROPERTY_REQUIRED,
|
|
16
|
+
PROPERTY_TYPE,
|
|
17
|
+
PROPERTY_VALIDATORS,
|
|
18
|
+
TEMPLATE_PATTERN,
|
|
19
|
+
TYPE_ALL,
|
|
20
|
+
TYPE_OBJECT,
|
|
21
|
+
TYPE_UNDEFINED,
|
|
22
|
+
VALIDATABLE_TYPES,
|
|
23
|
+
} from '../constants';
|
|
24
|
+
import {instanceOf, isSchematic} from '../is';
|
|
25
|
+
import {
|
|
26
|
+
SchematicError,
|
|
27
|
+
type ValidatedProperty,
|
|
28
|
+
type ValidatedPropertyType,
|
|
29
|
+
type ValidatedPropertyValidators,
|
|
30
|
+
type ValueName,
|
|
31
|
+
} from '../models';
|
|
32
|
+
import {schematic} from '../schematic';
|
|
33
|
+
|
|
34
|
+
function getKeyPrefix(key: string): string | undefined {
|
|
35
|
+
const prefix = key.replace(EXPRESSION_KEY_PREFIX, '');
|
|
36
|
+
|
|
37
|
+
return prefix === key ? undefined : prefix;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getKeyValue(key: string): string {
|
|
41
|
+
return key.replace(EXPRESSION_KEY_VALUE, '$1');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getProperties(original: PlainObject): ValidatedProperty[] {
|
|
45
|
+
if (Object.keys(original).length === 0) {
|
|
46
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const smushed = smush(original);
|
|
50
|
+
const keys = Object.keys(smushed);
|
|
51
|
+
const keysLength = keys.length;
|
|
52
|
+
|
|
53
|
+
const properties: ValidatedProperty[] = [];
|
|
54
|
+
|
|
55
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
56
|
+
const key = keys[keyIndex];
|
|
57
|
+
|
|
58
|
+
if (EXPRESSION_INDEX.test(key) || EXPRESSION_PROPERTY.test(key)) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const keyPrefix = getKeyPrefix(key);
|
|
63
|
+
const keyValue = getKeyValue(key);
|
|
64
|
+
const value = smushed[key];
|
|
65
|
+
|
|
66
|
+
const types: ValidatedPropertyType[] = [];
|
|
67
|
+
|
|
68
|
+
let required = true;
|
|
69
|
+
let validators: ValidatedPropertyValidators = {};
|
|
70
|
+
|
|
71
|
+
if (isPlainObject(value)) {
|
|
72
|
+
required = getRequired(key, value) ?? required;
|
|
73
|
+
validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
74
|
+
|
|
75
|
+
if (PROPERTY_TYPE in value) {
|
|
76
|
+
types.push(...getTypes(key, value[PROPERTY_TYPE]));
|
|
77
|
+
} else {
|
|
78
|
+
types.push(TYPE_OBJECT);
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
types.push(...getTypes(key, value));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!required && !types.includes(TYPE_UNDEFINED)) {
|
|
85
|
+
types.push(TYPE_UNDEFINED);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
properties.push({
|
|
89
|
+
types,
|
|
90
|
+
validators,
|
|
91
|
+
key: {
|
|
92
|
+
full: key,
|
|
93
|
+
prefix: keyPrefix,
|
|
94
|
+
value: keyValue,
|
|
95
|
+
},
|
|
96
|
+
required: required && !types.includes(TYPE_UNDEFINED),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return properties;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getRequired(key: string, value: PlainObject): boolean | undefined {
|
|
104
|
+
if (!(PROPERTY_REQUIRED in value)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (typeof value[PROPERTY_REQUIRED] !== 'boolean') {
|
|
109
|
+
throw new SchematicError(
|
|
110
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return value[PROPERTY_REQUIRED];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getTypes(key: string, original: unknown): ValidatedPropertyType[] {
|
|
118
|
+
const array = Array.isArray(original) ? original : [original];
|
|
119
|
+
const {length} = array;
|
|
120
|
+
|
|
121
|
+
const types: ValidatedPropertyType[] = [];
|
|
122
|
+
|
|
123
|
+
for (let index = 0; index < length; index += 1) {
|
|
124
|
+
const value = array[index];
|
|
125
|
+
|
|
126
|
+
switch (true) {
|
|
127
|
+
case typeof value === 'function':
|
|
128
|
+
types.push(isConstructor(value) ? instanceOf(value) : value);
|
|
129
|
+
break;
|
|
130
|
+
|
|
131
|
+
case isPlainObject(value):
|
|
132
|
+
types.push(schematic(value as never));
|
|
133
|
+
break;
|
|
134
|
+
|
|
135
|
+
case isSchematic(value):
|
|
136
|
+
types.push(value);
|
|
137
|
+
break;
|
|
138
|
+
|
|
139
|
+
case TYPE_ALL.has(value as never):
|
|
140
|
+
types.push(value as never);
|
|
141
|
+
break;
|
|
142
|
+
|
|
143
|
+
default:
|
|
144
|
+
throw new SchematicError(
|
|
145
|
+
MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (types.length === 0) {
|
|
151
|
+
throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return types;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getValidators(original: unknown): ValidatedPropertyValidators {
|
|
158
|
+
const validators: ValidatedPropertyValidators = {};
|
|
159
|
+
|
|
160
|
+
if (original == null) {
|
|
161
|
+
return validators;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!isPlainObject(original)) {
|
|
165
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const keys = Object.keys(original);
|
|
169
|
+
const {length} = keys;
|
|
170
|
+
|
|
171
|
+
for (let index = 0; index < length; index += 1) {
|
|
172
|
+
const key = keys[index];
|
|
173
|
+
|
|
174
|
+
if (!VALIDATABLE_TYPES.has(key as never)) {
|
|
175
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const value = (original as PlainObject)[key];
|
|
179
|
+
|
|
180
|
+
validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
|
|
181
|
+
if (typeof item !== 'function') {
|
|
182
|
+
throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return validators;
|
|
190
|
+
}
|
|
@@ -1,73 +1,49 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import type {ValidatedProperty, ValidatedPropertyType, ValidatedSchema, Values} from '../models';
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {isSchematic} from '../is';
|
|
3
|
+
import type {ValidatedProperty, ValidatedPropertyType, ValueName} from '../models';
|
|
5
4
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
property: ValidatedProperty,
|
|
9
|
-
value: unknown,
|
|
10
|
-
): boolean {
|
|
11
|
-
switch (true) {
|
|
12
|
-
case typeof type === 'function':
|
|
13
|
-
return (type as any)(value);
|
|
14
|
-
|
|
15
|
-
case typeof type === 'string':
|
|
16
|
-
return (
|
|
17
|
-
validators[type](value) &&
|
|
18
|
-
(property.validators[type]?.every(validator => validator(value)) ?? true)
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
default:
|
|
22
|
-
return type.is(value);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function validateValue(validated: ValidatedSchema, obj: unknown): boolean {
|
|
27
|
-
if (typeof obj !== 'object' || obj === null) {
|
|
5
|
+
export function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean {
|
|
6
|
+
if (!isPlainObject(obj)) {
|
|
28
7
|
return false;
|
|
29
8
|
}
|
|
30
9
|
|
|
31
|
-
const
|
|
32
|
-
const
|
|
10
|
+
const ignoredKeys = new Set<string>();
|
|
11
|
+
const propertiesLength = properties.length;
|
|
33
12
|
|
|
34
|
-
|
|
13
|
+
let key!: string;
|
|
14
|
+
let value!: unknown;
|
|
35
15
|
|
|
36
|
-
|
|
16
|
+
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
17
|
+
const property = properties[propertyIndex];
|
|
37
18
|
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
if (ignoredKeys.has(property.key.prefix!)) {
|
|
20
|
+
key = undefined as never;
|
|
40
21
|
|
|
41
|
-
|
|
22
|
+
ignoredKeys.add(property.key.full);
|
|
42
23
|
|
|
43
|
-
if (ignore.has(prefix)) {
|
|
44
24
|
continue;
|
|
45
25
|
}
|
|
46
26
|
|
|
47
|
-
|
|
48
|
-
|
|
27
|
+
/* if (key == null || !property.key.full.startsWith(key)) {
|
|
28
|
+
value = obj[property.key.full];
|
|
29
|
+
} else {
|
|
30
|
+
value = (value as PlainObject)?.[property.key.value];
|
|
31
|
+
} */
|
|
32
|
+
|
|
33
|
+
key = property.key.full;
|
|
34
|
+
value = obj[key];
|
|
49
35
|
|
|
50
|
-
if (value === undefined && property.required
|
|
36
|
+
if (value === undefined && property.required) {
|
|
51
37
|
return false;
|
|
52
38
|
}
|
|
53
39
|
|
|
54
40
|
const typesLength = property.types.length;
|
|
55
41
|
|
|
56
|
-
if (typesLength === 1) {
|
|
57
|
-
if (!validateType(property.types[0], property, value)) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
42
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
65
43
|
const type = property.types[typeIndex];
|
|
66
44
|
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
ignore.add(key);
|
|
70
|
-
}
|
|
45
|
+
if (validateValue(type, property, value)) {
|
|
46
|
+
ignoredKeys.add(property.key.full);
|
|
71
47
|
|
|
72
48
|
continue outer;
|
|
73
49
|
}
|
|
@@ -79,20 +55,36 @@ export function validateValue(validated: ValidatedSchema, obj: unknown): boolean
|
|
|
79
55
|
return true;
|
|
80
56
|
}
|
|
81
57
|
|
|
82
|
-
|
|
58
|
+
function validateValue(
|
|
59
|
+
type: ValidatedPropertyType,
|
|
60
|
+
property: ValidatedProperty,
|
|
61
|
+
value: unknown,
|
|
62
|
+
): boolean {
|
|
63
|
+
switch (true) {
|
|
64
|
+
case typeof type === 'function':
|
|
65
|
+
return (type as (value: unknown) => boolean)(value);
|
|
66
|
+
|
|
67
|
+
case isSchematic(type):
|
|
68
|
+
return type.is(value);
|
|
83
69
|
|
|
84
|
-
|
|
70
|
+
default:
|
|
71
|
+
return (
|
|
72
|
+
validators[type as ValueName](value) &&
|
|
73
|
+
(property.validators[type as ValueName]?.every(validator => validator(value)) ?? true)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
85
77
|
|
|
86
78
|
//
|
|
87
79
|
|
|
88
|
-
const validators: Record<
|
|
80
|
+
const validators: Record<ValueName, (value: unknown) => boolean> = {
|
|
89
81
|
array: Array.isArray,
|
|
90
82
|
bigint: value => typeof value === 'bigint',
|
|
91
83
|
boolean: value => typeof value === 'boolean',
|
|
92
84
|
date: value => value instanceof Date,
|
|
93
85
|
function: value => typeof value === 'function',
|
|
94
86
|
null: value => value === null,
|
|
95
|
-
number: value => typeof value === 'number'
|
|
87
|
+
number: value => typeof value === 'number',
|
|
96
88
|
object: value => typeof value === 'object' && value !== null,
|
|
97
89
|
string: value => typeof value === 'string',
|
|
98
90
|
symbol: value => typeof value === 'symbol',
|