@oscarpalmer/jhunal 0.21.0 → 0.22.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.
@@ -0,0 +1,498 @@
1
+ import {join, type PlainObject} from '@oscarpalmer/atoms';
2
+ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
3
+ import {clone} from '@oscarpalmer/atoms/value/clone';
4
+ import {
5
+ PROPERTY_REQUIRED,
6
+ PROPERTY_TYPE,
7
+ PROPERTY_VALIDATORS,
8
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY,
9
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
10
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE,
11
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
12
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
13
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY,
14
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE,
15
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE,
16
+ TEMPLATE_PATTERN,
17
+ TYPE_ALL,
18
+ TYPE_OBJECT,
19
+ TYPE_UNDEFINED,
20
+ } from './constants';
21
+ import {
22
+ getInvalidInputMessage,
23
+ getInvalidMissingMessage,
24
+ getInvalidTypeMessage,
25
+ getInvalidValidatorMessage,
26
+ getUnknownKeysMessage,
27
+ instanceOf,
28
+ isSchematic,
29
+ } from './helpers';
30
+ import type {ValueName} from './models/misc.model';
31
+ import {
32
+ SchematicError,
33
+ ValidationError,
34
+ type NamedValidatorHandlers,
35
+ type NamedValidators,
36
+ type ValidationInformation,
37
+ type ValidationInformationKey,
38
+ type Validator,
39
+ type ValidatorType,
40
+ } from './models/validation.model';
41
+ import {schematicValidator, type Schematic} from './schematic';
42
+
43
+ function getDisallowedProperty(obj: PlainObject): string | undefined {
44
+ if (PROPERTY_REQUIRED in obj) {
45
+ return PROPERTY_REQUIRED;
46
+ }
47
+
48
+ if (PROPERTY_TYPE in obj) {
49
+ return PROPERTY_TYPE;
50
+ }
51
+
52
+ if (PROPERTY_VALIDATORS in obj) {
53
+ return PROPERTY_VALIDATORS;
54
+ }
55
+ }
56
+
57
+ function getFunctionValidator(fn: Function): Validator {
58
+ const validator = isConstructor(fn) ? instanceOf(fn) : fn;
59
+
60
+ return input => validator(input) === true;
61
+ }
62
+
63
+ function getNamedValidator(
64
+ key: ValidationInformationKey,
65
+ name: ValueName,
66
+ handlers: NamedValidatorHandlers,
67
+ ): Validator {
68
+ const validator = namedValidators[name];
69
+
70
+ const named = handlers[name] ?? [];
71
+ const {length} = named;
72
+
73
+ return (input, parameters) => {
74
+ if (!validator(input)) {
75
+ return false;
76
+ }
77
+
78
+ for (let index = 0; index < length; index += 1) {
79
+ const handler = named[index];
80
+
81
+ if (handler(input) === true) {
82
+ continue;
83
+ }
84
+
85
+ const information: ValidationInformation = {
86
+ key,
87
+ validator,
88
+ message: getInvalidValidatorMessage(key.full, name, index, length),
89
+ value: input,
90
+ };
91
+
92
+ parameters.information?.push(information);
93
+
94
+ return parameters.reporting.none ? false : [information];
95
+ }
96
+
97
+ return true;
98
+ };
99
+ }
100
+
101
+ function getNamedHandlers(original: unknown, prefix: string): NamedValidatorHandlers {
102
+ const handlers: NamedValidatorHandlers = {};
103
+
104
+ if (original == null) {
105
+ return handlers;
106
+ }
107
+
108
+ if (!isPlainObject(original)) {
109
+ throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
110
+ }
111
+
112
+ const keys = Object.keys(original);
113
+ const {length} = keys;
114
+
115
+ for (let index = 0; index < length; index += 1) {
116
+ const key = keys[index];
117
+
118
+ if (!TYPE_ALL.has(key as never)) {
119
+ throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
120
+ }
121
+
122
+ const value = (original as PlainObject)[key];
123
+
124
+ handlers[key as ValueName] = (Array.isArray(value) ? value : [value]).map(item => {
125
+ if (typeof item !== 'function') {
126
+ throw new TypeError(
127
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key).replace(
128
+ TEMPLATE_PATTERN,
129
+ prefix,
130
+ ),
131
+ );
132
+ }
133
+
134
+ return item;
135
+ });
136
+ }
137
+
138
+ return handlers;
139
+ }
140
+
141
+ export function getObjectValidator(
142
+ original: PlainObject,
143
+ origin?: ValidationInformationKey,
144
+ fromType?: boolean,
145
+ ): Validator {
146
+ const keys = Object.keys(original);
147
+ const keysLength = keys.length;
148
+
149
+ if (keysLength === 0) {
150
+ throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
151
+ }
152
+
153
+ if (fromType ?? false) {
154
+ const property = getDisallowedProperty(original);
155
+
156
+ if (property != null) {
157
+ throw new SchematicError(
158
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(
159
+ TEMPLATE_PATTERN,
160
+ origin!.full,
161
+ ).replace(TEMPLATE_PATTERN, property),
162
+ );
163
+ }
164
+ }
165
+
166
+ const set = new Set<string>();
167
+
168
+ const items: {
169
+ key: ValidationInformationKey;
170
+ required: boolean;
171
+ types: ValidatorType[];
172
+ validator: Validator;
173
+ }[] = [];
174
+
175
+ for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
176
+ const key = keys[keyIndex];
177
+ const value = original[key];
178
+
179
+ if (value == null) {
180
+ throw new SchematicError(
181
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace(
182
+ TEMPLATE_PATTERN,
183
+ join([origin?.full, key], '.'),
184
+ ),
185
+ );
186
+ }
187
+
188
+ const prefixedKey = origin == null ? key : join([origin.full, key], '.');
189
+
190
+ const fullKey: ValidationInformationKey = {
191
+ full: prefixedKey,
192
+ short: key,
193
+ };
194
+
195
+ let handlers: NamedValidatorHandlers = {};
196
+ let required = true;
197
+ let typed = false;
198
+ let types: ValidatorType[];
199
+
200
+ const validators: Validator[] = [];
201
+
202
+ if (isPlainObject(value)) {
203
+ typed = PROPERTY_TYPE in value;
204
+
205
+ const type = typed ? value[PROPERTY_TYPE] : value;
206
+
207
+ handlers = getNamedHandlers(value[PROPERTY_VALIDATORS], prefixedKey);
208
+ required = getRequired(key, value) ?? required;
209
+
210
+ types = Array.isArray(type) ? type : [type];
211
+ } else {
212
+ types = Array.isArray(value) ? value : [value];
213
+ }
214
+
215
+ if (types.length === 0) {
216
+ throw new SchematicError(
217
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
218
+ TEMPLATE_PATTERN,
219
+ prefixedKey,
220
+ ).replace(TEMPLATE_PATTERN, String(value)),
221
+ );
222
+ }
223
+
224
+ const typesLength = types.length;
225
+
226
+ for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
227
+ const type = types[typeIndex];
228
+
229
+ let validator: Validator;
230
+
231
+ switch (true) {
232
+ case typeof type === 'function':
233
+ validator = getFunctionValidator(type);
234
+ break;
235
+
236
+ case isPlainObject(type):
237
+ validator = getObjectValidator(type, fullKey, typed);
238
+ break;
239
+
240
+ case isSchematic(type):
241
+ validator = getSchematicValidator(type);
242
+ break;
243
+
244
+ case TYPE_ALL.has(type as ValueName):
245
+ validator = getNamedValidator(fullKey, type as ValueName, handlers);
246
+ break;
247
+
248
+ default:
249
+ throw new SchematicError(
250
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
251
+ TEMPLATE_PATTERN,
252
+ prefixedKey,
253
+ ).replace(TEMPLATE_PATTERN, String(type)),
254
+ );
255
+ }
256
+
257
+ validators.push(validator);
258
+ }
259
+
260
+ set.add(key);
261
+
262
+ items.push({
263
+ types,
264
+ key: fullKey,
265
+ required: required && !types.includes(TYPE_UNDEFINED),
266
+ validator: getValidator(validators),
267
+ });
268
+ }
269
+
270
+ const validatorsLength = items.length;
271
+
272
+ return (input, parameters, get) => {
273
+ if (!isPlainObject(input)) {
274
+ if (origin != null) {
275
+ return false;
276
+ }
277
+
278
+ const information: ValidationInformation = {
279
+ key: {
280
+ full: '',
281
+ short: '',
282
+ },
283
+ value: input,
284
+ message: getInvalidInputMessage(input),
285
+ };
286
+
287
+ if (parameters.reporting.throw) {
288
+ throw new ValidationError([information]);
289
+ }
290
+
291
+ parameters.information?.push(information);
292
+
293
+ return parameters.reporting.none ? false : [information];
294
+ }
295
+
296
+ if (parameters.strict) {
297
+ const inputKeys = Object.keys(input);
298
+ const unknownKeys = inputKeys.filter(key => !set.has(key));
299
+
300
+ if (unknownKeys.length > 0) {
301
+ const information: ValidationInformation = {
302
+ key: origin ?? {
303
+ full: '',
304
+ short: '',
305
+ },
306
+ message: getUnknownKeysMessage(unknownKeys),
307
+ value: input,
308
+ };
309
+
310
+ if (parameters.reporting.throw) {
311
+ throw new ValidationError([information]);
312
+ }
313
+
314
+ parameters.information?.push(information);
315
+
316
+ return parameters.reporting.none ? false : [information];
317
+ }
318
+ }
319
+
320
+ const allInformation: ValidationInformation[] = [];
321
+ const output: PlainObject = {};
322
+
323
+ for (let validatorIndex = 0; validatorIndex < validatorsLength; validatorIndex += 1) {
324
+ const {key, required, types, validator} = items[validatorIndex];
325
+
326
+ const value = (input as PlainObject)[key.short];
327
+
328
+ if (value === undefined) {
329
+ if (required) {
330
+ if (parameters.reporting.none) {
331
+ return false;
332
+ }
333
+
334
+ const information: ValidationInformation = {
335
+ key,
336
+ value,
337
+ message: getInvalidMissingMessage(key.full, types),
338
+ };
339
+
340
+ if (parameters.reporting.throw) {
341
+ throw new ValidationError([information]);
342
+ }
343
+
344
+ parameters.information?.push(information);
345
+
346
+ if (parameters.reporting.all) {
347
+ allInformation.push(information);
348
+
349
+ continue;
350
+ }
351
+
352
+ return [information];
353
+ }
354
+
355
+ continue;
356
+ }
357
+
358
+ const previousOutput = parameters.output;
359
+
360
+ parameters.output = output;
361
+
362
+ const result = validator(value, parameters, get);
363
+
364
+ parameters.output = previousOutput;
365
+
366
+ if (result === false) {
367
+ continue;
368
+ }
369
+
370
+ if (result === true) {
371
+ if (get) {
372
+ output[key.short] = clone(value);
373
+ }
374
+
375
+ continue;
376
+ }
377
+
378
+ if (parameters.reporting.none) {
379
+ return false;
380
+ }
381
+
382
+ const information: ValidationInformation[] =
383
+ typeof result !== 'boolean' && result.length > 0
384
+ ? result
385
+ : [
386
+ {
387
+ key,
388
+ value,
389
+ message: getInvalidTypeMessage(key.full, types, value),
390
+ },
391
+ ];
392
+
393
+ if (parameters.reporting.throw) {
394
+ throw new ValidationError(information);
395
+ }
396
+
397
+ if (parameters.reporting.all) {
398
+ allInformation.push(...information);
399
+
400
+ continue;
401
+ }
402
+
403
+ return information;
404
+ }
405
+
406
+ if (get) {
407
+ if (origin == null) {
408
+ parameters.output = output;
409
+ } else {
410
+ parameters.output[origin.short] = output;
411
+ }
412
+ }
413
+
414
+ return parameters.reporting.none || allInformation.length === 0 ? true : allInformation;
415
+ };
416
+ }
417
+
418
+ function getRequired(key: string, obj: PlainObject): boolean | undefined {
419
+ if (!(PROPERTY_REQUIRED in obj)) {
420
+ return;
421
+ }
422
+
423
+ if (typeof obj[PROPERTY_REQUIRED] !== 'boolean') {
424
+ throw new SchematicError(
425
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
426
+ );
427
+ }
428
+
429
+ return obj[PROPERTY_REQUIRED];
430
+ }
431
+
432
+ function getSchematicValidator(schematic: Schematic<unknown>): Validator {
433
+ const validator = schematicValidator.get(schematic)!;
434
+
435
+ return (input, parameters, get) => {
436
+ let result: ReturnType<Validator> = false;
437
+
438
+ if (isPlainObject(input)) {
439
+ result = validator(input, parameters, get);
440
+ }
441
+
442
+ if (typeof result === 'boolean') {
443
+ return result;
444
+ }
445
+
446
+ parameters.information?.push(...result);
447
+
448
+ return result.length === 0 ? true : result;
449
+ };
450
+ }
451
+
452
+ function getValidator(validators: Validator[]): Validator {
453
+ const {length} = validators;
454
+
455
+ return (input, parameters, get) => {
456
+ const allInformation: ValidationInformation[] = [];
457
+
458
+ for (let index = 0; index < length; index += 1) {
459
+ const previousInformation = parameters.information;
460
+
461
+ const nextInformation: ValidationInformation[] = [];
462
+
463
+ parameters.information = nextInformation;
464
+
465
+ const result = validators[index](input, parameters, get);
466
+
467
+ parameters.information = previousInformation;
468
+
469
+ if (result === false) {
470
+ continue;
471
+ }
472
+
473
+ if (result === true) {
474
+ return true;
475
+ }
476
+
477
+ parameters.information?.push(...result);
478
+
479
+ allInformation.push(...result);
480
+ }
481
+
482
+ return allInformation;
483
+ };
484
+ }
485
+
486
+ const namedValidators: NamedValidators = {
487
+ array: Array.isArray,
488
+ bigint: value => typeof value === 'bigint',
489
+ boolean: value => typeof value === 'boolean',
490
+ date: value => value instanceof Date,
491
+ function: value => typeof value === 'function',
492
+ null: value => value === null,
493
+ number: value => typeof value === 'number',
494
+ object: value => typeof value === TYPE_OBJECT && value !== null,
495
+ string: value => typeof value === 'string',
496
+ symbol: value => typeof value === 'symbol',
497
+ undefined: value => value === undefined,
498
+ };
@@ -1,7 +0,0 @@
1
- import { ValidatedProperty } from "../models/validation.model.mjs";
2
- import { PlainObject } from "@oscarpalmer/atoms/models";
3
-
4
- //#region src/validation/property.validation.d.ts
5
- declare function getProperties(original: PlainObject, prefix?: string, fromType?: boolean): ValidatedProperty[];
6
- //#endregion
7
- export { getProperties };
@@ -1,92 +0,0 @@
1
- import { PROPERTY_REQUIRED, PROPERTY_TYPE, PROPERTY_VALIDATORS, SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED, SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE, SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE, TYPE_ALL, TYPE_UNDEFINED, VALIDATABLE_TYPES } from "../constants.mjs";
2
- import { instanceOf, isSchematic } from "../helpers.mjs";
3
- import { SchematicError } from "../models/validation.model.mjs";
4
- import { isConstructor, isPlainObject } from "@oscarpalmer/atoms/is";
5
- import { join } from "@oscarpalmer/atoms/string";
6
- //#region src/validation/property.validation.ts
7
- function getDisallowedProperty(obj) {
8
- if ("$required" in obj) return PROPERTY_REQUIRED;
9
- if ("$type" in obj) return PROPERTY_TYPE;
10
- if ("$validators" in obj) return PROPERTY_VALIDATORS;
11
- }
12
- function getProperties(original, prefix, fromType) {
13
- if (Object.keys(original).length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
14
- if (fromType ?? false) {
15
- const property = getDisallowedProperty(original);
16
- if (property != null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace("<>", prefix).replace("<>", property));
17
- }
18
- const keys = Object.keys(original);
19
- const keysLength = keys.length;
20
- const properties = [];
21
- for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
22
- const key = keys[keyIndex];
23
- const value = original[key];
24
- if (value == null) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace("<>", join([prefix, key], ".")));
25
- const types = [];
26
- let required = true;
27
- let validators = {};
28
- if (isPlainObject(value)) {
29
- required = getRequired(key, value) ?? required;
30
- validators = getValidators(value[PROPERTY_VALIDATORS]);
31
- const hasType = PROPERTY_TYPE in value;
32
- types.push(...getTypes(key, hasType ? value[PROPERTY_TYPE] : value, prefix, hasType));
33
- } else types.push(...getTypes(key, value, prefix));
34
- if (!required && !types.includes("undefined")) types.push(TYPE_UNDEFINED);
35
- properties.push({
36
- key,
37
- types,
38
- validators,
39
- required: required && !types.includes("undefined")
40
- });
41
- }
42
- return properties;
43
- }
44
- function getRequired(key, obj) {
45
- if (!("$required" in obj)) return;
46
- if (typeof obj["$required"] !== "boolean") throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace("<>", key));
47
- return obj[PROPERTY_REQUIRED];
48
- }
49
- function getTypes(key, original, prefix, fromType) {
50
- const array = Array.isArray(original) ? original : [original];
51
- const { length } = array;
52
- const types = [];
53
- for (let index = 0; index < length; index += 1) {
54
- const value = array[index];
55
- switch (true) {
56
- case typeof value === "function":
57
- types.push(isConstructor(value) ? instanceOf(value) : value);
58
- break;
59
- case isPlainObject(value):
60
- types.push(getProperties(value, join([prefix, key], "."), fromType));
61
- break;
62
- case isSchematic(value):
63
- types.push(value);
64
- break;
65
- case TYPE_ALL.has(value):
66
- types.push(value);
67
- break;
68
- default: throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
69
- }
70
- }
71
- if (types.length === 0) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace("<>", join([prefix, key], ".")));
72
- return types;
73
- }
74
- function getValidators(original) {
75
- const validators = {};
76
- if (original == null) return validators;
77
- if (!isPlainObject(original)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
78
- const keys = Object.keys(original);
79
- const { length } = keys;
80
- for (let index = 0; index < length; index += 1) {
81
- const key = keys[index];
82
- if (!VALIDATABLE_TYPES.has(key)) throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace("<>", key));
83
- const value = original[key];
84
- validators[key] = (Array.isArray(value) ? value : [value]).map((item) => {
85
- if (typeof item !== "function") throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace("<>", key));
86
- return item;
87
- });
88
- }
89
- return validators;
90
- }
91
- //#endregion
92
- export { getProperties };
@@ -1,7 +0,0 @@
1
- import { ValidatedProperty, ValidationInformation, ValidationParameters } from "../models/validation.model.mjs";
2
- import { PlainObject } from "@oscarpalmer/atoms/models";
3
-
4
- //#region src/validation/value.validation.d.ts
5
- declare function validateObject(obj: unknown, properties: ValidatedProperty[], parameters: ValidationParameters, get: boolean): PlainObject | ValidationInformation[] | undefined;
6
- //#endregion
7
- export { validateObject };