@oscarpalmer/jhunal 0.21.0 → 0.23.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.d.mts +7 -3
- package/dist/constants.mjs +34 -12
- package/dist/helpers/message.helper.d.mts +11 -0
- package/dist/helpers/message.helper.mjs +70 -0
- package/dist/helpers/misc.helper.d.mts +22 -0
- package/dist/helpers/misc.helper.mjs +56 -0
- package/dist/index.d.mts +318 -328
- package/dist/index.mjs +324 -295
- package/dist/models/schema.plain.model.d.mts +2 -8
- package/dist/models/validation.model.d.mts +26 -59
- package/dist/schematic.d.mts +28 -10
- package/dist/schematic.mjs +15 -16
- package/dist/validator/base.validator.d.mts +6 -0
- package/dist/validator/base.validator.mjs +19 -0
- package/dist/validator/function.validator.d.mts +6 -0
- package/dist/validator/function.validator.mjs +9 -0
- package/dist/validator/named.handler.d.mts +6 -0
- package/dist/validator/named.handler.mjs +22 -0
- package/dist/validator/named.validator.d.mts +7 -0
- package/dist/validator/named.validator.mjs +39 -0
- package/dist/validator/object.validator.d.mts +7 -0
- package/dist/validator/object.validator.mjs +167 -0
- package/dist/validator/schematic.validator.d.mts +7 -0
- package/dist/validator/schematic.validator.mjs +16 -0
- package/package.json +1 -1
- package/src/constants.ts +42 -10
- package/src/helpers/message.helper.ts +152 -0
- package/src/helpers/misc.helper.ts +93 -0
- package/src/index.ts +7 -2
- package/src/models/schema.plain.model.ts +1 -8
- package/src/models/validation.model.ts +55 -77
- package/src/schematic.ts +49 -27
- package/src/validator/base.validator.ts +31 -0
- package/src/validator/function.validator.ts +9 -0
- package/src/validator/named.handler.ts +50 -0
- package/src/validator/named.validator.ts +62 -0
- package/src/validator/object.validator.ts +340 -0
- package/src/validator/schematic.validator.ts +25 -0
- package/dist/helpers.d.mts +0 -28
- package/dist/helpers.mjs +0 -119
- package/dist/validation/property.validation.d.mts +0 -7
- package/dist/validation/property.validation.mjs +0 -92
- package/dist/validation/value.validation.d.mts +0 -7
- package/dist/validation/value.validation.mjs +0 -162
- package/src/helpers.ts +0 -246
- package/src/validation/property.validation.ts +0 -217
- package/src/validation/value.validation.ts +0 -293
package/src/schematic.ts
CHANGED
|
@@ -3,18 +3,18 @@ import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
|
3
3
|
import {error, ok} from '@oscarpalmer/atoms/result/misc';
|
|
4
4
|
import type {Result} from '@oscarpalmer/atoms/result/models';
|
|
5
5
|
import {PROPERTY_SCHEMATIC, SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE} from './constants';
|
|
6
|
-
import {getParameters, isSchematic} from './helpers';
|
|
6
|
+
import {getParameters, isSchematic} from './helpers/misc.helper';
|
|
7
7
|
import type {Infer} from './models/infer.model';
|
|
8
8
|
import type {Schema} from './models/schema.plain.model';
|
|
9
9
|
import type {TypedSchema} from './models/schema.typed.model';
|
|
10
10
|
import {
|
|
11
11
|
SchematicError,
|
|
12
|
-
type
|
|
12
|
+
type GetOptions,
|
|
13
|
+
type IsOptions,
|
|
13
14
|
type ValidationInformation,
|
|
14
|
-
type
|
|
15
|
+
type Validator,
|
|
15
16
|
} from './models/validation.model';
|
|
16
|
-
import {
|
|
17
|
-
import {validateObject} from './validation/value.validation';
|
|
17
|
+
import {getObjectValidator} from './validator/object.validator';
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
20
|
* A schematic for validating objects
|
|
@@ -22,16 +22,16 @@ import {validateObject} from './validation/value.validation';
|
|
|
22
22
|
export class Schematic<Model> {
|
|
23
23
|
declare private readonly $schematic: true;
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
#validator: Validator;
|
|
26
26
|
|
|
27
|
-
constructor(
|
|
27
|
+
constructor(validator: Validator) {
|
|
28
28
|
Object.defineProperty(this, PROPERTY_SCHEMATIC, {
|
|
29
29
|
value: true,
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
this.#
|
|
32
|
+
this.#validator = validator;
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
schematicValidator.set(this, validator);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -42,7 +42,7 @@ export class Schematic<Model> {
|
|
|
42
42
|
* @param options Validation options
|
|
43
43
|
* @returns Deeply cloned version of the value if it matches the schema, otherwise throws an error
|
|
44
44
|
*/
|
|
45
|
-
get(value: unknown, options:
|
|
45
|
+
get(value: unknown, options: GetOptions<'throw'>): Model;
|
|
46
46
|
|
|
47
47
|
/**
|
|
48
48
|
* Parse a value according to the schema
|
|
@@ -62,7 +62,7 @@ export class Schematic<Model> {
|
|
|
62
62
|
* @param options Validation options
|
|
63
63
|
* @returns Result holding deeply cloned value or all validation information
|
|
64
64
|
*/
|
|
65
|
-
get(value: unknown, options:
|
|
65
|
+
get(value: unknown, options: GetOptions<'all'>): Result<Model, ValidationInformation[]>;
|
|
66
66
|
|
|
67
67
|
/**
|
|
68
68
|
* Parse a value according to the schema
|
|
@@ -82,7 +82,7 @@ export class Schematic<Model> {
|
|
|
82
82
|
* @param options Validation options
|
|
83
83
|
* @returns Result holding deeply cloned value or all validation information
|
|
84
84
|
*/
|
|
85
|
-
get(value: unknown, options:
|
|
85
|
+
get(value: unknown, options: GetOptions<'first'>): Result<Model, ValidationInformation>;
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
88
|
* Parse a value according to the schema
|
|
@@ -94,6 +94,16 @@ export class Schematic<Model> {
|
|
|
94
94
|
*/
|
|
95
95
|
get(value: unknown, errors: 'first'): Result<Model, ValidationInformation>;
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Parse a value according to the schema
|
|
99
|
+
*
|
|
100
|
+
* Returns a deeply cloned version of the value or `undefined` if the value does not match the schema
|
|
101
|
+
* @param value Value to parse
|
|
102
|
+
* @param options Validation options
|
|
103
|
+
* @returns Deeply cloned value, or `undefined` if it's invalid
|
|
104
|
+
*/
|
|
105
|
+
get(value: unknown, options: GetOptions<'none'>): Model | undefined;
|
|
106
|
+
|
|
97
107
|
/**
|
|
98
108
|
* Parse a value according to the schema
|
|
99
109
|
*
|
|
@@ -107,14 +117,16 @@ export class Schematic<Model> {
|
|
|
107
117
|
get(value: unknown, options?: unknown): unknown {
|
|
108
118
|
const parameters = getParameters(options);
|
|
109
119
|
|
|
110
|
-
const result =
|
|
120
|
+
const result = this.#validator(value, parameters, true);
|
|
111
121
|
|
|
112
|
-
if (result
|
|
113
|
-
return
|
|
122
|
+
if (result === true) {
|
|
123
|
+
return parameters.reporting.none || parameters.reporting.throw
|
|
124
|
+
? parameters.output
|
|
125
|
+
: ok(parameters.output);
|
|
114
126
|
}
|
|
115
127
|
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
128
|
+
if (parameters.reporting.none) {
|
|
129
|
+
return;
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
return error(parameters.reporting.all ? result : result[0]);
|
|
@@ -128,7 +140,7 @@ export class Schematic<Model> {
|
|
|
128
140
|
* @param options Validation options
|
|
129
141
|
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
130
142
|
*/
|
|
131
|
-
is(value: unknown, options:
|
|
143
|
+
is(value: unknown, options: IsOptions<'throw'>): asserts value is Model;
|
|
132
144
|
|
|
133
145
|
/**
|
|
134
146
|
* Does the value match the schema?
|
|
@@ -148,7 +160,7 @@ export class Schematic<Model> {
|
|
|
148
160
|
* @param options Validation options
|
|
149
161
|
* @returns Result holding `true` or all validation information
|
|
150
162
|
*/
|
|
151
|
-
is(value: unknown, options:
|
|
163
|
+
is(value: unknown, options: IsOptions<'all'>): Result<true, ValidationInformation[]>;
|
|
152
164
|
|
|
153
165
|
/**
|
|
154
166
|
* Does the value match the schema?
|
|
@@ -168,7 +180,7 @@ export class Schematic<Model> {
|
|
|
168
180
|
* @param options Validation options
|
|
169
181
|
* @returns `true` if the value matches the schema, otherwise `false`
|
|
170
182
|
*/
|
|
171
|
-
is(value: unknown, options:
|
|
183
|
+
is(value: unknown, options: IsOptions<'first'>): Result<true, ValidationInformation>;
|
|
172
184
|
|
|
173
185
|
/**
|
|
174
186
|
* Does the value match the schema?
|
|
@@ -180,6 +192,16 @@ export class Schematic<Model> {
|
|
|
180
192
|
*/
|
|
181
193
|
is(value: unknown, errors: 'first'): Result<true, ValidationInformation>;
|
|
182
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Does the value match the schema?
|
|
197
|
+
*
|
|
198
|
+
* Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures
|
|
199
|
+
* @param value Value to validate
|
|
200
|
+
* @param options Validation options
|
|
201
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
202
|
+
*/
|
|
203
|
+
is(value: unknown, options: IsOptions<'none'>): value is Model;
|
|
204
|
+
|
|
183
205
|
/**
|
|
184
206
|
* Does the value match the schema?
|
|
185
207
|
*
|
|
@@ -193,14 +215,14 @@ export class Schematic<Model> {
|
|
|
193
215
|
is(value: unknown, options?: unknown): unknown {
|
|
194
216
|
const parameters = getParameters(options);
|
|
195
217
|
|
|
196
|
-
const result =
|
|
218
|
+
const result = this.#validator(value, parameters, false);
|
|
197
219
|
|
|
198
|
-
if (result
|
|
199
|
-
return
|
|
220
|
+
if (result === true) {
|
|
221
|
+
return parameters.reporting.none || parameters.reporting.throw ? result : ok(result);
|
|
200
222
|
}
|
|
201
223
|
|
|
202
|
-
if (
|
|
203
|
-
return
|
|
224
|
+
if (parameters.reporting.none) {
|
|
225
|
+
return false;
|
|
204
226
|
}
|
|
205
227
|
|
|
206
228
|
return error(parameters.reporting.all ? result : result[0]);
|
|
@@ -234,7 +256,7 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Model>
|
|
|
234
256
|
throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
|
|
235
257
|
}
|
|
236
258
|
|
|
237
|
-
return new Schematic<Model>(
|
|
259
|
+
return new Schematic<Model>(getObjectValidator(schema));
|
|
238
260
|
}
|
|
239
261
|
|
|
240
|
-
export const
|
|
262
|
+
export const schematicValidator = new WeakMap<Schematic<unknown>, Validator>();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type {ValidationInformation, Validator} from '../models/validation.model';
|
|
2
|
+
|
|
3
|
+
export function getBaseValidator(validators: Validator[]): Validator {
|
|
4
|
+
const {length} = validators;
|
|
5
|
+
|
|
6
|
+
return (input, parameters, get) => {
|
|
7
|
+
const allInformation: ValidationInformation[] = [];
|
|
8
|
+
|
|
9
|
+
for (let index = 0; index < length; index += 1) {
|
|
10
|
+
const previousInformation = parameters.information;
|
|
11
|
+
|
|
12
|
+
const nextInformation: ValidationInformation[] = [];
|
|
13
|
+
|
|
14
|
+
parameters.information = nextInformation;
|
|
15
|
+
|
|
16
|
+
const result = validators[index](input, parameters, get);
|
|
17
|
+
|
|
18
|
+
parameters.information = previousInformation;
|
|
19
|
+
|
|
20
|
+
if (result === true) {
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
parameters.information?.push(...result);
|
|
25
|
+
|
|
26
|
+
allInformation.push(...result);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return allInformation;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {isConstructor} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {instanceOf} from '../helpers/misc.helper';
|
|
3
|
+
import type {Validator} from '../models/validation.model';
|
|
4
|
+
|
|
5
|
+
export function getFunctionValidator(fn: Function): Validator {
|
|
6
|
+
const validator = isConstructor(fn) ? instanceOf(fn) : fn;
|
|
7
|
+
|
|
8
|
+
return input => (validator(input) ? true : []);
|
|
9
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {
|
|
3
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY,
|
|
4
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE,
|
|
5
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE,
|
|
6
|
+
TEMPLATE_PATTERN,
|
|
7
|
+
TYPE_ALL,
|
|
8
|
+
} from '../constants';
|
|
9
|
+
import type {ValueName} from '../models/misc.model';
|
|
10
|
+
import type {NamedValidatorHandlers} from '../models/validation.model';
|
|
11
|
+
|
|
12
|
+
export function getNamedHandlers(original: unknown, prefix: string): NamedValidatorHandlers {
|
|
13
|
+
const handlers: NamedValidatorHandlers = {};
|
|
14
|
+
|
|
15
|
+
if (original == null) {
|
|
16
|
+
return handlers;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!isPlainObject(original)) {
|
|
20
|
+
throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const keys = Object.keys(original);
|
|
24
|
+
const {length} = keys;
|
|
25
|
+
|
|
26
|
+
for (let index = 0; index < length; index += 1) {
|
|
27
|
+
const key = keys[index];
|
|
28
|
+
|
|
29
|
+
if (!TYPE_ALL.has(key as never)) {
|
|
30
|
+
throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const value = original[key];
|
|
34
|
+
|
|
35
|
+
handlers[key as ValueName] = (Array.isArray(value) ? value : [value]).map(item => {
|
|
36
|
+
if (typeof item !== 'function') {
|
|
37
|
+
throw new TypeError(
|
|
38
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key).replace(
|
|
39
|
+
TEMPLATE_PATTERN,
|
|
40
|
+
prefix,
|
|
41
|
+
),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return item;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return handlers;
|
|
50
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {TYPE_OBJECT} from '../constants';
|
|
2
|
+
import {getInvalidValidatorMessage} from '../helpers/message.helper';
|
|
3
|
+
import type {ValueName} from '../models/misc.model';
|
|
4
|
+
import type {
|
|
5
|
+
NamedValidatorHandlers,
|
|
6
|
+
NamedValidators,
|
|
7
|
+
ValidationInformation,
|
|
8
|
+
ValidationInformationKey,
|
|
9
|
+
Validator,
|
|
10
|
+
} from '../models/validation.model';
|
|
11
|
+
|
|
12
|
+
export function getNamedValidator(
|
|
13
|
+
key: ValidationInformationKey,
|
|
14
|
+
name: ValueName,
|
|
15
|
+
handlers: NamedValidatorHandlers,
|
|
16
|
+
): Validator {
|
|
17
|
+
const validator = namedValidators[name];
|
|
18
|
+
|
|
19
|
+
const named = handlers[name] ?? [];
|
|
20
|
+
const {length} = named;
|
|
21
|
+
|
|
22
|
+
return (input, parameters) => {
|
|
23
|
+
if (!validator(input)) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
for (let index = 0; index < length; index += 1) {
|
|
28
|
+
const handler = named[index];
|
|
29
|
+
|
|
30
|
+
if (handler(input) === true) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const information: ValidationInformation = {
|
|
35
|
+
key,
|
|
36
|
+
validator,
|
|
37
|
+
message: getInvalidValidatorMessage(key.full, name, index, length),
|
|
38
|
+
value: input,
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
parameters.information?.push(information);
|
|
42
|
+
|
|
43
|
+
return parameters.reporting.none ? [] : [information];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const namedValidators: NamedValidators = {
|
|
51
|
+
array: Array.isArray,
|
|
52
|
+
bigint: value => typeof value === 'bigint',
|
|
53
|
+
boolean: value => typeof value === 'boolean',
|
|
54
|
+
date: value => value instanceof Date,
|
|
55
|
+
function: value => typeof value === 'function',
|
|
56
|
+
null: value => value === null,
|
|
57
|
+
number: value => typeof value === 'number',
|
|
58
|
+
object: value => typeof value === TYPE_OBJECT && value !== null,
|
|
59
|
+
string: value => typeof value === 'string',
|
|
60
|
+
symbol: value => typeof value === 'symbol',
|
|
61
|
+
undefined: value => value === undefined,
|
|
62
|
+
};
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {join} from '@oscarpalmer/atoms/string';
|
|
4
|
+
import {clone} from '@oscarpalmer/atoms/value/clone';
|
|
5
|
+
import {
|
|
6
|
+
PROPERTY_REQUIRED,
|
|
7
|
+
PROPERTY_TYPE,
|
|
8
|
+
PROPERTY_VALIDATORS,
|
|
9
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY,
|
|
10
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
|
|
11
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE,
|
|
12
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
|
|
13
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
|
|
14
|
+
TEMPLATE_PATTERN,
|
|
15
|
+
TYPE_ALL,
|
|
16
|
+
TYPE_UNDEFINED,
|
|
17
|
+
} from '../constants';
|
|
18
|
+
import {
|
|
19
|
+
getInvalidInputMessage,
|
|
20
|
+
getInvalidMissingMessage,
|
|
21
|
+
getInvalidTypeMessage,
|
|
22
|
+
getUnknownKeysMessage,
|
|
23
|
+
} from '../helpers/message.helper';
|
|
24
|
+
import {isSchematic} from '../helpers/misc.helper';
|
|
25
|
+
import type {ValueName} from '../models/misc.model';
|
|
26
|
+
import {
|
|
27
|
+
type NamedValidatorHandlers,
|
|
28
|
+
SchematicError,
|
|
29
|
+
ValidationError,
|
|
30
|
+
type ValidationInformation,
|
|
31
|
+
type ValidationInformationKey,
|
|
32
|
+
type Validator,
|
|
33
|
+
type ValidatorType,
|
|
34
|
+
} from '../models/validation.model';
|
|
35
|
+
import {getBaseValidator} from './base.validator';
|
|
36
|
+
import {getFunctionValidator} from './function.validator';
|
|
37
|
+
import {getNamedHandlers} from './named.handler';
|
|
38
|
+
import {getNamedValidator} from './named.validator';
|
|
39
|
+
import {getSchematicValidator} from './schematic.validator';
|
|
40
|
+
|
|
41
|
+
function getDisallowedProperty(obj: PlainObject): string | undefined {
|
|
42
|
+
if (PROPERTY_REQUIRED in obj) {
|
|
43
|
+
return PROPERTY_REQUIRED;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (PROPERTY_TYPE in obj) {
|
|
47
|
+
return PROPERTY_TYPE;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (PROPERTY_VALIDATORS in obj) {
|
|
51
|
+
return PROPERTY_VALIDATORS;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function getObjectValidator(
|
|
56
|
+
original: PlainObject,
|
|
57
|
+
origin?: ValidationInformationKey,
|
|
58
|
+
fromType?: boolean,
|
|
59
|
+
): Validator {
|
|
60
|
+
const keys = Object.keys(original);
|
|
61
|
+
const keysLength = keys.length;
|
|
62
|
+
|
|
63
|
+
if (keysLength === 0) {
|
|
64
|
+
throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (fromType ?? false) {
|
|
68
|
+
const property = getDisallowedProperty(original);
|
|
69
|
+
|
|
70
|
+
if (property != null) {
|
|
71
|
+
throw new SchematicError(
|
|
72
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(
|
|
73
|
+
TEMPLATE_PATTERN,
|
|
74
|
+
origin!.full,
|
|
75
|
+
).replace(TEMPLATE_PATTERN, property),
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const set = new Set<string>();
|
|
81
|
+
|
|
82
|
+
const items: {
|
|
83
|
+
key: ValidationInformationKey;
|
|
84
|
+
required: boolean;
|
|
85
|
+
types: ValidatorType[];
|
|
86
|
+
validator: Validator;
|
|
87
|
+
}[] = [];
|
|
88
|
+
|
|
89
|
+
for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
|
|
90
|
+
const key = keys[keyIndex];
|
|
91
|
+
const value = original[key];
|
|
92
|
+
|
|
93
|
+
if (value == null) {
|
|
94
|
+
throw new SchematicError(
|
|
95
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace(
|
|
96
|
+
TEMPLATE_PATTERN,
|
|
97
|
+
join([origin?.full, key], '.'),
|
|
98
|
+
),
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const prefixedKey = origin == null ? key : join([origin.full, key], '.');
|
|
103
|
+
|
|
104
|
+
const fullKey: ValidationInformationKey = {
|
|
105
|
+
full: prefixedKey,
|
|
106
|
+
short: key,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
let handlers: NamedValidatorHandlers = {};
|
|
110
|
+
let required = true;
|
|
111
|
+
let typed = false;
|
|
112
|
+
let types: ValidatorType[];
|
|
113
|
+
|
|
114
|
+
const validators: Validator[] = [];
|
|
115
|
+
|
|
116
|
+
if (isPlainObject(value)) {
|
|
117
|
+
typed = PROPERTY_TYPE in value;
|
|
118
|
+
|
|
119
|
+
const type = typed ? value[PROPERTY_TYPE] : value;
|
|
120
|
+
|
|
121
|
+
handlers = getNamedHandlers(value[PROPERTY_VALIDATORS], prefixedKey);
|
|
122
|
+
required = getRequired(key, value) ?? required;
|
|
123
|
+
|
|
124
|
+
types = Array.isArray(type) ? type : [type];
|
|
125
|
+
} else {
|
|
126
|
+
types = Array.isArray(value) ? value : [value];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (types.length === 0) {
|
|
130
|
+
throw new SchematicError(
|
|
131
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
132
|
+
TEMPLATE_PATTERN,
|
|
133
|
+
prefixedKey,
|
|
134
|
+
).replace(TEMPLATE_PATTERN, String(value)),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const typesLength = types.length;
|
|
139
|
+
|
|
140
|
+
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
141
|
+
const type = types[typeIndex];
|
|
142
|
+
|
|
143
|
+
let validator: Validator;
|
|
144
|
+
|
|
145
|
+
switch (true) {
|
|
146
|
+
case typeof type === 'function':
|
|
147
|
+
validator = getFunctionValidator(type);
|
|
148
|
+
break;
|
|
149
|
+
|
|
150
|
+
case isPlainObject(type):
|
|
151
|
+
validator = getObjectValidator(type, fullKey, typed);
|
|
152
|
+
break;
|
|
153
|
+
|
|
154
|
+
case isSchematic(type):
|
|
155
|
+
validator = getSchematicValidator(type);
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case TYPE_ALL.has(type as ValueName):
|
|
159
|
+
validator = getNamedValidator(fullKey, type as ValueName, handlers);
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
default:
|
|
163
|
+
throw new SchematicError(
|
|
164
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
|
|
165
|
+
TEMPLATE_PATTERN,
|
|
166
|
+
prefixedKey,
|
|
167
|
+
).replace(TEMPLATE_PATTERN, String(type)),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
validators.push(validator);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
set.add(key);
|
|
175
|
+
|
|
176
|
+
items.push({
|
|
177
|
+
types,
|
|
178
|
+
key: fullKey,
|
|
179
|
+
required: required && !types.includes(TYPE_UNDEFINED),
|
|
180
|
+
validator: getBaseValidator(validators),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const validatorsLength = items.length;
|
|
185
|
+
|
|
186
|
+
return (input, parameters, get) => {
|
|
187
|
+
if (!isPlainObject(input)) {
|
|
188
|
+
if (origin != null) {
|
|
189
|
+
return [];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const information: ValidationInformation = {
|
|
193
|
+
key: {
|
|
194
|
+
full: '',
|
|
195
|
+
short: '',
|
|
196
|
+
},
|
|
197
|
+
value: input,
|
|
198
|
+
message: getInvalidInputMessage(input),
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (parameters.reporting.throw) {
|
|
202
|
+
throw new ValidationError([information]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
parameters.information?.push(information);
|
|
206
|
+
|
|
207
|
+
return [information];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (parameters.strict) {
|
|
211
|
+
const inputKeys = Object.keys(input);
|
|
212
|
+
const unknownKeys = inputKeys.filter(key => !set.has(key));
|
|
213
|
+
|
|
214
|
+
if (unknownKeys.length > 0) {
|
|
215
|
+
const information: ValidationInformation = {
|
|
216
|
+
key: origin ?? {
|
|
217
|
+
full: '',
|
|
218
|
+
short: '',
|
|
219
|
+
},
|
|
220
|
+
message: getUnknownKeysMessage(unknownKeys),
|
|
221
|
+
value: input,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
if (parameters.reporting.throw) {
|
|
225
|
+
throw new ValidationError([information]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
parameters.information?.push(information);
|
|
229
|
+
|
|
230
|
+
return [information];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const allInformation: ValidationInformation[] = [];
|
|
235
|
+
const output: PlainObject = {};
|
|
236
|
+
|
|
237
|
+
for (let validatorIndex = 0; validatorIndex < validatorsLength; validatorIndex += 1) {
|
|
238
|
+
const {key, required, types, validator} = items[validatorIndex];
|
|
239
|
+
|
|
240
|
+
const value = (input as PlainObject)[key.short];
|
|
241
|
+
|
|
242
|
+
if (value === undefined) {
|
|
243
|
+
if (required) {
|
|
244
|
+
if (parameters.reporting.none) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const information: ValidationInformation = {
|
|
249
|
+
key,
|
|
250
|
+
value,
|
|
251
|
+
message: getInvalidMissingMessage(key.full, types),
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
if (parameters.reporting.throw) {
|
|
255
|
+
throw new ValidationError([information]);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
parameters.information?.push(information);
|
|
259
|
+
|
|
260
|
+
if (parameters.reporting.all) {
|
|
261
|
+
allInformation.push(information);
|
|
262
|
+
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return [information];
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const previousOutput = parameters.output;
|
|
273
|
+
|
|
274
|
+
parameters.output = output;
|
|
275
|
+
|
|
276
|
+
const result = validator(value, parameters, get);
|
|
277
|
+
|
|
278
|
+
parameters.output = previousOutput;
|
|
279
|
+
|
|
280
|
+
if (result === true) {
|
|
281
|
+
if (get && !isPlainObject(value)) {
|
|
282
|
+
output[key.short] = parameters.clone ? clone(value) : value;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (parameters.reporting.none) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const information: ValidationInformation[] =
|
|
293
|
+
typeof result !== 'boolean' && result.length > 0
|
|
294
|
+
? result
|
|
295
|
+
: [
|
|
296
|
+
{
|
|
297
|
+
key,
|
|
298
|
+
value,
|
|
299
|
+
message: getInvalidTypeMessage(key.full, types, value),
|
|
300
|
+
},
|
|
301
|
+
];
|
|
302
|
+
|
|
303
|
+
if (parameters.reporting.throw) {
|
|
304
|
+
throw new ValidationError(information);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (parameters.reporting.all) {
|
|
308
|
+
allInformation.push(...information);
|
|
309
|
+
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return information;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (get) {
|
|
317
|
+
if (origin == null) {
|
|
318
|
+
parameters.output = output;
|
|
319
|
+
} else {
|
|
320
|
+
parameters.output[origin.short] = output;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return allInformation.length === 0 ? true : allInformation;
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function getRequired(key: string, obj: PlainObject): boolean | undefined {
|
|
329
|
+
if (!(PROPERTY_REQUIRED in obj)) {
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (typeof obj[PROPERTY_REQUIRED] !== 'boolean') {
|
|
334
|
+
throw new SchematicError(
|
|
335
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return obj[PROPERTY_REQUIRED];
|
|
340
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {Validator} from '../models/validation.model';
|
|
3
|
+
import {Schematic, schematicValidator} from '../schematic';
|
|
4
|
+
|
|
5
|
+
export function getSchematicValidator(schematic: Schematic<unknown>): Validator {
|
|
6
|
+
const validator = schematicValidator.get(schematic)!;
|
|
7
|
+
|
|
8
|
+
return (input, parameters, get) => {
|
|
9
|
+
let result: ReturnType<Validator>;
|
|
10
|
+
|
|
11
|
+
if (isPlainObject(input)) {
|
|
12
|
+
result = validator(input, parameters, get);
|
|
13
|
+
} else {
|
|
14
|
+
result = [];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (result === true) {
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
parameters.information?.push(...result);
|
|
22
|
+
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
}
|