@oscarpalmer/jhunal 0.11.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 +4 -3
- package/dist/index.js +2 -2
- package/dist/is.js +2 -2
- package/dist/jhunal.full.js +99 -109
- package/dist/schematic.js +9 -7
- 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 +5 -3
- package/src/index.ts +1 -1
- package/src/is.ts +1 -1
- package/src/models.ts +23 -13
- package/src/schematic.ts +13 -10
- package/src/validation/property.validation.ts +190 -0
- package/src/validation/value.validation.ts +45 -53
- package/types/constants.d.ts +2 -1
- package/types/index.d.ts +1 -1
- package/types/is.d.ts +1 -1
- package/types/models.d.ts +11 -12
- package/types/schematic.d.ts +2 -2
- package/types/validation/property.validation.d.ts +3 -0
- package/types/validation/value.validation.d.ts +2 -3
- package/dist/validation/schema.validation.js +0 -105
- package/src/validation/schema.validation.ts +0 -233
- package/types/validation/schema.validation.d.ts +0 -2
package/src/schematic.ts
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
3
|
import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
|
|
4
|
+
import {isSchematic} from './is';
|
|
4
5
|
import {
|
|
5
6
|
SchematicError,
|
|
6
7
|
type Infer,
|
|
7
8
|
type Schema,
|
|
8
9
|
type TypedSchema,
|
|
9
|
-
type
|
|
10
|
+
type ValidatedProperty,
|
|
10
11
|
} from './models';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
12
|
+
import {getProperties} from './validation/property.validation';
|
|
13
|
+
import {validateObject} from './validation/value.validation';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* A schematic for validating objects
|
|
@@ -17,21 +18,21 @@ import {validateValue} from './validation/value.validation';
|
|
|
17
18
|
export class Schematic<Model> {
|
|
18
19
|
declare private readonly $schematic: true;
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
+
#properties: ValidatedProperty[];
|
|
21
22
|
|
|
22
|
-
constructor(
|
|
23
|
+
constructor(properties: ValidatedProperty[]) {
|
|
23
24
|
Object.defineProperty(this, SCHEMATIC_NAME, {
|
|
24
25
|
value: true,
|
|
25
26
|
});
|
|
26
27
|
|
|
27
|
-
this.#
|
|
28
|
+
this.#properties = properties;
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* Does the value match the schema?
|
|
32
33
|
*/
|
|
33
34
|
is(value: unknown): value is Model {
|
|
34
|
-
return
|
|
35
|
+
return validateObject(value, this.#properties);
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -46,11 +47,13 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
|
|
|
46
47
|
export function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
47
48
|
|
|
48
49
|
export function schematic<Model extends Schema>(schema: Model): Schematic<Model> {
|
|
50
|
+
if (isSchematic(schema)) {
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
|
|
49
54
|
if (!isPlainObject(schema)) {
|
|
50
55
|
throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return new Schematic<Model>(validated);
|
|
58
|
+
return new Schematic<Model>(getProperties(schema));
|
|
56
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',
|
package/types/constants.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const ERROR_NAME = "SchematicError";
|
|
2
|
-
export declare const EXPRESSION_HAS_NUMBER: RegExp;
|
|
3
2
|
export declare const EXPRESSION_INDEX: RegExp;
|
|
3
|
+
export declare const EXPRESSION_KEY_PREFIX: RegExp;
|
|
4
|
+
export declare const EXPRESSION_KEY_VALUE: RegExp;
|
|
4
5
|
export declare const EXPRESSION_PROPERTY: RegExp;
|
|
5
6
|
export declare const MESSAGE_CONSTRUCTOR = "Expected a constructor function";
|
|
6
7
|
export declare const MESSAGE_SCHEMA_INVALID_EMPTY = "Schema must have at least one property";
|
package/types/index.d.ts
CHANGED
package/types/is.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Constructor } from './models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
|
-
export declare function
|
|
3
|
+
export declare function instanceOf<Instance>(constructor: Constructor<Instance>): (value: unknown) => value is Instance;
|
|
4
4
|
export declare function isSchematic(value: unknown): value is Schematic<never>;
|
package/types/models.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PlainObject, Simplify } from '@oscarpalmer/atoms/models';
|
|
1
|
+
import type { GenericCallback, PlainObject, Simplify } from '@oscarpalmer/atoms/models';
|
|
2
2
|
import type { Schematic } from './schematic';
|
|
3
3
|
export type Constructor<Instance = any> = new (...args: any[]) => Instance;
|
|
4
4
|
type DeduplicateTuple<Value extends unknown[], Seen extends unknown[] = []> = Value extends [
|
|
@@ -50,9 +50,9 @@ type RequiredKeys<Value> = Exclude<keyof Value, OptionalKeys<Value>>;
|
|
|
50
50
|
* A schema for validating objects
|
|
51
51
|
*/
|
|
52
52
|
export type Schema = SchemaIndex;
|
|
53
|
-
type SchemaEntry = Constructor | SchemaProperty | Schematic<unknown> | ValueName |
|
|
53
|
+
type SchemaEntry = Constructor | Schema | SchemaProperty | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
54
54
|
interface SchemaIndex {
|
|
55
|
-
[key: string]: SchemaEntry | SchemaEntry[];
|
|
55
|
+
[key: string]: NestedSchema | SchemaEntry | SchemaEntry[];
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* A property definition with explicit type(s) and optional requirement flag
|
|
@@ -62,7 +62,7 @@ export type SchemaProperty = {
|
|
|
62
62
|
$type: SchemaPropertyType | SchemaPropertyType[];
|
|
63
63
|
$validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
|
|
64
64
|
};
|
|
65
|
-
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName;
|
|
65
|
+
type SchemaPropertyType = Constructor | Schema | Schematic<unknown> | ValueName | ((value: unknown) => boolean);
|
|
66
66
|
export declare class SchematicError extends Error {
|
|
67
67
|
constructor(message: string);
|
|
68
68
|
}
|
|
@@ -123,21 +123,20 @@ type UnionToIntersection<Value> = (Value extends unknown ? (value: Value) => voi
|
|
|
123
123
|
type UnionToTuple<Value, Items extends unknown[] = []> = [Value] extends [never] ? Items : UnionToTuple<Exclude<Value, LastOfUnion<Value>>, [LastOfUnion<Value>, ...Items]>;
|
|
124
124
|
type UnwrapSingle<Value extends unknown[]> = Value extends [infer Only] ? Only : Value['length'] extends 1 | 2 | 3 | 4 | 5 ? TuplePermutations<Value> : Value;
|
|
125
125
|
export type ValidatedProperty = {
|
|
126
|
+
key: ValidatedPropertyKey;
|
|
126
127
|
required: boolean;
|
|
127
128
|
types: ValidatedPropertyType[];
|
|
128
129
|
validators: ValidatedPropertyValidators;
|
|
129
130
|
};
|
|
130
|
-
export type
|
|
131
|
+
export type ValidatedPropertyKey = {
|
|
132
|
+
full: string;
|
|
133
|
+
prefix: string | undefined;
|
|
134
|
+
value: string;
|
|
135
|
+
};
|
|
136
|
+
export type ValidatedPropertyType = GenericCallback | Schematic<unknown> | ValueName;
|
|
131
137
|
export type ValidatedPropertyValidators = {
|
|
132
138
|
[Key in ValueName]?: Array<(value: unknown) => boolean>;
|
|
133
139
|
};
|
|
134
|
-
export type ValidatedSchema = {
|
|
135
|
-
keys: {
|
|
136
|
-
array: string[];
|
|
137
|
-
set: Set<string>;
|
|
138
|
-
};
|
|
139
|
-
properties: Record<string, ValidatedProperty>;
|
|
140
|
-
};
|
|
141
140
|
/**
|
|
142
141
|
* Valid type name strings
|
|
143
142
|
*/
|
package/types/schematic.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { PlainObject } from '@oscarpalmer/atoms/models';
|
|
2
|
-
import { type Infer, type Schema, type TypedSchema, type
|
|
2
|
+
import { type Infer, type Schema, type TypedSchema, type ValidatedProperty } from './models';
|
|
3
3
|
/**
|
|
4
4
|
* A schematic for validating objects
|
|
5
5
|
*/
|
|
6
6
|
export declare class Schematic<Model> {
|
|
7
7
|
#private;
|
|
8
8
|
private readonly $schematic;
|
|
9
|
-
constructor(
|
|
9
|
+
constructor(properties: ValidatedProperty[]);
|
|
10
10
|
/**
|
|
11
11
|
* Does the value match the schema?
|
|
12
12
|
*/
|
|
@@ -1,3 +1,2 @@
|
|
|
1
|
-
import type { ValidatedProperty
|
|
2
|
-
export declare function
|
|
3
|
-
export declare function validateValue(validated: ValidatedSchema, obj: unknown): boolean;
|
|
1
|
+
import type { ValidatedProperty } from '../models';
|
|
2
|
+
export declare function validateObject(obj: unknown, properties: ValidatedProperty[]): boolean;
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import { EXPRESSION_HAS_NUMBER, EXPRESSION_INDEX, EXPRESSION_PROPERTY, MESSAGE_SCHEMA_INVALID_EMPTY, MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, MESSAGE_VALIDATOR_INVALID_KEY, MESSAGE_VALIDATOR_INVALID_TYPE, MESSAGE_VALIDATOR_INVALID_VALUE, PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, TYPE_ALL, TYPE_OBJECT, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.js";
|
|
2
|
-
import { isInstance, isSchematic } from "../is.js";
|
|
3
|
-
import { SchematicError } from "../models.js";
|
|
4
|
-
import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
|
|
5
|
-
import { smush } from "@oscarpalmer/atoms/value/misc";
|
|
6
|
-
function addPropertyType(to, key, values, validators, required) {
|
|
7
|
-
if (to.keys.set.has(key)) {
|
|
8
|
-
const property = to.properties[key];
|
|
9
|
-
for (const type of values) property.types.push(type);
|
|
10
|
-
} else {
|
|
11
|
-
to.keys.array.push(key);
|
|
12
|
-
to.keys.set.add(key);
|
|
13
|
-
to.properties[key] = {
|
|
14
|
-
required,
|
|
15
|
-
types: values,
|
|
16
|
-
validators: {}
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
if (!required && !to.properties[key].types.includes("undefined")) to.properties[key].types.push(TYPE_UNDEFINED);
|
|
20
|
-
to.properties[key].validators = validators;
|
|
21
|
-
}
|
|
22
|
-
function getSchema(schema) {
|
|
23
|
-
return getValidatedSchema(schema, {
|
|
24
|
-
keys: {
|
|
25
|
-
array: [],
|
|
26
|
-
set: /* @__PURE__ */ new Set()
|
|
27
|
-
},
|
|
28
|
-
properties: {}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
function getTypes(value, validated, prefix) {
|
|
32
|
-
const propertyTypes = [];
|
|
33
|
-
const values = Array.isArray(value) ? value : [value];
|
|
34
|
-
const { length } = values;
|
|
35
|
-
for (let index = 0; index < length; index += 1) {
|
|
36
|
-
const type = values[index];
|
|
37
|
-
if (isSchematic(type) || TYPE_ALL.has(type)) {
|
|
38
|
-
propertyTypes.push(type);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (typeof type === "function") {
|
|
42
|
-
propertyTypes.push(isConstructor(type) ? isInstance(type) : type);
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
if (!isPlainObject(type)) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", prefix));
|
|
46
|
-
if ("$type" in type) {
|
|
47
|
-
propertyTypes.push(...getTypes(type[PROPERTY_TYPE], validated, prefix));
|
|
48
|
-
continue;
|
|
49
|
-
}
|
|
50
|
-
const { [PROPERTY_REQUIRED]: required, ...nested } = type;
|
|
51
|
-
if ("$required" in type && typeof required !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", prefix));
|
|
52
|
-
addPropertyType(validated, prefix, [TYPE_OBJECT], {}, required !== false);
|
|
53
|
-
propertyTypes.push(TYPE_OBJECT);
|
|
54
|
-
getValidatedSchema(nested, validated, prefix);
|
|
55
|
-
}
|
|
56
|
-
return propertyTypes;
|
|
57
|
-
}
|
|
58
|
-
function getValidatedSchema(schema, validated, prefix) {
|
|
59
|
-
const smushed = smush(schema);
|
|
60
|
-
const keys = Object.keys(smushed);
|
|
61
|
-
const { length } = keys;
|
|
62
|
-
const arrayKeys = /* @__PURE__ */ new Set();
|
|
63
|
-
const noPrefix = prefix == null;
|
|
64
|
-
prefix = noPrefix ? "" : `${prefix}.`;
|
|
65
|
-
for (let index = 0; index < length; index += 1) {
|
|
66
|
-
const key = keys[index];
|
|
67
|
-
const value = smushed[key];
|
|
68
|
-
if (Array.isArray(value)) arrayKeys.add(key);
|
|
69
|
-
if (EXPRESSION_PROPERTY.test(key)) continue;
|
|
70
|
-
if (EXPRESSION_HAS_NUMBER.test(key) && arrayKeys.has(key.replace(EXPRESSION_INDEX, ""))) continue;
|
|
71
|
-
let required = true;
|
|
72
|
-
let validators = {};
|
|
73
|
-
const isObject = isPlainObject(value);
|
|
74
|
-
if (isObject && "$required" in value) {
|
|
75
|
-
if (typeof value["$required"] !== "boolean") throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
|
|
76
|
-
required = value[PROPERTY_REQUIRED] === true;
|
|
77
|
-
}
|
|
78
|
-
if (isObject && "$validators" in value) validators = getValidators(value[PROPERTY_VALIDATORS]);
|
|
79
|
-
const prefixedKey = `${prefix}${key}`;
|
|
80
|
-
const types = getTypes(value, validated, prefixedKey);
|
|
81
|
-
if (types.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", key));
|
|
82
|
-
addPropertyType(validated, prefixedKey, types, validators, required);
|
|
83
|
-
}
|
|
84
|
-
if (noPrefix) validated.keys.array.sort();
|
|
85
|
-
if (noPrefix && validated.keys.array.length === 0) throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
86
|
-
return validated;
|
|
87
|
-
}
|
|
88
|
-
function getValidators(original) {
|
|
89
|
-
const validators = {};
|
|
90
|
-
if (original == null) return validators;
|
|
91
|
-
if (!isPlainObject(original)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
92
|
-
const keys = Object.keys(original);
|
|
93
|
-
const { length } = keys;
|
|
94
|
-
for (let index = 0; index < length; index += 1) {
|
|
95
|
-
const key = keys[index];
|
|
96
|
-
if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
|
|
97
|
-
const value = original[key];
|
|
98
|
-
validators[key] = (Array.isArray(value) ? value : [value]).filter((item) => {
|
|
99
|
-
if (typeof item !== "function") throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
|
|
100
|
-
return true;
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return validators;
|
|
104
|
-
}
|
|
105
|
-
export { getSchema };
|