@oscarpalmer/jhunal 0.16.0 → 0.18.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.
Files changed (40) hide show
  1. package/dist/constants.d.mts +28 -15
  2. package/dist/constants.mjs +31 -14
  3. package/dist/helpers.d.mts +8 -1
  4. package/dist/helpers.mjs +68 -3
  5. package/dist/index.d.mts +283 -262
  6. package/dist/index.mjs +189 -56
  7. package/dist/models/infer.model.d.mts +66 -0
  8. package/dist/models/infer.model.mjs +1 -0
  9. package/dist/models/misc.model.d.mts +153 -0
  10. package/dist/models/misc.model.mjs +1 -0
  11. package/dist/models/schema.plain.model.d.mts +92 -0
  12. package/dist/models/schema.plain.model.mjs +1 -0
  13. package/dist/models/schema.typed.model.d.mts +96 -0
  14. package/dist/models/schema.typed.model.mjs +1 -0
  15. package/dist/models/transform.model.d.mts +59 -0
  16. package/dist/models/transform.model.mjs +1 -0
  17. package/dist/models/validation.model.d.mts +81 -0
  18. package/dist/models/validation.model.mjs +21 -0
  19. package/dist/schematic.d.mts +20 -6
  20. package/dist/schematic.mjs +7 -12
  21. package/dist/validation/property.validation.d.mts +1 -1
  22. package/dist/validation/property.validation.mjs +21 -17
  23. package/dist/validation/value.validation.d.mts +2 -2
  24. package/dist/validation/value.validation.mjs +63 -11
  25. package/package.json +3 -3
  26. package/src/constants.ts +84 -18
  27. package/src/helpers.ts +162 -4
  28. package/src/index.ts +3 -1
  29. package/src/models/infer.model.ts +105 -0
  30. package/src/models/misc.model.ts +212 -0
  31. package/src/models/schema.plain.model.ts +110 -0
  32. package/src/models/schema.typed.model.ts +109 -0
  33. package/src/models/transform.model.ts +85 -0
  34. package/src/models/validation.model.ts +123 -0
  35. package/src/schematic.ts +29 -18
  36. package/src/validation/property.validation.ts +46 -36
  37. package/src/validation/value.validation.ts +115 -15
  38. package/dist/models.d.mts +0 -507
  39. package/dist/models.mjs +0 -18
  40. package/src/models.ts +0 -691
@@ -0,0 +1,110 @@
1
+ import type {Constructor} from '@oscarpalmer/atoms/models';
2
+ import type {Schematic} from '../schematic';
3
+ import type {ExtractValueNames, ValueName, Values} from './misc.model';
4
+
5
+ /**
6
+ * A generic schema allowing {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry} as values
7
+ */
8
+ export type PlainSchema = {
9
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[] | undefined;
10
+ } & {
11
+ $required?: never;
12
+ $type?: never;
13
+ $validators?: never;
14
+ };
15
+
16
+ /**
17
+ * A schema for validating objects
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * const schema: Schema = {
22
+ * name: 'string',
23
+ * age: 'number',
24
+ * tags: ['string', 'number'],
25
+ * };
26
+ * ```
27
+ */
28
+ export type Schema = SchemaIndex;
29
+
30
+ /**
31
+ * A union of all valid types for a single schema entry
32
+ *
33
+ * Can be a {@link Constructor}, nested {@link Schema}, {@link SchemaProperty}, {@link Schematic}, {@link ValueName} string, or a custom validator function
34
+ */
35
+ export type SchemaEntry =
36
+ | Constructor
37
+ | PlainSchema
38
+ | SchemaProperty
39
+ | Schematic<unknown>
40
+ | ValueName
41
+ | ((value: unknown) => boolean);
42
+
43
+ /**
44
+ * Index signature interface backing {@link Schema}, allowing string-keyed entries of {@link NestedSchema}, {@link SchemaEntry}, or arrays of {@link SchemaEntry}
45
+ */
46
+ export interface SchemaIndex {
47
+ [key: string]: PlainSchema | SchemaEntry | SchemaEntry[];
48
+ }
49
+
50
+ /**
51
+ * A property definition with explicit type(s), an optional requirement flag, and optional validators
52
+ *
53
+ * @example
54
+ * ```ts
55
+ * const prop: SchemaProperty = {
56
+ * $required: false,
57
+ * $type: ['string', 'number'],
58
+ * $validators: {
59
+ * string: (v) => v.length > 0,
60
+ * number: (v) => v > 0,
61
+ * },
62
+ * };
63
+ * ```
64
+ */
65
+ export type SchemaProperty = {
66
+ /**
67
+ * Whether the property is required _(defaults to `true`)_
68
+ */
69
+ $required?: boolean;
70
+ /**
71
+ * The type(s) the property value must match; a single {@link SchemaPropertyType} or an array
72
+ */
73
+ $type: SchemaPropertyType | SchemaPropertyType[];
74
+ /**
75
+ * Optional validators keyed by {@link ValueName}, applied during validation
76
+ */
77
+ $validators?: PropertyValidators<SchemaPropertyType | SchemaPropertyType[]>;
78
+ };
79
+
80
+ /**
81
+ * A union of valid types for a {@link SchemaProperty}'s `$type` field
82
+ *
83
+ * Can be a {@link Constructor}, {@link PlainSchema}, {@link Schematic}, {@link ValueName} string, or a custom validator function
84
+ */
85
+ export type SchemaPropertyType =
86
+ | Constructor
87
+ | PlainSchema
88
+ | Schematic<unknown>
89
+ | ValueName
90
+ | ((value: unknown) => boolean);
91
+
92
+ /**
93
+ * A map of optional validator functions keyed by {@link ValueName}, used to add custom validation to {@link SchemaProperty} definitions
94
+ *
95
+ * Each key may hold a single validator or an array of validators that receive the typed value
96
+ *
97
+ * @template Value - `$type` value(s) to derive validator keys from
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const validators: PropertyValidators<'string'> = {
102
+ * string: (value) => value.length > 0,
103
+ * };
104
+ * ```
105
+ */
106
+ export type PropertyValidators<Value> = {
107
+ [Key in ExtractValueNames<Value>]?:
108
+ | ((value: Values[Key]) => boolean)
109
+ | Array<(value: Values[Key]) => boolean>;
110
+ };
@@ -0,0 +1,109 @@
1
+ import type {PlainObject, Simplify} from '@oscarpalmer/atoms/models';
2
+ import type {Schematic} from '../schematic';
3
+ import type {OptionalKeys, RequiredKeys} from './misc.model';
4
+ import type {PropertyValidators} from './schema.plain.model';
5
+ import type {ToSchemaPropertyType, ToSchemaType} from './transform.model';
6
+
7
+ /**
8
+ * A typed optional property definition generated by {@link TypedSchema} for optional keys, with `$required` set to `false` and excludes `undefined` from the type
9
+ *
10
+ * @template Value - Property's type _(including `undefined`)_
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * // For `{ name?: string }`, the `name` key produces:
15
+ * // TypedPropertyOptional<string | undefined>
16
+ * // => { $required: false; $type: 'string'; ... }
17
+ * ```
18
+ */
19
+ export type TypedPropertyOptional<Value> = {
20
+ /**
21
+ * The property is not required
22
+ */
23
+ $required: false;
24
+ /**
25
+ * The type(s) of the property
26
+ */
27
+ $type: ToSchemaPropertyType<Exclude<Value, undefined>>;
28
+ /**
29
+ * Custom validators for the property and its types
30
+ */
31
+ $validators?: PropertyValidators<ToSchemaPropertyType<Exclude<Value, undefined>>>;
32
+ };
33
+
34
+ /**
35
+ * A typed required property definition generated by {@link TypedSchema} for required keys, with `$required` defaulting to `true`
36
+ *
37
+ * @template Value - Property's type
38
+ *
39
+ * @example
40
+ * ```ts
41
+ * // For `{ name: string }`, the `name` key produces:
42
+ * // TypedPropertyRequired<string>
43
+ * // => { $required?: true; $type: 'string'; ... }
44
+ * ```
45
+ */
46
+ export type TypedPropertyRequired<Value> = {
47
+ /**
48
+ * The property is required _(defaults to `true`)_
49
+ */
50
+ $required?: true;
51
+ /**
52
+ * The type(s) of the property
53
+ */
54
+ $type: ToSchemaPropertyType<Value>;
55
+ /**
56
+ * Custom validators for the property and its types
57
+ */
58
+ $validators?: PropertyValidators<ToSchemaPropertyType<Value>>;
59
+ };
60
+
61
+ /**
62
+ * Creates a schema type constrained to match a TypeScript type
63
+ *
64
+ * Required keys map to {@link ToSchemaType} or {@link TypedPropertyRequired}; plain object values may also use {@link Schematic}. Optional keys map to {@link TypedPropertyOptional} or, for plain objects, {@link TypedSchemaOptional}
65
+ *
66
+ * @template Model - Object type to generate a schema for
67
+ *
68
+ * @example
69
+ * ```ts
70
+ * type User = { name: string; age: number; bio?: string };
71
+ *
72
+ * const schema: TypedSchema<User> = {
73
+ * name: 'string',
74
+ * age: 'number',
75
+ * bio: { $required: false, $type: 'string' },
76
+ * };
77
+ * ```
78
+ */
79
+ export type TypedSchema<Model extends PlainObject> = Simplify<
80
+ {
81
+ [Key in RequiredKeys<Model>]: Model[Key] extends PlainObject
82
+ ? TypedSchemaRequired<Model[Key]> | Schematic<Model[Key]>
83
+ : ToSchemaType<Model[Key]> | TypedPropertyRequired<Model[Key]>;
84
+ } & {
85
+ [Key in OptionalKeys<Model>]: Exclude<Model[Key], undefined> extends PlainObject
86
+ ?
87
+ | TypedSchemaOptional<Exclude<Model[Key], undefined>>
88
+ | Schematic<Exclude<Model[Key], undefined>>
89
+ : TypedPropertyOptional<Model[Key]>;
90
+ }
91
+ >;
92
+
93
+ /**
94
+ * A {@link TypedSchema} variant for optional nested objects, with `$required` fixed to `false`
95
+ *
96
+ * @template Model - Nested object type
97
+ */
98
+ type TypedSchemaOptional<Model extends PlainObject> = {
99
+ $required: false;
100
+ } & TypedSchema<Model>;
101
+
102
+ /**
103
+ * A {@link TypedSchema} variant for required nested objects, with `$required` defaulting to `true`
104
+ *
105
+ * @template Model - Nested object type
106
+ */
107
+ type TypedSchemaRequired<Model extends PlainObject> = {
108
+ $required?: true;
109
+ } & TypedSchema<Model>;
@@ -0,0 +1,85 @@
1
+ import type {PlainObject} from '@oscarpalmer/atoms/models';
2
+ import type {Schematic} from '../schematic';
3
+ import type {DeduplicateTuple, UnionToTuple, UnwrapSingle, Values} from './misc.model';
4
+ import type {TypedSchema} from './schema.typed.model';
5
+
6
+ /**
7
+ * Maps each element of a tuple through {@link ToValueType}
8
+ *
9
+ * @template Value - Tuple of types to map
10
+ */
11
+ export type MapToValueTypes<Value extends unknown[]> = Value extends [infer Head, ...infer Tail]
12
+ ? [ToValueType<Head>, ...MapToValueTypes<Tail>]
13
+ : [];
14
+
15
+ /**
16
+ * Maps each element of a tuple through {@link ToSchemaPropertyTypeEach}
17
+ *
18
+ * @template Value - Tuple of types to map
19
+ */
20
+ export type MapToSchemaPropertyTypes<Value extends unknown[]> = Value extends [
21
+ infer Head,
22
+ ...infer Tail,
23
+ ]
24
+ ? [ToSchemaPropertyTypeEach<Head>, ...MapToSchemaPropertyTypes<Tail>]
25
+ : [];
26
+
27
+ /**
28
+ * Converts a type into its corresponding {@link SchemaPropertyType}-representation
29
+ *
30
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
31
+ *
32
+ * @template Value - type to convert
33
+ */
34
+ export type ToSchemaPropertyType<Value> = UnwrapSingle<
35
+ DeduplicateTuple<MapToSchemaPropertyTypes<UnionToTuple<Value>>>
36
+ >;
37
+
38
+ /**
39
+ * Converts a single type to its schema property equivalent
40
+ *
41
+ * {@link NestedSchema} values have `$required` stripped, plain objects become {@link TypedSchema}, and primitives go through {@link ToValueType}
42
+ *
43
+ * @template Value - type to convert
44
+ */
45
+ export type ToSchemaPropertyTypeEach<Value> = Value extends PlainObject
46
+ ? TypedSchema<Value>
47
+ : ToValueType<Value>;
48
+
49
+ /**
50
+ * Converts a type into its corresponding {@link ValueName}-representation
51
+ *
52
+ * Deduplicates and unwraps single-element tuples via {@link UnwrapSingle}
53
+ *
54
+ * @template Value - type to convert
55
+ */
56
+ export type ToSchemaType<Value> = UnwrapSingle<
57
+ DeduplicateTuple<MapToValueTypes<UnionToTuple<Value>>>
58
+ >;
59
+
60
+ /**
61
+ * Maps a type to its {@link ValueName} string equivalent
62
+ *
63
+ * Resolves {@link Schematic} types as-is, then performs a reverse-lookup against {@link Values} _(excluding `'object'`)_ to find a matching key. If no match is found, `object` types resolve to `'object'` or a type-guard function, and all other unrecognised types resolve to a type-guard function
64
+ *
65
+ * @template Value - type to map
66
+ *
67
+ * @example
68
+ * ```ts
69
+ * // ToValueType<string> => 'string'
70
+ * // ToValueType<number[]> => 'array'
71
+ * // ToValueType<Date> => 'date'
72
+ * ```
73
+ */
74
+ export type ToValueType<Value> =
75
+ Value extends Schematic<any>
76
+ ? Value
77
+ : {
78
+ [Key in keyof Omit<Values, 'object'>]: Value extends Values[Key] ? Key : never;
79
+ }[keyof Omit<Values, 'object'>] extends infer Match
80
+ ? [Match] extends [never]
81
+ ? Value extends object
82
+ ? 'object' | ((value: unknown) => value is Value)
83
+ : (value: unknown) => value is Value
84
+ : Match
85
+ : never;
@@ -0,0 +1,123 @@
1
+ import type {GenericCallback} from '@oscarpalmer/atoms/models';
2
+ import {join} from '@oscarpalmer/atoms/string';
3
+ import {NAME_ERROR_SCHEMATIC, NAME_ERROR_VALIDATION} from '../constants';
4
+ import type {Schematic} from '../schematic';
5
+ import type {ValueName} from './misc.model';
6
+
7
+ // #region Reporting
8
+
9
+ export type ReportingInformation = Record<ReportingType, boolean>;
10
+
11
+ export type ReportingType = 'all' | 'first' | 'none' | 'throw';
12
+
13
+ // #endregion
14
+
15
+ // #region Schematic validation
16
+
17
+ /**
18
+ * A custom error class for schematic validation failures
19
+ */
20
+ export class SchematicError extends Error {
21
+ constructor(message: string) {
22
+ super(message);
23
+
24
+ this.name = NAME_ERROR_SCHEMATIC;
25
+ }
26
+ }
27
+
28
+ // #endregion
29
+
30
+ // #region Validated property
31
+
32
+ /**
33
+ * The runtime representation of a parsed schema property, used internally during validation
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const parsed: ValidatedProperty = {
38
+ * key: 'age',
39
+ * required: true,
40
+ * types: ['number'],
41
+ * validators: { number: [(v) => v > 0] },
42
+ * };
43
+ * ```
44
+ */
45
+ export type ValidatedProperty = {
46
+ /**
47
+ * The property name in the schema
48
+ */
49
+ key: ValidatedPropertyKey;
50
+ /**
51
+ * Whether the property is required
52
+ */
53
+ required: boolean;
54
+ /**
55
+ * The allowed types for this property
56
+ */
57
+ types: ValidatedPropertyType[];
58
+ /**
59
+ * Custom validators grouped by {@link ValueName}
60
+ */
61
+ validators: ValidatedPropertyValidators;
62
+ };
63
+
64
+ /**
65
+ * Property name in schema
66
+ */
67
+ export type ValidatedPropertyKey = {
68
+ /**
69
+ * Full property key, including parent keys for nested properties _(e.g., `address.street`)_
70
+ */
71
+ full: string;
72
+ /**
73
+ * The last segment of the property key _(e.g., `street` for `address.street`)_
74
+ */
75
+ short: string;
76
+ };
77
+
78
+ /**
79
+ * A union of valid types for a {@link ValidatedProperty}'s `types` array
80
+ *
81
+ * Can be a callback _(custom validator)_, a {@link Schematic}, a nested {@link ValidatedProperty}, or a {@link ValueName} string
82
+ */
83
+ export type ValidatedPropertyType =
84
+ | GenericCallback
85
+ | ValidatedProperty[]
86
+ | Schematic<unknown>
87
+ | ValueName;
88
+
89
+ /**
90
+ * A map of validator functions keyed by {@link ValueName}, used at runtime in {@link ValidatedProperty}
91
+ *
92
+ * Each key holds an array of validator functions that receive an `unknown` value and return a `boolean`
93
+ */
94
+ export type ValidatedPropertyValidators = {
95
+ [Key in ValueName]?: Array<(value: unknown) => boolean>;
96
+ };
97
+
98
+ // #endregion
99
+
100
+ // #region Property validation
101
+
102
+ export class ValidationError extends Error {
103
+ constructor(readonly information: ValidationInformation[]) {
104
+ super(
105
+ join(
106
+ information.map(item => item.message),
107
+ '; ',
108
+ ),
109
+ );
110
+
111
+ this.name = NAME_ERROR_VALIDATION;
112
+ }
113
+ }
114
+
115
+ export type ValidationInformation = {
116
+ key: ValidationInformationKey;
117
+ message: string;
118
+ validator?: GenericCallback;
119
+ };
120
+
121
+ export type ValidationInformationKey = ValidatedPropertyKey;
122
+
123
+ // #endregion
package/src/schematic.ts CHANGED
@@ -1,14 +1,11 @@
1
1
  import {isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {PlainObject} from '@oscarpalmer/atoms/models';
3
- import {MESSAGE_SCHEMA_INVALID_TYPE, SCHEMATIC_NAME} from './constants';
4
- import {isSchematic} from './helpers';
5
- import {
6
- SchematicError,
7
- type Infer,
8
- type Schema,
9
- type TypedSchema,
10
- type ValidatedProperty,
11
- } from './models';
3
+ import {SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE, PROPERTY_SCHEMATIC} from './constants';
4
+ import {getReporting, isSchematic} from './helpers';
5
+ import type {Infer} from './models/infer.model';
6
+ import type {Schema} from './models/schema.plain.model';
7
+ import type {TypedSchema} from './models/schema.typed.model';
8
+ import {SchematicError, type ValidatedProperty} from './models/validation.model';
12
9
  import {getProperties} from './validation/property.validation';
13
10
  import {validateObject} from './validation/value.validation';
14
11
 
@@ -21,7 +18,7 @@ export class Schematic<Model> {
21
18
  #properties: ValidatedProperty[];
22
19
 
23
20
  constructor(properties: ValidatedProperty[]) {
24
- Object.defineProperty(this, SCHEMATIC_NAME, {
21
+ Object.defineProperty(this, PROPERTY_SCHEMATIC, {
25
22
  value: true,
26
23
  });
27
24
 
@@ -30,18 +27,32 @@ export class Schematic<Model> {
30
27
 
31
28
  /**
32
29
  * Does the value match the schema?
33
- * @param value - Value to validate
30
+ *
31
+ * 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.
32
+ * @param value Value to validate
33
+ * @param errors Throws an error for the first validation failure
34
+ * @returns `true` if the value matches the schema, otherwise throws an error
35
+ */
36
+ is(value: unknown, errors: 'throw'): asserts value is Model;
37
+
38
+ /**
39
+ * Does the value match the schema?
40
+ *
41
+ * Will validate that the value matches the schema and return `true` or `false`, without any validation information for validation failures.
42
+ * @param value Value to validate
34
43
  * @returns `true` if the value matches the schema, otherwise `false`
35
44
  */
36
- is(value: unknown): value is Model {
37
- return validateObject(value, this.#properties);
45
+ is(value: unknown): value is Model;
46
+
47
+ is(value: unknown, errors?: unknown): boolean {
48
+ return validateObject(value, this.#properties, getReporting(errors));
38
49
  }
39
50
  }
40
51
 
41
52
  /**
42
53
  * Create a schematic from a schema
43
- * @template Model - Schema type
44
- * @param schema - Schema to create the schematic from
54
+ * @template Model Schema type
55
+ * @param schema Schema to create the schematic from
45
56
  * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
46
57
  * @returns A schematic for the given schema
47
58
  */
@@ -49,8 +60,8 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Infer<
49
60
 
50
61
  /**
51
62
  * Create a schematic from a typed schema
52
- * @template Model - Existing type
53
- * @param schema - Typed schema to create the schematic from
63
+ * @template Model Existing type
64
+ * @param schema Typed schema to create the schematic from
54
65
  * @throws Throws {@link SchematicError} if the schema can not be converted into a schematic
55
66
  * @returns A schematic for the given typed schema
56
67
  */
@@ -62,7 +73,7 @@ export function schematic<Model extends Schema>(schema: Model): Schematic<Model>
62
73
  }
63
74
 
64
75
  if (!isPlainObject(schema)) {
65
- throw new SchematicError(MESSAGE_SCHEMA_INVALID_TYPE);
76
+ throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_TYPE);
66
77
  }
67
78
 
68
79
  return new Schematic<Model>(getProperties(schema));
@@ -2,33 +2,31 @@ import {isConstructor, isPlainObject} from '@oscarpalmer/atoms/is';
2
2
  import type {PlainObject} from '@oscarpalmer/atoms/models';
3
3
  import {join} from '@oscarpalmer/atoms/string';
4
4
  import {
5
- EXPRESSION_PROPERTY,
6
- MESSAGE_SCHEMA_INVALID_EMPTY,
7
- MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
8
- MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
9
- MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
10
- MESSAGE_VALIDATOR_INVALID_KEY,
11
- MESSAGE_VALIDATOR_INVALID_TYPE,
12
- MESSAGE_VALIDATOR_INVALID_VALUE,
5
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY,
6
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED,
7
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE,
8
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED,
9
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE,
10
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY,
11
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE,
12
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE,
13
13
  PROPERTY_REQUIRED,
14
14
  PROPERTY_TYPE,
15
15
  PROPERTY_VALIDATORS,
16
16
  TEMPLATE_PATTERN,
17
- TEMPLATE_PATTERN_KEY,
18
- TEMPLATE_PATTERN_PROPERTY,
19
17
  TYPE_ALL,
20
18
  TYPE_OBJECT,
21
19
  TYPE_UNDEFINED,
22
20
  VALIDATABLE_TYPES,
23
21
  } from '../constants';
24
22
  import {instanceOf, isSchematic} from '../helpers';
23
+ import type {ValueName} from '../models/misc.model';
25
24
  import {
26
25
  SchematicError,
27
26
  type ValidatedProperty,
28
27
  type ValidatedPropertyType,
29
28
  type ValidatedPropertyValidators,
30
- type ValueName,
31
- } from '../models';
29
+ } from '../models/validation.model';
32
30
 
33
31
  function getDisallowedProperty(obj: PlainObject): string | undefined {
34
32
  if (PROPERTY_REQUIRED in obj) {
@@ -50,7 +48,7 @@ export function getProperties(
50
48
  fromType?: boolean,
51
49
  ): ValidatedProperty[] {
52
50
  if (Object.keys(original).length === 0) {
53
- throw new SchematicError(MESSAGE_SCHEMA_INVALID_EMPTY);
51
+ throw new SchematicError(SCHEMATIC_MESSAGE_SCHEMA_INVALID_EMPTY);
54
52
  }
55
53
 
56
54
  if (fromType ?? false) {
@@ -58,10 +56,10 @@ export function getProperties(
58
56
 
59
57
  if (property != null) {
60
58
  throw new SchematicError(
61
- MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(TEMPLATE_PATTERN_KEY, prefix!).replace(
62
- TEMPLATE_PATTERN_PROPERTY,
63
- property,
64
- ),
59
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_DISALLOWED.replace(
60
+ TEMPLATE_PATTERN,
61
+ prefix!,
62
+ ).replace(TEMPLATE_PATTERN, property),
65
63
  );
66
64
  }
67
65
  }
@@ -74,12 +72,15 @@ export function getProperties(
74
72
  for (let keyIndex = 0; keyIndex < keysLength; keyIndex += 1) {
75
73
  const key = keys[keyIndex];
76
74
 
77
- if (EXPRESSION_PROPERTY.test(key)) {
78
- continue;
79
- }
80
-
75
+ const prefixed = join([prefix, key], '.');
81
76
  const value = original[key];
82
77
 
78
+ if (value == null) {
79
+ throw new SchematicError(
80
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_NULLABLE.replace(TEMPLATE_PATTERN, prefixed),
81
+ );
82
+ }
83
+
83
84
  const types: ValidatedPropertyType[] = [];
84
85
 
85
86
  let required = true;
@@ -89,11 +90,9 @@ export function getProperties(
89
90
  required = getRequired(key, value) ?? required;
90
91
  validators = getValidators(value[PROPERTY_VALIDATORS]);
91
92
 
92
- if (PROPERTY_TYPE in value) {
93
- types.push(TYPE_OBJECT, ...getTypes(key, value[PROPERTY_TYPE], prefix, true));
94
- } else {
95
- types.push(TYPE_OBJECT, ...getTypes(key, value, prefix));
96
- }
93
+ const hasType = PROPERTY_TYPE in value;
94
+
95
+ types.push(...getTypes(key, hasType ? value[PROPERTY_TYPE] : value, prefix, hasType));
97
96
  } else {
98
97
  types.push(...getTypes(key, value, prefix));
99
98
  }
@@ -103,9 +102,12 @@ export function getProperties(
103
102
  }
104
103
 
105
104
  properties.push({
106
- key,
107
105
  types,
108
106
  validators,
107
+ key: {
108
+ full: prefixed,
109
+ short: key,
110
+ },
109
111
  required: required && !types.includes(TYPE_UNDEFINED),
110
112
  });
111
113
  }
@@ -120,7 +122,7 @@ function getRequired(key: string, obj: PlainObject): boolean | undefined {
120
122
 
121
123
  if (typeof obj[PROPERTY_REQUIRED] !== 'boolean') {
122
124
  throw new SchematicError(
123
- MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
125
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_REQUIRED.replace(TEMPLATE_PATTERN, key),
124
126
  );
125
127
  }
126
128
 
@@ -147,7 +149,7 @@ function getTypes(
147
149
  break;
148
150
 
149
151
  case isPlainObject(value):
150
- types.push(...getProperties(value, join([prefix, key], '.'), fromType));
152
+ types.push(getProperties(value, join([prefix, key], '.'), fromType));
151
153
  break;
152
154
 
153
155
  case isSchematic(value):
@@ -160,14 +162,20 @@ function getTypes(
160
162
 
161
163
  default:
162
164
  throw new SchematicError(
163
- MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
165
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
166
+ TEMPLATE_PATTERN,
167
+ join([prefix, key], '.'),
168
+ ),
164
169
  );
165
170
  }
166
171
  }
167
172
 
168
173
  if (types.length === 0) {
169
174
  throw new SchematicError(
170
- MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(TEMPLATE_PATTERN, join([prefix, key], '.')),
175
+ SCHEMATIC_MESSAGE_SCHEMA_INVALID_PROPERTY_TYPE.replace(
176
+ TEMPLATE_PATTERN,
177
+ join([prefix, key], '.'),
178
+ ),
171
179
  );
172
180
  }
173
181
 
@@ -182,7 +190,7 @@ function getValidators(original: unknown): ValidatedPropertyValidators {
182
190
  }
183
191
 
184
192
  if (!isPlainObject(original)) {
185
- throw new TypeError(MESSAGE_VALIDATOR_INVALID_TYPE);
193
+ throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_TYPE);
186
194
  }
187
195
 
188
196
  const keys = Object.keys(original);
@@ -192,17 +200,19 @@ function getValidators(original: unknown): ValidatedPropertyValidators {
192
200
  const key = keys[index];
193
201
 
194
202
  if (!VALIDATABLE_TYPES.has(key as never)) {
195
- throw new TypeError(MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
203
+ throw new TypeError(SCHEMATIC_MESSAGE_VALIDATOR_INVALID_KEY.replace(TEMPLATE_PATTERN, key));
196
204
  }
197
205
 
198
206
  const value = (original as PlainObject)[key];
199
207
 
200
- validators[key as ValueName] = (Array.isArray(value) ? value : [value]).filter(item => {
208
+ validators[key as ValueName] = (Array.isArray(value) ? value : [value]).map(item => {
201
209
  if (typeof item !== 'function') {
202
- throw new TypeError(MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key));
210
+ throw new TypeError(
211
+ SCHEMATIC_MESSAGE_VALIDATOR_INVALID_VALUE.replace(TEMPLATE_PATTERN, key),
212
+ );
203
213
  }
204
214
 
205
- return true;
215
+ return item;
206
216
  });
207
217
  }
208
218