@oscarpalmer/jhunal 0.22.0 → 0.24.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 +11 -4
- package/dist/constants.mjs +27 -8
- package/dist/helpers/message.helper.d.mts +17 -0
- package/dist/helpers/message.helper.mjs +92 -0
- package/dist/helpers/misc.helper.d.mts +22 -0
- package/dist/helpers/misc.helper.mjs +56 -0
- package/dist/index.d.mts +309 -292
- package/dist/index.mjs +237 -166
- package/dist/models/schema.plain.model.d.mts +2 -0
- package/dist/models/schema.typed.model.d.mts +3 -17
- package/dist/models/validation.model.d.mts +28 -7
- package/dist/schematic.d.mts +25 -7
- package/dist/schematic.mjs +6 -4
- 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 +23 -0
- package/dist/validator/named.validator.d.mts +7 -0
- package/dist/validator/named.validator.mjs +38 -0
- package/dist/validator/object.validator.d.mts +7 -0
- package/dist/validator/object.validator.mjs +185 -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 +34 -6
- package/src/helpers/message.helper.ts +217 -0
- package/src/helpers/misc.helper.ts +92 -0
- package/src/index.ts +3 -3
- package/src/models/schema.plain.model.ts +2 -0
- package/src/models/schema.typed.model.ts +4 -22
- package/src/models/validation.model.ts +31 -6
- package/src/schematic.ts +43 -16
- package/src/validator/base.validator.ts +31 -0
- package/src/validator/function.validator.ts +9 -0
- package/src/validator/named.handler.ts +65 -0
- package/src/validator/named.validator.ts +61 -0
- package/src/validator/object.validator.ts +366 -0
- package/src/validator/schematic.validator.ts +25 -0
- package/dist/helpers.d.mts +0 -28
- package/dist/helpers.mjs +0 -120
- package/dist/validation.d.mts +0 -7
- package/dist/validation.mjs +0 -245
- package/src/helpers.ts +0 -249
- package/src/validation.ts +0 -498
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {
|
|
3
|
+
COMMA,
|
|
4
|
+
CONJUNCTION_AND,
|
|
5
|
+
CONJUNCTION_AND_COMMA,
|
|
6
|
+
CONJUNCTION_OR,
|
|
7
|
+
CONJUNCTION_OR_COMMA,
|
|
8
|
+
PREFIXED_TYPES,
|
|
9
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_REQUIRED,
|
|
10
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_TYPE,
|
|
11
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
|
|
12
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE,
|
|
13
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
|
|
14
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
|
|
15
|
+
TEMPLATE_PATTERN,
|
|
16
|
+
TYPE_ALL,
|
|
17
|
+
TYPE_ARRAY,
|
|
18
|
+
TYPE_FUNCTION_RESULT,
|
|
19
|
+
TYPE_NULL,
|
|
20
|
+
TYPE_OBJECT,
|
|
21
|
+
VALIDATION_MESSAGE_INVALID_INPUT,
|
|
22
|
+
VALIDATION_MESSAGE_INVALID_REQUIRED,
|
|
23
|
+
VALIDATION_MESSAGE_INVALID_TYPE,
|
|
24
|
+
VALIDATION_MESSAGE_INVALID_VALUE,
|
|
25
|
+
VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX,
|
|
26
|
+
VALIDATION_MESSAGE_UNKNOWN_KEYS,
|
|
27
|
+
} from '../constants';
|
|
28
|
+
import type {ValueName} from '../models/misc.model';
|
|
29
|
+
import type {ValidatorType} from '../models/validation.model';
|
|
30
|
+
|
|
31
|
+
// #region Defaults
|
|
32
|
+
|
|
33
|
+
export function getDefaultRequiredMessage(key: string): string {
|
|
34
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_REQUIRED.replace(TEMPLATE_PATTERN, key);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getDefaultTypeMessage(key: string, types: ValidatorType[]): string {
|
|
38
|
+
let message = SCHEMATIC_MESSAGE_SCHEMA_INVALID_DEFAULT_TYPE.replace(TEMPLATE_PATTERN, key);
|
|
39
|
+
|
|
40
|
+
message = message.replace(TEMPLATE_PATTERN, renderTypes(types));
|
|
41
|
+
|
|
42
|
+
return message;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// #endregion
|
|
46
|
+
|
|
47
|
+
// #region Disallowed
|
|
48
|
+
|
|
49
|
+
export function getDisallowedMessage(key: string, property: string): string {
|
|
50
|
+
let message = SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(TEMPLATE_PATTERN, key);
|
|
51
|
+
|
|
52
|
+
message = message.replace(TEMPLATE_PATTERN, property);
|
|
53
|
+
|
|
54
|
+
return message;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// #endregion
|
|
58
|
+
|
|
59
|
+
// #region Input
|
|
60
|
+
|
|
61
|
+
export function getInputTypeMessage(actual: unknown): string {
|
|
62
|
+
return VALIDATION_MESSAGE_INVALID_INPUT.replace(TEMPLATE_PATTERN, getValueType(actual));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function getInputPropertyMissingMessage(key: string, types: ValidatorType[]): string {
|
|
66
|
+
let message = VALIDATION_MESSAGE_INVALID_REQUIRED.replace(TEMPLATE_PATTERN, renderTypes(types));
|
|
67
|
+
|
|
68
|
+
message = message.replace(TEMPLATE_PATTERN, key);
|
|
69
|
+
|
|
70
|
+
return message;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function getInputPropertyTypeMessage(
|
|
74
|
+
key: string,
|
|
75
|
+
types: ValidatorType[],
|
|
76
|
+
actual: unknown,
|
|
77
|
+
): string {
|
|
78
|
+
let message = VALIDATION_MESSAGE_INVALID_TYPE.replace(TEMPLATE_PATTERN, renderTypes(types));
|
|
79
|
+
|
|
80
|
+
message = message.replace(TEMPLATE_PATTERN, key);
|
|
81
|
+
message = message.replace(TEMPLATE_PATTERN, getValueType(actual));
|
|
82
|
+
|
|
83
|
+
return message;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function getInputPropertyValidatorMessage(
|
|
87
|
+
key: string,
|
|
88
|
+
type: ValueName,
|
|
89
|
+
index: number,
|
|
90
|
+
length: number,
|
|
91
|
+
): string {
|
|
92
|
+
let message = VALIDATION_MESSAGE_INVALID_VALUE.replace(TEMPLATE_PATTERN, key);
|
|
93
|
+
|
|
94
|
+
message = message.replace(TEMPLATE_PATTERN, type);
|
|
95
|
+
|
|
96
|
+
if (length > 1) {
|
|
97
|
+
message += VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX.replace(TEMPLATE_PATTERN, String(index));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return message;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// #endregion
|
|
104
|
+
|
|
105
|
+
// #region Schematic
|
|
106
|
+
|
|
107
|
+
export function getSchematicPropertyNullableMessage(key: string): string {
|
|
108
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace(TEMPLATE_PATTERN, key);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function getSchematicPropertyTypeMessage(key: string): string {
|
|
112
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, key);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// #endregion
|
|
116
|
+
|
|
117
|
+
// #region Misc.
|
|
118
|
+
|
|
119
|
+
function getPropertyType(type: ValidatorType): string {
|
|
120
|
+
switch (true) {
|
|
121
|
+
case typeof type === 'function':
|
|
122
|
+
return isConstructor(type) ? type.name : TYPE_FUNCTION_RESULT;
|
|
123
|
+
|
|
124
|
+
case TYPE_ALL.has(type as ValueName):
|
|
125
|
+
return PREFIXED_TYPES[type as ValueName];
|
|
126
|
+
|
|
127
|
+
default:
|
|
128
|
+
return PREFIXED_TYPES[TYPE_OBJECT];
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function getValueType(value: unknown): string {
|
|
133
|
+
const valueType = typeof value;
|
|
134
|
+
|
|
135
|
+
switch (true) {
|
|
136
|
+
case value === null:
|
|
137
|
+
return TYPE_NULL;
|
|
138
|
+
|
|
139
|
+
case Array.isArray(value):
|
|
140
|
+
return PREFIXED_TYPES[TYPE_ARRAY];
|
|
141
|
+
|
|
142
|
+
case isPlainObject(value):
|
|
143
|
+
return PREFIXED_TYPES[TYPE_OBJECT];
|
|
144
|
+
|
|
145
|
+
case valueType !== TYPE_OBJECT:
|
|
146
|
+
return PREFIXED_TYPES[valueType as ValueName];
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
return (value as object).constructor.name;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function renderKeys(keys: string[]): string {
|
|
154
|
+
return renderParts(
|
|
155
|
+
keys.map(key => `'${key}'`),
|
|
156
|
+
CONJUNCTION_AND,
|
|
157
|
+
CONJUNCTION_AND_COMMA,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function renderParts(parts: string[], delimiterShort: string, delimiterLong: string): string {
|
|
162
|
+
const {length} = parts;
|
|
163
|
+
|
|
164
|
+
if (length === 1) {
|
|
165
|
+
return parts[0];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let rendered = '';
|
|
169
|
+
|
|
170
|
+
for (let index = 0; index < length; index += 1) {
|
|
171
|
+
rendered += parts[index];
|
|
172
|
+
|
|
173
|
+
if (index < length - 2) {
|
|
174
|
+
rendered += COMMA;
|
|
175
|
+
} else if (index === length - 2) {
|
|
176
|
+
rendered += parts.length > 2 ? delimiterLong : delimiterShort;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return rendered;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderTypes(types: ValidatorType[]): string {
|
|
184
|
+
const unique = new Set<string>();
|
|
185
|
+
const parts: string[] = [];
|
|
186
|
+
|
|
187
|
+
for (let index = 0; index < types.length; index += 1) {
|
|
188
|
+
const rendered = getPropertyType(types[index]);
|
|
189
|
+
|
|
190
|
+
if (unique.has(rendered)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
unique.add(rendered);
|
|
195
|
+
parts.push(rendered);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// #endregion
|
|
202
|
+
|
|
203
|
+
// #region Required
|
|
204
|
+
|
|
205
|
+
export function getRequiredMessage(key: string): string {
|
|
206
|
+
return SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// #endregion
|
|
210
|
+
|
|
211
|
+
// #region Strictness
|
|
212
|
+
|
|
213
|
+
export function getUnknownKeysMessage(keys: string[]): string {
|
|
214
|
+
return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace(TEMPLATE_PATTERN, renderKeys(keys));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// #endregion
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import type {Constructor} from '@oscarpalmer/atoms/models';
|
|
3
|
+
import {
|
|
4
|
+
MESSAGE_CONSTRUCTOR,
|
|
5
|
+
PROPERTY_SCHEMATIC,
|
|
6
|
+
REPORTING_ALL,
|
|
7
|
+
REPORTING_FIRST,
|
|
8
|
+
REPORTING_NONE,
|
|
9
|
+
REPORTING_THROW,
|
|
10
|
+
REPORTING_TYPES,
|
|
11
|
+
} from '../constants';
|
|
12
|
+
import type {
|
|
13
|
+
ReportingInformation,
|
|
14
|
+
ReportingType,
|
|
15
|
+
ValidatorParameters,
|
|
16
|
+
} from '../models/validation.model';
|
|
17
|
+
import type {Schematic} from '../schematic';
|
|
18
|
+
|
|
19
|
+
export function getParameters(input?: unknown): ValidatorParameters {
|
|
20
|
+
if (typeof input === 'boolean') {
|
|
21
|
+
return {
|
|
22
|
+
clone: true,
|
|
23
|
+
output: {},
|
|
24
|
+
reporting: getReporting(),
|
|
25
|
+
strict: input,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (REPORTING_TYPES.has(input as ReportingType)) {
|
|
30
|
+
return {
|
|
31
|
+
clone: true,
|
|
32
|
+
output: {},
|
|
33
|
+
reporting: getReporting(input as ReportingType),
|
|
34
|
+
strict: false,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const options = isPlainObject(input) ? input : {};
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
clone: typeof options.clone === 'boolean' ? options.clone : true,
|
|
42
|
+
output: {},
|
|
43
|
+
reporting: getReporting(options.errors),
|
|
44
|
+
strict: typeof options.strict === 'boolean' ? options.strict : false,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getReporting(value?: unknown): ReportingInformation {
|
|
49
|
+
const type = REPORTING_TYPES.has(value as ReportingType)
|
|
50
|
+
? (value as ReportingType)
|
|
51
|
+
: REPORTING_NONE;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
type,
|
|
55
|
+
[REPORTING_ALL]: type === REPORTING_ALL,
|
|
56
|
+
[REPORTING_FIRST]: type === REPORTING_FIRST,
|
|
57
|
+
[REPORTING_NONE]: type === REPORTING_NONE,
|
|
58
|
+
[REPORTING_THROW]: type === REPORTING_THROW,
|
|
59
|
+
} as ReportingInformation;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a validator function for a given constructor
|
|
64
|
+
* @param constructor - Constructor to check against
|
|
65
|
+
* @throws Will throw a `TypeError` if the provided argument is not a valid constructor
|
|
66
|
+
* @returns Validator function that checks if a value is an instance of the constructor
|
|
67
|
+
*/
|
|
68
|
+
export function instanceOf<Instance>(
|
|
69
|
+
constructor: Constructor<Instance>,
|
|
70
|
+
): (value: unknown) => value is Instance {
|
|
71
|
+
if (!isConstructor(constructor)) {
|
|
72
|
+
throw new TypeError(MESSAGE_CONSTRUCTOR);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (value: unknown): value is Instance => {
|
|
76
|
+
return value instanceof constructor;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Is the value a schematic?
|
|
82
|
+
* @param value Value to check
|
|
83
|
+
* @returns `true` if the value is a schematic, `false` otherwise
|
|
84
|
+
*/
|
|
85
|
+
export function isSchematic(value: unknown): value is Schematic<never> {
|
|
86
|
+
return (
|
|
87
|
+
typeof value === 'object' &&
|
|
88
|
+
value !== null &&
|
|
89
|
+
PROPERTY_SCHEMATIC in value &&
|
|
90
|
+
value[PROPERTY_SCHEMATIC] === true
|
|
91
|
+
);
|
|
92
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export {instanceOf, isSchematic} from './helpers';
|
|
1
|
+
export {instanceOf, isSchematic} from './helpers/misc.helper';
|
|
2
2
|
export type {Schema} from './models/schema.plain.model';
|
|
3
3
|
export type {TypedSchema} from './models/schema.typed.model';
|
|
4
4
|
export {
|
|
5
5
|
SchematicError,
|
|
6
6
|
ValidationError,
|
|
7
|
-
type
|
|
8
|
-
type
|
|
7
|
+
type GetOptions,
|
|
8
|
+
type IsOptions,
|
|
9
9
|
} from './models/validation.model';
|
|
10
10
|
export {schematic, type Schematic} from './schematic';
|
|
@@ -8,6 +8,7 @@ import type {ExtractValueNames, ValueName, Values} from './misc.model';
|
|
|
8
8
|
export type PlainSchema = {
|
|
9
9
|
[key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
|
|
10
10
|
} & {
|
|
11
|
+
$default?: never;
|
|
11
12
|
$required?: never;
|
|
12
13
|
$type?: never;
|
|
13
14
|
$validators?: never;
|
|
@@ -56,6 +57,7 @@ export type SchemaEntry =
|
|
|
56
57
|
* ```
|
|
57
58
|
*/
|
|
58
59
|
export type SchemaProperty = {
|
|
60
|
+
$default?: unknown;
|
|
59
61
|
/**
|
|
60
62
|
* Whether the property is required _(defaults to `true`)_
|
|
61
63
|
*/
|
|
@@ -17,6 +17,7 @@ import type {ToSchemaPropertyType, ToSchemaType} from './transform.model';
|
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
19
|
export type TypedPropertyOptional<Value> = {
|
|
20
|
+
$default?: never;
|
|
20
21
|
$required: false;
|
|
21
22
|
$type: ToSchemaPropertyType<Exclude<Value, undefined>>;
|
|
22
23
|
$validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
|
|
@@ -35,6 +36,7 @@ export type TypedPropertyOptional<Value> = {
|
|
|
35
36
|
* ```
|
|
36
37
|
*/
|
|
37
38
|
export type TypedPropertyRequired<Value> = {
|
|
39
|
+
$default?: unknown;
|
|
38
40
|
$required?: true;
|
|
39
41
|
$type: ToSchemaPropertyType<Value>;
|
|
40
42
|
$validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
|
|
@@ -61,31 +63,11 @@ export type TypedPropertyRequired<Value> = {
|
|
|
61
63
|
export type TypedSchema<Model extends PlainObject> = Simplify<
|
|
62
64
|
{
|
|
63
65
|
[Key in RequiredKeys<Model>]: Model[Key] extends PlainObject
|
|
64
|
-
?
|
|
66
|
+
? Schematic<Model[Key]>
|
|
65
67
|
: ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
|
|
66
68
|
} & {
|
|
67
69
|
[Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject
|
|
68
|
-
?
|
|
69
|
-
| TypedSchemaOptional<Exclude<Model[Key], undefined>>
|
|
70
|
-
| Schematic<Exclude<Model[Key], undefined>>
|
|
70
|
+
? Schematic<Exclude<Model[Key], undefined>>
|
|
71
71
|
: TypedPropertyOptional<Model[Key]>;
|
|
72
72
|
}
|
|
73
73
|
>;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
|
|
77
|
-
*
|
|
78
|
-
* @template Model Nested object type
|
|
79
|
-
*/
|
|
80
|
-
type TypedSchemaOptional<Model extends PlainObject> = {
|
|
81
|
-
$required: false;
|
|
82
|
-
} & TypedSchema<Model>;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
|
|
86
|
-
*
|
|
87
|
-
* @template Model Nested object type
|
|
88
|
-
*/
|
|
89
|
-
type TypedSchemaRequired<Model extends PlainObject> = {
|
|
90
|
-
$required?: true;
|
|
91
|
-
} & TypedSchema<Model>;
|
|
@@ -91,20 +91,32 @@ export type ValidationInformationKey = {
|
|
|
91
91
|
|
|
92
92
|
// #region Options
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
* Options for validation
|
|
96
|
-
*/
|
|
97
|
-
export type ValidationOptions<Errors extends ReportingType> = {
|
|
94
|
+
type BaseOptions<Errors extends ReportingType> = {
|
|
98
95
|
/**
|
|
99
96
|
* How should validation failures be reported; see {@link ReportingType} _(defaults to `'none'`)_
|
|
100
97
|
*/
|
|
101
|
-
errors
|
|
98
|
+
errors: Errors;
|
|
102
99
|
/**
|
|
103
100
|
* Validate if unknown keys are present in the object? _(defaults to `false`)_
|
|
104
101
|
*/
|
|
105
102
|
strict?: boolean;
|
|
106
103
|
};
|
|
107
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Options for validating and getting a value from an input
|
|
107
|
+
*/
|
|
108
|
+
export type GetOptions<Errors extends ReportingType> = BaseOptions<Errors> & {
|
|
109
|
+
/**
|
|
110
|
+
* Get a deeply cloned version of the input? _(defaults to `true`)_
|
|
111
|
+
*/
|
|
112
|
+
clone?: boolean;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Options for validation an input value
|
|
117
|
+
*/
|
|
118
|
+
export type IsOptions<Errors extends ReportingType> = BaseOptions<Errors>;
|
|
119
|
+
|
|
108
120
|
// #endregion
|
|
109
121
|
|
|
110
122
|
// #region Validator
|
|
@@ -113,9 +125,22 @@ export type Validator = (
|
|
|
113
125
|
input: unknown,
|
|
114
126
|
parameters: ValidatorParameters,
|
|
115
127
|
get: boolean,
|
|
116
|
-
) =>
|
|
128
|
+
) => true | ValidationInformation[];
|
|
129
|
+
|
|
130
|
+
export type ValidatorDefaults = {
|
|
131
|
+
value: unknown;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export type ValidatorItem = {
|
|
135
|
+
defaults: ValidatorDefaults | undefined;
|
|
136
|
+
key: ValidationInformationKey;
|
|
137
|
+
required: boolean;
|
|
138
|
+
types: ValidatorType[];
|
|
139
|
+
validator: Validator;
|
|
140
|
+
};
|
|
117
141
|
|
|
118
142
|
export type ValidatorParameters = {
|
|
143
|
+
clone: boolean;
|
|
119
144
|
information?: ValidationInformation[];
|
|
120
145
|
output: PlainObject;
|
|
121
146
|
reporting: ReportingInformation;
|
package/src/schematic.ts
CHANGED
|
@@ -3,17 +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 GetOptions,
|
|
13
|
+
type IsOptions,
|
|
12
14
|
type ValidationInformation,
|
|
13
|
-
type ValidationOptions,
|
|
14
15
|
type Validator,
|
|
15
16
|
} from './models/validation.model';
|
|
16
|
-
import {getObjectValidator} from './
|
|
17
|
+
import {getObjectValidator} from './validator/object.validator';
|
|
17
18
|
|
|
18
19
|
/**
|
|
19
20
|
* A schematic for validating objects
|
|
@@ -41,7 +42,7 @@ export class Schematic<Model> {
|
|
|
41
42
|
* @param options Validation options
|
|
42
43
|
* @returns Deeply cloned version of the value if it matches the schema, otherwise throws an error
|
|
43
44
|
*/
|
|
44
|
-
get(value: unknown, options:
|
|
45
|
+
get(value: unknown, options: GetOptions<'throw'>): Model;
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
48
|
* Parse a value according to the schema
|
|
@@ -61,7 +62,7 @@ export class Schematic<Model> {
|
|
|
61
62
|
* @param options Validation options
|
|
62
63
|
* @returns Result holding deeply cloned value or all validation information
|
|
63
64
|
*/
|
|
64
|
-
get(value: unknown, options:
|
|
65
|
+
get(value: unknown, options: GetOptions<'all'>): Result<Model, ValidationInformation[]>;
|
|
65
66
|
|
|
66
67
|
/**
|
|
67
68
|
* Parse a value according to the schema
|
|
@@ -81,7 +82,7 @@ export class Schematic<Model> {
|
|
|
81
82
|
* @param options Validation options
|
|
82
83
|
* @returns Result holding deeply cloned value or all validation information
|
|
83
84
|
*/
|
|
84
|
-
get(value: unknown, options:
|
|
85
|
+
get(value: unknown, options: GetOptions<'first'>): Result<Model, ValidationInformation>;
|
|
85
86
|
|
|
86
87
|
/**
|
|
87
88
|
* Parse a value according to the schema
|
|
@@ -93,6 +94,16 @@ export class Schematic<Model> {
|
|
|
93
94
|
*/
|
|
94
95
|
get(value: unknown, errors: 'first'): Result<Model, ValidationInformation>;
|
|
95
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
|
+
|
|
96
107
|
/**
|
|
97
108
|
* Parse a value according to the schema
|
|
98
109
|
*
|
|
@@ -108,14 +119,16 @@ export class Schematic<Model> {
|
|
|
108
119
|
|
|
109
120
|
const result = this.#validator(value, parameters, true);
|
|
110
121
|
|
|
111
|
-
if (
|
|
112
|
-
return parameters.reporting.none
|
|
113
|
-
?
|
|
114
|
-
? parameters.output
|
|
115
|
-
: undefined
|
|
122
|
+
if (result === true) {
|
|
123
|
+
return parameters.reporting.none || parameters.reporting.throw
|
|
124
|
+
? parameters.output
|
|
116
125
|
: ok(parameters.output);
|
|
117
126
|
}
|
|
118
127
|
|
|
128
|
+
if (parameters.reporting.none) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
119
132
|
return error(parameters.reporting.all ? result : result[0]);
|
|
120
133
|
}
|
|
121
134
|
|
|
@@ -127,7 +140,7 @@ export class Schematic<Model> {
|
|
|
127
140
|
* @param options Validation options
|
|
128
141
|
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
129
142
|
*/
|
|
130
|
-
is(value: unknown, options:
|
|
143
|
+
is(value: unknown, options: IsOptions<'throw'>): asserts value is Model;
|
|
131
144
|
|
|
132
145
|
/**
|
|
133
146
|
* Does the value match the schema?
|
|
@@ -147,7 +160,7 @@ export class Schematic<Model> {
|
|
|
147
160
|
* @param options Validation options
|
|
148
161
|
* @returns Result holding `true` or all validation information
|
|
149
162
|
*/
|
|
150
|
-
is(value: unknown, options:
|
|
163
|
+
is(value: unknown, options: IsOptions<'all'>): Result<true, ValidationInformation[]>;
|
|
151
164
|
|
|
152
165
|
/**
|
|
153
166
|
* Does the value match the schema?
|
|
@@ -167,7 +180,7 @@ export class Schematic<Model> {
|
|
|
167
180
|
* @param options Validation options
|
|
168
181
|
* @returns `true` if the value matches the schema, otherwise `false`
|
|
169
182
|
*/
|
|
170
|
-
is(value: unknown, options:
|
|
183
|
+
is(value: unknown, options: IsOptions<'first'>): Result<true, ValidationInformation>;
|
|
171
184
|
|
|
172
185
|
/**
|
|
173
186
|
* Does the value match the schema?
|
|
@@ -179,6 +192,16 @@ export class Schematic<Model> {
|
|
|
179
192
|
*/
|
|
180
193
|
is(value: unknown, errors: 'first'): Result<true, ValidationInformation>;
|
|
181
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
|
+
|
|
182
205
|
/**
|
|
183
206
|
* Does the value match the schema?
|
|
184
207
|
*
|
|
@@ -194,8 +217,12 @@ export class Schematic<Model> {
|
|
|
194
217
|
|
|
195
218
|
const result = this.#validator(value, parameters, false);
|
|
196
219
|
|
|
197
|
-
if (
|
|
198
|
-
return parameters.reporting.none ? result : ok(result);
|
|
220
|
+
if (result === true) {
|
|
221
|
+
return parameters.reporting.none || parameters.reporting.throw ? result : ok(result);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (parameters.reporting.none) {
|
|
225
|
+
return false;
|
|
199
226
|
}
|
|
200
227
|
|
|
201
228
|
return error(parameters.reporting.all ? result : result[0]);
|
|
@@ -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,65 @@
|
|
|
1
|
+
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {
|
|
3
|
+
PROPERTY_VALIDATORS,
|
|
4
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
|
|
5
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY,
|
|
6
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE,
|
|
7
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE,
|
|
8
|
+
TEMPLATE_PATTERN,
|
|
9
|
+
TYPE_ALL,
|
|
10
|
+
} from '../constants';
|
|
11
|
+
import type {ValueName} from '../models/misc.model';
|
|
12
|
+
import type {NamedValidatorHandlers} from '../models/validation.model';
|
|
13
|
+
|
|
14
|
+
export function getNamedHandlers(
|
|
15
|
+
original: unknown,
|
|
16
|
+
prefix: string,
|
|
17
|
+
allowed: boolean,
|
|
18
|
+
): NamedValidatorHandlers {
|
|
19
|
+
const handlers: NamedValidatorHandlers = {};
|
|
20
|
+
|
|
21
|
+
if (original == null) {
|
|
22
|
+
return handlers;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!allowed) {
|
|
26
|
+
throw new TypeError(
|
|
27
|
+
SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(
|
|
28
|
+
TEMPLATE_PATTERN,
|
|
29
|
+
prefix,
|
|
30
|
+
).replace(TEMPLATE_PATTERN, PROPERTY_VALIDATORS),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isPlainObject(original)) {
|
|
35
|
+
throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const keys = Object.keys(original);
|
|
39
|
+
const {length} = keys;
|
|
40
|
+
|
|
41
|
+
for (let index = 0; index < length; index += 1) {
|
|
42
|
+
const key = keys[index];
|
|
43
|
+
|
|
44
|
+
if (!TYPE_ALL.has(key as never)) {
|
|
45
|
+
throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const value = original[key];
|
|
49
|
+
|
|
50
|
+
handlers[key as ValueName] = (Array.isArray(value) ? value : [value]).map(item => {
|
|
51
|
+
if (typeof item !== 'function') {
|
|
52
|
+
throw new TypeError(
|
|
53
|
+
SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key).replace(
|
|
54
|
+
TEMPLATE_PATTERN,
|
|
55
|
+
prefix,
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return item;
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return handlers;
|
|
65
|
+
}
|