@oscarpalmer/jhunal 0.18.0 → 0.20.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 -1
- package/dist/constants.mjs +7 -1
- package/dist/helpers.d.mts +6 -1
- package/dist/helpers.mjs +36 -10
- package/dist/index.d.mts +138 -94
- package/dist/index.mjs +128 -43
- package/dist/models/infer.model.d.mts +12 -12
- package/dist/models/misc.model.d.mts +20 -29
- package/dist/models/schema.plain.model.d.mts +4 -4
- package/dist/models/schema.typed.model.d.mts +5 -23
- package/dist/models/transform.model.d.mts +9 -13
- package/dist/models/validation.model.d.mts +49 -13
- package/dist/models/validation.model.mjs +4 -1
- package/dist/schematic.d.mts +52 -4
- package/dist/schematic.mjs +13 -4
- package/dist/validation/value.validation.d.mts +2 -2
- package/dist/validation/value.validation.mjs +78 -33
- package/package.json +1 -1
- package/src/constants.ts +16 -0
- package/src/helpers.ts +61 -15
- package/src/models/infer.model.ts +12 -12
- package/src/models/misc.model.ts +20 -29
- package/src/models/schema.plain.model.ts +4 -4
- package/src/models/schema.typed.model.ts +5 -23
- package/src/models/transform.model.ts +9 -13
- package/src/models/validation.model.ts +51 -9
- package/src/schematic.ts +79 -9
- package/src/validation/property.validation.ts +3 -4
- package/src/validation/value.validation.ts +120 -43
|
@@ -3,10 +3,23 @@ import { ValueName } from "./misc.model.mjs";
|
|
|
3
3
|
import { GenericCallback } from "@oscarpalmer/atoms/models";
|
|
4
4
|
|
|
5
5
|
//#region src/models/validation.model.d.ts
|
|
6
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Maps each {@link ReportingType} to a boolean flag
|
|
8
|
+
*/
|
|
9
|
+
type ReportingInformation = Record<ReportingType, boolean> & {
|
|
10
|
+
type: ReportingType;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Controls how validation failures are reported
|
|
14
|
+
*
|
|
15
|
+
* - `'none'` — returns a boolean _(default)_
|
|
16
|
+
* - `'first'` — returns the first failure as a `Result`
|
|
17
|
+
* - `'all'` — returns all failures as a `Result` _(from same level)_
|
|
18
|
+
* - `'throw'` — throws a {@link ValidationError} on failure
|
|
19
|
+
*/
|
|
7
20
|
type ReportingType = 'all' | 'first' | 'none' | 'throw';
|
|
8
21
|
/**
|
|
9
|
-
*
|
|
22
|
+
* Thrown when a schema definition is invalid
|
|
10
23
|
*/
|
|
11
24
|
declare class SchematicError extends Error {
|
|
12
25
|
constructor(message: string);
|
|
@@ -43,16 +56,12 @@ type ValidatedProperty = {
|
|
|
43
56
|
validators: ValidatedPropertyValidators;
|
|
44
57
|
};
|
|
45
58
|
/**
|
|
46
|
-
*
|
|
59
|
+
* The full and short forms of a property's key path
|
|
60
|
+
*
|
|
61
|
+
* For a nested property `address.street`: `full` is `'address.street'`, `short` is `'street'`
|
|
47
62
|
*/
|
|
48
63
|
type ValidatedPropertyKey = {
|
|
49
|
-
/**
|
|
50
|
-
* Full property key, including parent keys for nested properties _(e.g., `address.street`)_
|
|
51
|
-
*/
|
|
52
64
|
full: string;
|
|
53
|
-
/**
|
|
54
|
-
* The last segment of the property key _(e.g., `street` for `address.street`)_
|
|
55
|
-
*/
|
|
56
65
|
short: string;
|
|
57
66
|
};
|
|
58
67
|
/**
|
|
@@ -67,15 +76,42 @@ type ValidatedPropertyType = GenericCallback | ValidatedProperty[] | Schematic<u
|
|
|
67
76
|
* Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
|
|
68
77
|
*/
|
|
69
78
|
type ValidatedPropertyValidators = { [Key in ValueName]?: Array<(value: unknown) => boolean> };
|
|
79
|
+
/**
|
|
80
|
+
* Thrown in `'throw'` mode when one or more properties fail validation; `information` holds all failures
|
|
81
|
+
*/
|
|
70
82
|
declare class ValidationError extends Error {
|
|
71
83
|
readonly information: ValidationInformation[];
|
|
72
84
|
constructor(information: ValidationInformation[]);
|
|
73
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* Describes a single validation failure
|
|
88
|
+
*/
|
|
74
89
|
type ValidationInformation = {
|
|
75
|
-
key: ValidationInformationKey;
|
|
76
|
-
message: string;
|
|
77
|
-
validator?: GenericCallback;
|
|
90
|
+
/** The key path of the property that failed */key: ValidationInformationKey; /** Human-readable description of the failure */
|
|
91
|
+
message: string; /** The validator function that failed, if the failure was from a `$validators` entry */
|
|
92
|
+
validator?: GenericCallback; /** The value that was provided */
|
|
93
|
+
value: unknown;
|
|
78
94
|
};
|
|
95
|
+
/**
|
|
96
|
+
* Same shape as {@link ValidatedPropertyKey}; the key path of a failed property
|
|
97
|
+
*/
|
|
79
98
|
type ValidationInformationKey = ValidatedPropertyKey;
|
|
99
|
+
/**
|
|
100
|
+
* Options for validation
|
|
101
|
+
*/
|
|
102
|
+
type ValidationOptions<Errors extends ReportingType> = {
|
|
103
|
+
/**
|
|
104
|
+
* How should validation failures be reported; see {@link ReportingType} _(defaults to `'none'`)_
|
|
105
|
+
*/
|
|
106
|
+
errors?: Errors;
|
|
107
|
+
/**
|
|
108
|
+
* Validate if unknown keys are present in the object? _(defaults to `false`)_
|
|
109
|
+
*/
|
|
110
|
+
strict?: boolean;
|
|
111
|
+
};
|
|
112
|
+
type ValidationOptionsExtended = {
|
|
113
|
+
reporting: ReportingInformation;
|
|
114
|
+
strict: boolean;
|
|
115
|
+
};
|
|
80
116
|
//#endregion
|
|
81
|
-
export { ReportingInformation, ReportingType, SchematicError, ValidatedProperty, ValidatedPropertyKey, ValidatedPropertyType, ValidatedPropertyValidators, ValidationError, ValidationInformation, ValidationInformationKey };
|
|
117
|
+
export { ReportingInformation, ReportingType, SchematicError, ValidatedProperty, ValidatedPropertyKey, ValidatedPropertyType, ValidatedPropertyValidators, ValidationError, ValidationInformation, ValidationInformationKey, ValidationOptions, ValidationOptionsExtended };
|
|
@@ -2,7 +2,7 @@ import { NAME_ERROR_SCHEMATIC, NAME_ERROR_VALIDATION } from "../constants.mjs";
|
|
|
2
2
|
import { join } from "@oscarpalmer/atoms/string";
|
|
3
3
|
//#region src/models/validation.model.ts
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Thrown when a schema definition is invalid
|
|
6
6
|
*/
|
|
7
7
|
var SchematicError = class extends Error {
|
|
8
8
|
constructor(message) {
|
|
@@ -10,6 +10,9 @@ var SchematicError = class extends Error {
|
|
|
10
10
|
this.name = NAME_ERROR_SCHEMATIC;
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Thrown in `'throw'` mode when one or more properties fail validation; `information` holds all failures
|
|
15
|
+
*/
|
|
13
16
|
var ValidationError = class extends Error {
|
|
14
17
|
constructor(information) {
|
|
15
18
|
super(join(information.map((item) => item.message), "; "));
|
package/dist/schematic.d.mts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Infer } from "./models/infer.model.mjs";
|
|
2
2
|
import { TypedSchema } from "./models/schema.typed.model.mjs";
|
|
3
|
-
import { ValidatedProperty } from "./models/validation.model.mjs";
|
|
3
|
+
import { ValidatedProperty, ValidationInformation, ValidationOptions } from "./models/validation.model.mjs";
|
|
4
4
|
import { Schema } from "./models/schema.plain.model.mjs";
|
|
5
5
|
import { PlainObject } from "@oscarpalmer/atoms/models";
|
|
6
|
+
import { Result } from "@oscarpalmer/atoms/result/models";
|
|
6
7
|
|
|
7
8
|
//#region src/schematic.d.ts
|
|
8
9
|
/**
|
|
@@ -17,18 +18,64 @@ declare class Schematic<Model> {
|
|
|
17
18
|
*
|
|
18
19
|
* Will assert that the values matches the schema and throw an error if it does not. The error will contain all validation information for the first property that fails validation.
|
|
19
20
|
* @param value Value to validate
|
|
20
|
-
* @param
|
|
21
|
+
* @param options Validation options
|
|
22
|
+
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
23
|
+
*/
|
|
24
|
+
is(value: unknown, options: ValidationOptions<'throw'>): asserts value is Model;
|
|
25
|
+
/**
|
|
26
|
+
* Does the value match the schema?
|
|
27
|
+
*
|
|
28
|
+
* Will assert that the values matches the schema and throw an error if it does not. The error will contain all validation information for the first property that fails validation.
|
|
29
|
+
* @param value Value to validate
|
|
30
|
+
* @param errors Reporting type
|
|
21
31
|
* @returns `true` if the value matches the schema, otherwise throws an error
|
|
22
32
|
*/
|
|
23
33
|
is(value: unknown, errors: 'throw'): asserts value is Model;
|
|
34
|
+
/**
|
|
35
|
+
* Does the value match the schema?
|
|
36
|
+
*
|
|
37
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for validation failures from the same depth in the object.
|
|
38
|
+
* @param value Value to validate
|
|
39
|
+
* @param options Validation options
|
|
40
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
41
|
+
*/
|
|
42
|
+
is(value: unknown, options: ValidationOptions<'all'>): Result<true, ValidationInformation[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Does the value match the schema?
|
|
45
|
+
*
|
|
46
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for validation failures from the same depth in the object.
|
|
47
|
+
* @param value Value to validate
|
|
48
|
+
* @param errors Reporting type
|
|
49
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
50
|
+
*/
|
|
51
|
+
is(value: unknown, errors: 'all'): Result<true, ValidationInformation[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Does the value match the schema?
|
|
54
|
+
*
|
|
55
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for the failing property.
|
|
56
|
+
* @param value Value to validate
|
|
57
|
+
* @param options Validation options
|
|
58
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
59
|
+
*/
|
|
60
|
+
is(value: unknown, options: ValidationOptions<'first'>): Result<true, ValidationInformation>;
|
|
61
|
+
/**
|
|
62
|
+
* Does the value match the schema?
|
|
63
|
+
*
|
|
64
|
+
* Will validate that the value matches the schema and return a result of `true` or all validation information for the failing property.
|
|
65
|
+
* @param value Value to validate
|
|
66
|
+
* @param errors Reporting type
|
|
67
|
+
* @returns `true` if the value matches the schema, otherwise `false`
|
|
68
|
+
*/
|
|
69
|
+
is(value: unknown, errors: 'first'): Result<true, ValidationInformation>;
|
|
24
70
|
/**
|
|
25
71
|
* Does the value match the schema?
|
|
26
72
|
*
|
|
27
73
|
* Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures.
|
|
28
74
|
* @param value Value to validate
|
|
75
|
+
* @param strict Validate if unknown keys are present in the object? _(defaults to `false`)_
|
|
29
76
|
* @returns `true` if the value matches the schema, otherwise `false`
|
|
30
77
|
*/
|
|
31
|
-
is(value: unknown): value is Model;
|
|
78
|
+
is(value: unknown, strict?: true): value is Model;
|
|
32
79
|
}
|
|
33
80
|
/**
|
|
34
81
|
* Create a schematic from a schema
|
|
@@ -46,5 +93,6 @@ declare function schematic<Model extends Schema>(schema: Model): Schematic<Infer
|
|
|
46
93
|
* @returns A schematic for the given typed schema
|
|
47
94
|
*/
|
|
48
95
|
declare function schematic<Model extends PlainObject>(schema: TypedSchema<Model>): Schematic<Model>;
|
|
96
|
+
declare const schematicProperties: WeakMap<Schematic<unknown>, ValidatedProperty[]>;
|
|
49
97
|
//#endregion
|
|
50
|
-
export { Schematic, schematic };
|
|
98
|
+
export { Schematic, schematic, schematicProperties };
|
package/dist/schematic.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { PROPERTY_SCHEMATIC, SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE } from "./constants.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { getOptions, isSchematic } from "./helpers.mjs";
|
|
3
3
|
import { SchematicError } from "./models/validation.model.mjs";
|
|
4
4
|
import { getProperties } from "./validation/property.validation.mjs";
|
|
5
5
|
import { validateObject } from "./validation/value.validation.mjs";
|
|
6
6
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
7
|
+
import { error } from "@oscarpalmer/atoms/result/misc";
|
|
7
8
|
//#region src/schematic.ts
|
|
8
9
|
/**
|
|
9
10
|
* A schematic for validating objects
|
|
@@ -13,9 +14,16 @@ var Schematic = class {
|
|
|
13
14
|
constructor(properties) {
|
|
14
15
|
Object.defineProperty(this, PROPERTY_SCHEMATIC, { value: true });
|
|
15
16
|
this.#properties = properties;
|
|
17
|
+
schematicProperties.set(this, properties);
|
|
16
18
|
}
|
|
17
|
-
is(value,
|
|
18
|
-
|
|
19
|
+
is(value, options) {
|
|
20
|
+
const { reporting, strict } = getOptions(options);
|
|
21
|
+
const result = validateObject(value, this.#properties, {
|
|
22
|
+
reporting,
|
|
23
|
+
strict
|
|
24
|
+
});
|
|
25
|
+
if (typeof result === "boolean") return result;
|
|
26
|
+
return error(reporting.all ? result : result[0]);
|
|
19
27
|
}
|
|
20
28
|
};
|
|
21
29
|
function schematic(schema) {
|
|
@@ -23,5 +31,6 @@ function schematic(schema) {
|
|
|
23
31
|
if (!isPlainObject(schema)) throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
|
|
24
32
|
return new Schematic(getProperties(schema));
|
|
25
33
|
}
|
|
34
|
+
const schematicProperties = /* @__PURE__ */ new WeakMap();
|
|
26
35
|
//#endregion
|
|
27
|
-
export { Schematic, schematic };
|
|
36
|
+
export { Schematic, schematic, schematicProperties };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ValidatedProperty, ValidationInformation, ValidationOptionsExtended } from "../models/validation.model.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/validation/value.validation.d.ts
|
|
4
|
-
declare function validateObject(obj: unknown, properties: ValidatedProperty[],
|
|
4
|
+
declare function validateObject(obj: unknown, properties: ValidatedProperty[], options: ValidationOptionsExtended, origin?: ValidatedProperty, validation?: ValidationInformation[]): boolean | ValidationInformation[];
|
|
5
5
|
//#endregion
|
|
6
6
|
export { validateObject };
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import "../constants.mjs";
|
|
2
|
+
import { getInvalidInputMessage, getInvalidMissingMessage, getInvalidTypeMessage, getInvalidValidatorMessage, getUnknownKeysMessage, isSchematic } from "../helpers.mjs";
|
|
2
3
|
import { ValidationError } from "../models/validation.model.mjs";
|
|
4
|
+
import { schematicProperties } from "../schematic.mjs";
|
|
3
5
|
import { isPlainObject } from "@oscarpalmer/atoms/is";
|
|
6
|
+
import { join } from "@oscarpalmer/atoms/string";
|
|
4
7
|
//#region src/validation/value.validation.ts
|
|
5
8
|
function validateNamed(property, name, value, validation) {
|
|
6
9
|
if (!validators[name](value)) return false;
|
|
@@ -11,6 +14,7 @@ function validateNamed(property, name, value, validation) {
|
|
|
11
14
|
const validator = propertyValidators[index];
|
|
12
15
|
if (!validator(value)) {
|
|
13
16
|
validation.push({
|
|
17
|
+
value,
|
|
14
18
|
key: { ...property.key },
|
|
15
19
|
message: getInvalidValidatorMessage(property, name, index, length),
|
|
16
20
|
validator
|
|
@@ -20,63 +24,104 @@ function validateNamed(property, name, value, validation) {
|
|
|
20
24
|
}
|
|
21
25
|
return true;
|
|
22
26
|
}
|
|
23
|
-
function validateObject(obj, properties,
|
|
27
|
+
function validateObject(obj, properties, options, origin, validation) {
|
|
24
28
|
if (!isPlainObject(obj)) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
const key = origin == null ? {
|
|
30
|
+
full: "",
|
|
31
|
+
short: ""
|
|
32
|
+
} : { ...origin.key };
|
|
33
|
+
const information = {
|
|
34
|
+
key,
|
|
35
|
+
message: origin == null ? getInvalidInputMessage(obj) : getInvalidTypeMessage({
|
|
36
|
+
...origin,
|
|
37
|
+
key
|
|
38
|
+
}, obj),
|
|
39
|
+
value: obj
|
|
40
|
+
};
|
|
41
|
+
if (options.reporting.throw) throw new ValidationError([information]);
|
|
42
|
+
validation?.push(information);
|
|
43
|
+
return options.reporting.none ? false : [information];
|
|
44
|
+
}
|
|
45
|
+
if (options.strict) {
|
|
46
|
+
const objKeys = Object.keys(obj);
|
|
47
|
+
const propertiesKeys = new Set(properties.map((property) => property.key.short));
|
|
48
|
+
const unknownKeys = objKeys.filter((key) => !propertiesKeys.has(key));
|
|
49
|
+
if (unknownKeys.length > 0) {
|
|
50
|
+
const information = {
|
|
51
|
+
key: origin == null ? {
|
|
52
|
+
full: "",
|
|
53
|
+
short: ""
|
|
54
|
+
} : { ...origin.key },
|
|
55
|
+
message: getUnknownKeysMessage(unknownKeys.map((key) => join([origin?.key.full, key], "."))),
|
|
56
|
+
value: obj
|
|
57
|
+
};
|
|
58
|
+
if (options.reporting.throw) throw new ValidationError([information]);
|
|
59
|
+
validation?.push(information);
|
|
60
|
+
return options.reporting.none ? false : [information];
|
|
61
|
+
}
|
|
33
62
|
}
|
|
63
|
+
const allInformation = [];
|
|
34
64
|
const propertiesLength = properties.length;
|
|
35
65
|
outer: for (let propertyIndex = 0; propertyIndex < propertiesLength; propertyIndex += 1) {
|
|
36
|
-
|
|
66
|
+
let property = properties[propertyIndex];
|
|
67
|
+
property = {
|
|
68
|
+
...property,
|
|
69
|
+
key: {
|
|
70
|
+
full: join([origin?.key.full, property.key.short], "."),
|
|
71
|
+
short: property.key.short
|
|
72
|
+
}
|
|
73
|
+
};
|
|
37
74
|
const { key, required, types } = property;
|
|
38
75
|
const value = obj[key.short];
|
|
39
76
|
if (value === void 0 && required) {
|
|
40
77
|
const information = {
|
|
78
|
+
value,
|
|
41
79
|
key: { ...key },
|
|
42
80
|
message: getInvalidMissingMessage(property)
|
|
43
81
|
};
|
|
44
|
-
if (reporting.throw && validation == null) throw new ValidationError([information]);
|
|
82
|
+
if (options.reporting.throw && validation == null) throw new ValidationError([information]);
|
|
45
83
|
if (validation != null) validation.push(information);
|
|
46
|
-
|
|
84
|
+
if (options.reporting.all) {
|
|
85
|
+
allInformation.push(information);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
return options.reporting.none ? false : [information];
|
|
47
89
|
}
|
|
48
90
|
const typesLength = types.length;
|
|
49
91
|
const information = [];
|
|
50
92
|
for (let typeIndex = 0; typeIndex < typesLength; typeIndex += 1) {
|
|
51
93
|
const type = types[typeIndex];
|
|
52
|
-
if (validateValue(type, property, value,
|
|
94
|
+
if (validateValue(type, property, value, options, information)) continue outer;
|
|
53
95
|
}
|
|
54
|
-
if (
|
|
96
|
+
if (information.length === 0) information.push({
|
|
97
|
+
value,
|
|
55
98
|
key: { ...key },
|
|
56
99
|
message: getInvalidTypeMessage(property, value)
|
|
57
|
-
}
|
|
100
|
+
});
|
|
101
|
+
if (options.reporting.throw && validation == null) throw new ValidationError(information);
|
|
58
102
|
validation?.push(...information);
|
|
59
|
-
|
|
103
|
+
if (options.reporting.all) {
|
|
104
|
+
allInformation.push(...information);
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
return options.reporting.none ? false : information;
|
|
60
108
|
}
|
|
61
|
-
return true;
|
|
109
|
+
return options.reporting.none || allInformation.length === 0 ? true : allInformation;
|
|
62
110
|
}
|
|
63
|
-
function
|
|
64
|
-
|
|
111
|
+
function validateSchematic(property, schematic, value, options, validation) {
|
|
112
|
+
const result = validateObject(value, schematicProperties.get(schematic), options, property, validation);
|
|
113
|
+
return typeof result === "boolean" ? result : result.length === 0;
|
|
114
|
+
}
|
|
115
|
+
function validateValue(type, property, value, options, validation) {
|
|
65
116
|
switch (true) {
|
|
66
|
-
case typeof type === "function":
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
result = type.is(value, reporting);
|
|
74
|
-
break;
|
|
75
|
-
default:
|
|
76
|
-
result = validateNamed(property, type, value, validation);
|
|
77
|
-
break;
|
|
117
|
+
case typeof type === "function": return type(value);
|
|
118
|
+
case Array.isArray(type): {
|
|
119
|
+
const validated = validateObject(value, type, options, property, validation);
|
|
120
|
+
return typeof validated === "boolean" ? validated : false;
|
|
121
|
+
}
|
|
122
|
+
case isSchematic(type): return validateSchematic(property, type, value, options, validation);
|
|
123
|
+
default: return validateNamed(property, type, value, validation);
|
|
78
124
|
}
|
|
79
|
-
return result;
|
|
80
125
|
}
|
|
81
126
|
const validators = {
|
|
82
127
|
array: Array.isArray,
|
package/package.json
CHANGED
package/src/constants.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import type {ValueName} from './models/misc.model';
|
|
2
2
|
import type {ReportingType} from './models/validation.model';
|
|
3
3
|
|
|
4
|
+
// #region Grammar
|
|
5
|
+
|
|
6
|
+
export const COMMA = ', ';
|
|
7
|
+
|
|
8
|
+
export const CONJUNCTION_OR = ' or ';
|
|
9
|
+
|
|
10
|
+
export const CONJUNCTION_OR_COMMA = ', or ';
|
|
11
|
+
|
|
12
|
+
export const CONJUNCTION_AND = ' and ';
|
|
13
|
+
|
|
14
|
+
export const CONJUNCTION_AND_COMMA = ', and ';
|
|
15
|
+
|
|
16
|
+
// #endregion
|
|
17
|
+
|
|
4
18
|
// #region Misc.
|
|
5
19
|
|
|
6
20
|
export const MESSAGE_CONSTRUCTOR = 'Expected a constructor function';
|
|
@@ -42,6 +56,8 @@ export const VALIDATION_MESSAGE_INVALID_VALUE =
|
|
|
42
56
|
|
|
43
57
|
export const VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX = ' at index <>';
|
|
44
58
|
|
|
59
|
+
export const VALIDATION_MESSAGE_UNKNOWN_KEYS = 'Found keys that are not defined in the schema: <>';
|
|
60
|
+
|
|
45
61
|
// #endregion
|
|
46
62
|
|
|
47
63
|
// #region Reporting
|
package/src/helpers.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import type {Constructor} from '@oscarpalmer/atoms/models';
|
|
3
3
|
import {
|
|
4
|
+
COMMA,
|
|
5
|
+
CONJUNCTION_AND,
|
|
6
|
+
CONJUNCTION_AND_COMMA,
|
|
7
|
+
CONJUNCTION_OR,
|
|
8
|
+
CONJUNCTION_OR_COMMA,
|
|
4
9
|
MESSAGE_CONSTRUCTOR,
|
|
5
10
|
NAME_SCHEMATIC,
|
|
6
11
|
PROPERTY_SCHEMATIC,
|
|
@@ -19,6 +24,7 @@ import {
|
|
|
19
24
|
VALIDATION_MESSAGE_INVALID_TYPE,
|
|
20
25
|
VALIDATION_MESSAGE_INVALID_VALUE,
|
|
21
26
|
VALIDATION_MESSAGE_INVALID_VALUE_SUFFIX,
|
|
27
|
+
VALIDATION_MESSAGE_UNKNOWN_KEYS,
|
|
22
28
|
} from './constants';
|
|
23
29
|
import type {ValueName} from './models/misc.model';
|
|
24
30
|
import type {
|
|
@@ -73,6 +79,29 @@ export function getInvalidValidatorMessage(
|
|
|
73
79
|
return message;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
export function getOptions(input: unknown) {
|
|
83
|
+
if (typeof input === 'boolean') {
|
|
84
|
+
return {
|
|
85
|
+
reporting: getReporting(REPORTING_NONE),
|
|
86
|
+
strict: input,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (REPORTING_TYPES.has(input as ReportingType)) {
|
|
91
|
+
return {
|
|
92
|
+
reporting: getReporting(input as ReportingType),
|
|
93
|
+
strict: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const options = isPlainObject(input) ? input : {};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
reporting: getReporting(options.errors),
|
|
101
|
+
strict: typeof options.strict === 'boolean' ? options.strict : false,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
76
105
|
function getPropertyType(original: ValidatedPropertyType): string {
|
|
77
106
|
if (typeof original === 'function') {
|
|
78
107
|
return 'a validated value';
|
|
@@ -95,6 +124,7 @@ export function getReporting(value: unknown): ReportingInformation {
|
|
|
95
124
|
: REPORTING_NONE;
|
|
96
125
|
|
|
97
126
|
return {
|
|
127
|
+
type,
|
|
98
128
|
[REPORTING_ALL]: type === REPORTING_ALL,
|
|
99
129
|
[REPORTING_FIRST]: type === REPORTING_FIRST,
|
|
100
130
|
[REPORTING_NONE]: type === REPORTING_NONE,
|
|
@@ -102,6 +132,10 @@ export function getReporting(value: unknown): ReportingInformation {
|
|
|
102
132
|
} as ReportingInformation;
|
|
103
133
|
}
|
|
104
134
|
|
|
135
|
+
export function getUnknownKeysMessage(keys: string[]): string {
|
|
136
|
+
return VALIDATION_MESSAGE_UNKNOWN_KEYS.replace(TEMPLATE_PATTERN, renderKeys(keys));
|
|
137
|
+
}
|
|
138
|
+
|
|
105
139
|
function getValueType(value: unknown): string {
|
|
106
140
|
const valueType = typeof value;
|
|
107
141
|
|
|
@@ -161,34 +195,46 @@ export function isSchematic(value: unknown): value is Schematic<never> {
|
|
|
161
195
|
);
|
|
162
196
|
}
|
|
163
197
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
for (let index = 0; index < types.length; index += 1) {
|
|
169
|
-
const rendered = getPropertyType(types[index]);
|
|
198
|
+
function renderKeys(keys: string[]): string {
|
|
199
|
+
return renderParts(keys.map(key => `'${key}'`), CONJUNCTION_AND, CONJUNCTION_AND_COMMA);
|
|
200
|
+
}
|
|
170
201
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
202
|
+
function renderParts(parts: string[], delimiterShort: string, delimiterLong: string): string {
|
|
203
|
+
const {length} = parts;
|
|
174
204
|
|
|
175
|
-
|
|
176
|
-
parts
|
|
205
|
+
if (length === 1) {
|
|
206
|
+
return parts[0];
|
|
177
207
|
}
|
|
178
208
|
|
|
179
|
-
const {length} = parts;
|
|
180
|
-
|
|
181
209
|
let rendered = '';
|
|
182
210
|
|
|
183
211
|
for (let index = 0; index < length; index += 1) {
|
|
184
212
|
rendered += parts[index];
|
|
185
213
|
|
|
186
214
|
if (index < length - 2) {
|
|
187
|
-
rendered +=
|
|
215
|
+
rendered += COMMA;
|
|
188
216
|
} else if (index === length - 2) {
|
|
189
|
-
rendered += parts.length > 2 ?
|
|
217
|
+
rendered += parts.length > 2 ? delimiterLong : delimiterShort;
|
|
190
218
|
}
|
|
191
219
|
}
|
|
192
220
|
|
|
193
221
|
return rendered;
|
|
194
222
|
}
|
|
223
|
+
|
|
224
|
+
function renderTypes(types: ValidatedPropertyType[]): string {
|
|
225
|
+
const unique = new Set<string>();
|
|
226
|
+
const parts: string[] = [];
|
|
227
|
+
|
|
228
|
+
for (let index = 0; index < types.length; index += 1) {
|
|
229
|
+
const rendered = getPropertyType(types[index]);
|
|
230
|
+
|
|
231
|
+
if (unique.has(rendered)) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
unique.add(rendered);
|
|
236
|
+
parts.push(rendered);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return renderParts(parts, CONJUNCTION_OR, CONJUNCTION_OR_COMMA);
|
|
240
|
+
}
|
|
@@ -6,7 +6,7 @@ import type {PlainSchema, Schema, SchemaProperty} from './schema.plain.model';
|
|
|
6
6
|
/**
|
|
7
7
|
* Infers the TypeScript type from a {@link Schema} definition
|
|
8
8
|
*
|
|
9
|
-
* @template Model
|
|
9
|
+
* @template Model Schema to infer types from
|
|
10
10
|
*
|
|
11
11
|
* @example
|
|
12
12
|
* ```ts
|
|
@@ -38,20 +38,20 @@ export type InferOptionalKeys<Model extends Schema> = keyof {
|
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
|
-
* Infers the TypeScript type
|
|
41
|
+
* Infers the TypeScript type from a {@link SchemaProperty}'s `$type` field
|
|
42
42
|
*
|
|
43
|
-
* @template Value
|
|
43
|
+
* @template Value `$type` value _(single or array)_
|
|
44
44
|
*/
|
|
45
45
|
export type InferPropertyType<Value> = Value extends (infer Item)[]
|
|
46
46
|
? InferPropertyValue<Item>
|
|
47
47
|
: InferPropertyValue<Value>;
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Maps a single type definition to its TypeScript equivalent
|
|
50
|
+
* Maps a single `$type` definition to its TypeScript equivalent
|
|
51
51
|
*
|
|
52
|
-
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link
|
|
52
|
+
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link ValueName} strings, and nested {@link PlainSchema} objects
|
|
53
53
|
*
|
|
54
|
-
* @template Value
|
|
54
|
+
* @template Value single type definition
|
|
55
55
|
*/
|
|
56
56
|
export type InferPropertyValue<Value> =
|
|
57
57
|
Value extends Constructor<infer Instance>
|
|
@@ -67,27 +67,27 @@ export type InferPropertyValue<Value> =
|
|
|
67
67
|
/**
|
|
68
68
|
* Extracts keys from a {@link Schema} whose entries are required _(i.e., `$required` is not `false`)_
|
|
69
69
|
*
|
|
70
|
-
* @template Model
|
|
70
|
+
* @template Model Schema to extract required keys from
|
|
71
71
|
*/
|
|
72
72
|
export type InferRequiredKeys<Model extends Schema> = keyof {
|
|
73
73
|
[Key in keyof Model as IsOptionalProperty<Model[Key]> extends true ? never : Key]: never;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
76
|
/**
|
|
77
|
-
* Infers the type
|
|
77
|
+
* Infers the TypeScript type from a top-level {@link Schema} entry
|
|
78
78
|
*
|
|
79
|
-
* @template Value
|
|
79
|
+
* @template Value Schema entry value _(single or array)_
|
|
80
80
|
*/
|
|
81
81
|
export type InferSchemaEntry<Value> = Value extends (infer Item)[]
|
|
82
82
|
? InferSchemaEntryValue<Item>
|
|
83
83
|
: InferSchemaEntryValue<Value>;
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
|
-
*
|
|
86
|
+
* Maps a single top-level schema entry to its TypeScript type
|
|
87
87
|
*
|
|
88
|
-
*
|
|
88
|
+
* Resolves, in order: {@link Constructor} instances, {@link Schematic} models, {@link SchemaProperty} objects, {@link PlainSchema} objects, and {@link ValueName} strings
|
|
89
89
|
*
|
|
90
|
-
* @template Value
|
|
90
|
+
* @template Value single schema entry
|
|
91
91
|
*/
|
|
92
92
|
export type InferSchemaEntryValue<Value> =
|
|
93
93
|
Value extends Constructor<infer Instance>
|