@travetto/schema 7.1.3 → 8.0.0-alpha.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/README.md +44 -38
- package/__index__.ts +1 -0
- package/package.json +3 -3
- package/src/bind-util.ts +4 -2
- package/src/data.ts +8 -3
- package/src/decorator/input.ts +23 -17
- package/src/internal/types.ts +25 -21
- package/src/service/registry-adapter.ts +15 -8
- package/src/service/registry-index.ts +8 -12
- package/src/service/types.ts +10 -9
- package/src/type-config.ts +54 -0
- package/src/validate/error.ts +3 -3
- package/src/validate/types.ts +4 -2
- package/src/validate/validator.ts +34 -33
- package/support/transformer/util.ts +1 -1
package/README.md
CHANGED
|
@@ -65,27 +65,27 @@ User:
|
|
|
65
65
|
### Fields
|
|
66
66
|
This schema provides a powerful base for data binding and validation at runtime. Additionally there may be types that cannot be detected, or some information that the programmer would like to override. Below are the supported field decorators:
|
|
67
67
|
* [@Field](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L24) defines a field that will be serialized.
|
|
68
|
-
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
69
|
-
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
70
|
-
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
71
|
-
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
72
|
-
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
73
|
-
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
74
|
-
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
75
|
-
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
76
|
-
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
77
|
-
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
68
|
+
* [@Required](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L55) defines a that field should be required
|
|
69
|
+
* [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L64) defines the allowable values that a field can have
|
|
70
|
+
* [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L90) defines a regular expression that the field value should match
|
|
71
|
+
* [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99) enforces min length of a string
|
|
72
|
+
* [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110) enforces max length of a string
|
|
73
|
+
* [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L99) enforces min value for a date or a number
|
|
74
|
+
* [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L110) enforces max value for a date or a number
|
|
75
|
+
* [@Email](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L140) ensures string field matches basic email regex
|
|
76
|
+
* [@Telephone](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L148) ensures string field matches basic telephone regex
|
|
77
|
+
* [@Url](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L156) ensures string field matches basic url regex
|
|
78
78
|
* [@Ignore](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/common.ts#L41) exclude from auto schema registration
|
|
79
|
-
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
80
|
-
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
81
|
-
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
82
|
-
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
83
|
-
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
79
|
+
* [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L172) ensures number passed in is only a whole number
|
|
80
|
+
* [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L179) ensures number passed in allows fractional values
|
|
81
|
+
* [@Currency](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L193) provides support for standard currency
|
|
82
|
+
* [@Text](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L74) indicates that a field is expecting natural language input, not just discrete values
|
|
83
|
+
* [@LongText](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L81) same as text, but expects longer form content
|
|
84
84
|
* [@Readonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L40) defines a that field should not be bindable external to the class
|
|
85
85
|
* [@Writeonly](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L33) defines a that field should not be exported in serialization, but that it can be bound to
|
|
86
86
|
* [@Secret](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L48) marks a field as being sensitive. This is used by certain logging activities to ensure sensitive information is not logged out.
|
|
87
|
-
* [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
88
|
-
* [@DiscriminatorField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#
|
|
87
|
+
* [@Specifier](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L201) attributes additional specifiers to a field, allowing for more specification beyond just the field's type.
|
|
88
|
+
* [@DiscriminatorField](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/input.ts#L208) allows for promoting a given field as the owner of the sub type discriminator.
|
|
89
89
|
|
|
90
90
|
Additionally, schemas can be nested to form more complex data structures that are able to bound and validated.
|
|
91
91
|
|
|
@@ -218,6 +218,7 @@ would produce an exception similar to following structure
|
|
|
218
218
|
$ trv main doc/person-invalid-output.ts
|
|
219
219
|
|
|
220
220
|
Validation Failed {
|
|
221
|
+
"$trv": "runtime",
|
|
221
222
|
"message": "Validation errors have occurred",
|
|
222
223
|
"category": "data",
|
|
223
224
|
"type": "ValidationResultError",
|
|
@@ -297,7 +298,7 @@ export interface ValidationError {
|
|
|
297
298
|
/**
|
|
298
299
|
* Number to compare against
|
|
299
300
|
*/
|
|
300
|
-
limit?:
|
|
301
|
+
limit?: NumericLikeIntrinsic;
|
|
301
302
|
/**
|
|
302
303
|
* The type of the field
|
|
303
304
|
*/
|
|
@@ -330,35 +331,39 @@ export type Point = [number, number];
|
|
|
330
331
|
**Code: Point Implementation**
|
|
331
332
|
```typescript
|
|
332
333
|
import { DataUtil } from '../data.ts';
|
|
334
|
+
import { SchemaTypeUtil } from '../type-config.ts';
|
|
333
335
|
|
|
334
336
|
const InvalidSymbol = Symbol();
|
|
335
337
|
|
|
336
338
|
/**
|
|
337
|
-
*
|
|
339
|
+
* Convert to tuple of two numbers
|
|
338
340
|
*/
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const bound = this.bindSchema(input);
|
|
346
|
-
return bound !== InvalidSymbol && bound && !isNaN(bound[0]) && !isNaN(bound[1]) ? undefined : 'type';
|
|
341
|
+
function bindPoint(input: unknown): [number, number] | typeof InvalidSymbol | undefined {
|
|
342
|
+
if (Array.isArray(input) && input.length === 2) {
|
|
343
|
+
const [a, b] = input.map(value => DataUtil.coerceType(value, Number, false));
|
|
344
|
+
return [a, b];
|
|
345
|
+
} else {
|
|
346
|
+
return InvalidSymbol;
|
|
347
347
|
}
|
|
348
|
+
}
|
|
348
349
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
return [a, b];
|
|
356
|
-
} else {
|
|
357
|
-
return InvalidSymbol;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
350
|
+
/**
|
|
351
|
+
* Validate we have an actual point
|
|
352
|
+
*/
|
|
353
|
+
function validatePoint(input: unknown): 'type' | undefined {
|
|
354
|
+
const bound = bindPoint(input);
|
|
355
|
+
return bound !== InvalidSymbol && bound && !isNaN(bound[0]) && !isNaN(bound[1]) ? undefined : 'type';
|
|
360
356
|
}
|
|
361
357
|
|
|
358
|
+
/**
|
|
359
|
+
* Point Contract
|
|
360
|
+
*/
|
|
361
|
+
export class PointContract { }
|
|
362
|
+
|
|
363
|
+
SchemaTypeUtil.setSchemaTypeConfig(PointContract, {
|
|
364
|
+
validate: validatePoint,
|
|
365
|
+
bind: bindPoint,
|
|
366
|
+
});
|
|
362
367
|
Object.defineProperty(PointContract, 'name', { value: 'Point' });
|
|
363
368
|
```
|
|
364
369
|
|
|
@@ -384,6 +389,7 @@ All that happens now, is the type is exported, and the class above is able to pr
|
|
|
384
389
|
$ trv main doc/custom-type-output.ts
|
|
385
390
|
|
|
386
391
|
Validation Failed {
|
|
392
|
+
"$trv": "runtime",
|
|
387
393
|
"message": "Validation errors have occurred",
|
|
388
394
|
"category": "data",
|
|
389
395
|
"type": "ValidationResultError",
|
package/__index__.ts
CHANGED
|
@@ -14,5 +14,6 @@ export * from './src/bind-util.ts';
|
|
|
14
14
|
export * from './src/data.ts';
|
|
15
15
|
export * from './src/name.ts';
|
|
16
16
|
export * from './src/types.ts';
|
|
17
|
+
export * from './src/type-config.ts';
|
|
17
18
|
export * from './src/service/registry-index.ts';
|
|
18
19
|
export * from './src/service/registry-adapter.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/schema",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Data type registry for runtime validation, reflection and binding.",
|
|
6
6
|
"keywords": [
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"directory": "module/schema"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@travetto/registry": "^
|
|
31
|
+
"@travetto/registry": "^8.0.0-alpha.0"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@travetto/transformer": "^
|
|
34
|
+
"@travetto/transformer": "^8.0.0-alpha.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependenciesMeta": {
|
|
37
37
|
"@travetto/transformer": {
|
package/src/bind-util.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { castTo, type Class, classConstruct, asFull, TypedObject, castKey } from
|
|
|
3
3
|
import { DataUtil } from './data.ts';
|
|
4
4
|
import type { SchemaInputConfig, SchemaParameterConfig, SchemaFieldMap } from './service/types.ts';
|
|
5
5
|
import { SchemaRegistryIndex } from './service/registry-index.ts';
|
|
6
|
+
import { SchemaTypeUtil } from './type-config.ts';
|
|
6
7
|
|
|
7
8
|
type BindConfig = {
|
|
8
9
|
view?: string;
|
|
@@ -25,8 +26,9 @@ export class BindUtil {
|
|
|
25
26
|
* @param value The provided value
|
|
26
27
|
*/
|
|
27
28
|
static #coerceType<T>(config: SchemaInputConfig, value: unknown): T | null | undefined {
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
const typeConfig = SchemaTypeUtil.getSchemaTypeConfig(config.type);
|
|
30
|
+
if (typeConfig?.bind) {
|
|
31
|
+
value = typeConfig.bind(value);
|
|
30
32
|
} else {
|
|
31
33
|
value = DataUtil.coerceType(value, config.type, false);
|
|
32
34
|
|
package/src/data.ts
CHANGED
|
@@ -152,9 +152,11 @@ export class DataUtil {
|
|
|
152
152
|
input :
|
|
153
153
|
typeof input === 'number' ?
|
|
154
154
|
new Date(input) :
|
|
155
|
-
|
|
156
|
-
new Date(
|
|
157
|
-
|
|
155
|
+
typeof input === 'bigint' ?
|
|
156
|
+
new Date(Number(input)) :
|
|
157
|
+
(typeof input === 'string' && /^[-]?\d+$/.test(input)) ?
|
|
158
|
+
new Date(parseInt(input, 10)) :
|
|
159
|
+
new Date(input.toString());
|
|
158
160
|
}
|
|
159
161
|
if (strict && value && Number.isNaN(value.getTime())) {
|
|
160
162
|
throw new Error(`Invalid date value: ${input}`);
|
|
@@ -162,6 +164,9 @@ export class DataUtil {
|
|
|
162
164
|
return value;
|
|
163
165
|
}
|
|
164
166
|
case Number: {
|
|
167
|
+
if (typeof input === 'bigint') {
|
|
168
|
+
return Number(input);
|
|
169
|
+
}
|
|
165
170
|
const value = `${input}`.includes('.') ? parseFloat(`${input}`) : parseInt(`${input}`, 10);
|
|
166
171
|
if (strict && Number.isNaN(value)) {
|
|
167
172
|
throw new Error(`Invalid numeric value: ${input}`);
|
package/src/decorator/input.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { type Any, type Class, type ClassInstance, getClass } from '@travetto/runtime';
|
|
1
|
+
import { type Any, type Class, type ClassInstance, getClass, type NumericLikeIntrinsic, type NumericPrimitive, type Primitive } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import { CommonRegex } from '../validate/regex.ts';
|
|
4
4
|
import { CONSTRUCTOR_PROPERTY, type SchemaInputConfig } from '../service/types.ts';
|
|
5
5
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
6
6
|
|
|
7
|
+
type StringType = string | string[];
|
|
8
|
+
type LengthType = string | unknown[] | Uint8Array | Uint16Array | Uint32Array;
|
|
9
|
+
type NumberType = NumericPrimitive | NumericPrimitive[];
|
|
10
|
+
type NumberLikeType = NumericLikeIntrinsic | NumericLikeIntrinsic[];
|
|
11
|
+
type EnumType = Exclude<Primitive, 'boolean'> | Exclude<Primitive, 'boolean'>[];
|
|
12
|
+
|
|
7
13
|
type PropType<V> = (<T extends Partial<Record<K, V | Function>>, K extends string>(
|
|
8
14
|
instance: T, property: K, idx?: TypedPropertyDescriptor<Any> | number
|
|
9
15
|
) => void);
|
|
@@ -55,7 +61,7 @@ export function Required(active = true, message?: string): PropType<unknown> { r
|
|
|
55
61
|
* @augments `@travetto/schema:Input`
|
|
56
62
|
* @kind decorator
|
|
57
63
|
*/
|
|
58
|
-
export function Enum(values: string[], message?: string): PropType<
|
|
64
|
+
export function Enum(values: string[], message?: string): PropType<EnumType> {
|
|
59
65
|
message = message || `{path} is only allowed to be "${values.join('" or "')}"`;
|
|
60
66
|
return input({ enum: { values, message } });
|
|
61
67
|
}
|
|
@@ -65,14 +71,14 @@ export function Enum(values: string[], message?: string): PropType<string | numb
|
|
|
65
71
|
* @augments `@travetto/schema:Input`
|
|
66
72
|
* @kind decorator
|
|
67
73
|
*/
|
|
68
|
-
export function Text(): PropType<
|
|
74
|
+
export function Text(): PropType<StringType> { return input({ specifiers: ['text'] }); }
|
|
69
75
|
|
|
70
76
|
/**
|
|
71
77
|
* Mark the input to indicate it's for long form text
|
|
72
78
|
* @augments `@travetto/schema:Input`
|
|
73
79
|
* @kind decorator
|
|
74
80
|
*/
|
|
75
|
-
export function LongText(): PropType<
|
|
81
|
+
export function LongText(): PropType<StringType> { return input({ specifiers: ['text', 'long'] }); }
|
|
76
82
|
|
|
77
83
|
/**
|
|
78
84
|
* Require the input to match a specific RegExp
|
|
@@ -81,7 +87,7 @@ export function LongText(): PropType<string | string[]> { return input({ specifi
|
|
|
81
87
|
* @augments `@travetto/schema:Input`
|
|
82
88
|
* @kind decorator
|
|
83
89
|
*/
|
|
84
|
-
export function Match(regex: RegExp, message?: string): PropType<
|
|
90
|
+
export function Match(regex: RegExp, message?: string): PropType<StringType> { return input({ match: { regex, message } }); }
|
|
85
91
|
|
|
86
92
|
/**
|
|
87
93
|
* The minimum length for the string or array
|
|
@@ -90,7 +96,7 @@ export function Match(regex: RegExp, message?: string): PropType<string | string
|
|
|
90
96
|
* @augments `@travetto/schema:Input`
|
|
91
97
|
* @kind decorator
|
|
92
98
|
*/
|
|
93
|
-
export function MinLength(limit: number, message?: string): PropType<
|
|
99
|
+
export function MinLength(limit: number, message?: string): PropType<LengthType> {
|
|
94
100
|
return input({ minlength: { limit, message }, ...(limit === 0 ? { required: { active: false } } : {}) });
|
|
95
101
|
}
|
|
96
102
|
|
|
@@ -101,7 +107,7 @@ export function MinLength(limit: number, message?: string): PropType<string | un
|
|
|
101
107
|
* @augments `@travetto/schema:Input`
|
|
102
108
|
* @kind decorator
|
|
103
109
|
*/
|
|
104
|
-
export function MaxLength(limit: number, message?: string): PropType<
|
|
110
|
+
export function MaxLength(limit: number, message?: string): PropType<LengthType> { return input({ maxlength: { limit, message } }); }
|
|
105
111
|
|
|
106
112
|
/**
|
|
107
113
|
* The minimum value
|
|
@@ -110,8 +116,8 @@ export function MaxLength(limit: number, message?: string): PropType<string | un
|
|
|
110
116
|
* @augments `@travetto/schema:Input`
|
|
111
117
|
* @kind decorator
|
|
112
118
|
*/
|
|
113
|
-
export function Min
|
|
114
|
-
return input({ min: { limit, message } });
|
|
119
|
+
export function Min(limit: NumericLikeIntrinsic, message?: string): PropType<NumberLikeType> {
|
|
120
|
+
return input<NumberLikeType>({ min: { limit, message } });
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
/**
|
|
@@ -121,8 +127,8 @@ export function Min<T extends number | Date>(limit: T, message?: string): PropTy
|
|
|
121
127
|
* @augments `@travetto/schema:Input`
|
|
122
128
|
* @kind decorator
|
|
123
129
|
*/
|
|
124
|
-
export function Max
|
|
125
|
-
return input({ max: { limit, message } });
|
|
130
|
+
export function Max(limit: NumericLikeIntrinsic, message?: string): PropType<NumberLikeType> {
|
|
131
|
+
return input<NumberLikeType>({ max: { limit, message } });
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/**
|
|
@@ -131,7 +137,7 @@ export function Max<T extends number | Date>(limit: T, message?: string): PropTy
|
|
|
131
137
|
* @augments `@travetto/schema:Input`
|
|
132
138
|
* @kind decorator
|
|
133
139
|
*/
|
|
134
|
-
export function Email(message?: string): PropType<
|
|
140
|
+
export function Email(message?: string): PropType<StringType> { return Match(CommonRegex.email, message); }
|
|
135
141
|
|
|
136
142
|
/**
|
|
137
143
|
* Mark an input as an telephone number
|
|
@@ -139,7 +145,7 @@ export function Email(message?: string): PropType<string | string[]> { return Ma
|
|
|
139
145
|
* @augments `@travetto/schema:Input`
|
|
140
146
|
* @kind decorator
|
|
141
147
|
*/
|
|
142
|
-
export function Telephone(message?: string): PropType<
|
|
148
|
+
export function Telephone(message?: string): PropType<StringType> { return Match(CommonRegex.telephone, message); }
|
|
143
149
|
|
|
144
150
|
/**
|
|
145
151
|
* Mark an input as a url
|
|
@@ -147,7 +153,7 @@ export function Telephone(message?: string): PropType<string | string[]> { retur
|
|
|
147
153
|
* @augments `@travetto/schema:Input`
|
|
148
154
|
* @kind decorator
|
|
149
155
|
*/
|
|
150
|
-
export function Url(message?: string): PropType<
|
|
156
|
+
export function Url(message?: string): PropType<StringType> { return Match(CommonRegex.url, message); }
|
|
151
157
|
|
|
152
158
|
/**
|
|
153
159
|
* Determine the numeric precision of the value
|
|
@@ -163,7 +169,7 @@ export function Precision(digits: number, decimals?: number): PropType<number> {
|
|
|
163
169
|
* @augments `@travetto/schema:Input`
|
|
164
170
|
* @kind decorator
|
|
165
171
|
*/
|
|
166
|
-
export function Integer(): PropType<
|
|
172
|
+
export function Integer(): PropType<NumberType> { return Precision(0); }
|
|
167
173
|
|
|
168
174
|
/**
|
|
169
175
|
* Mark a number as a float
|
|
@@ -177,14 +183,14 @@ export function Float(): PropType<number> { return Precision(10, 7); }
|
|
|
177
183
|
* @augments `@travetto/schema:Input`
|
|
178
184
|
* @kind decorator
|
|
179
185
|
*/
|
|
180
|
-
export function Long(): PropType<
|
|
186
|
+
export function Long(): PropType<NumberType> { return Precision(19, 0); }
|
|
181
187
|
|
|
182
188
|
/**
|
|
183
189
|
* Mark a number as a currency
|
|
184
190
|
* @augments `@travetto/schema:Input`
|
|
185
191
|
* @kind decorator
|
|
186
192
|
*/
|
|
187
|
-
export function Currency(): PropType<
|
|
193
|
+
export function Currency(): PropType<NumberType> { return Precision(13, 2); }
|
|
188
194
|
|
|
189
195
|
/**
|
|
190
196
|
* Specifier for the input
|
package/src/internal/types.ts
CHANGED
|
@@ -1,31 +1,35 @@
|
|
|
1
1
|
import { DataUtil } from '../data.ts';
|
|
2
|
+
import { SchemaTypeUtil } from '../type-config.ts';
|
|
2
3
|
|
|
3
4
|
const InvalidSymbol = Symbol();
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
+
* Convert to tuple of two numbers
|
|
7
8
|
*/
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const bound = this.bindSchema(input);
|
|
15
|
-
return bound !== InvalidSymbol && bound && !isNaN(bound[0]) && !isNaN(bound[1]) ? undefined : 'type';
|
|
9
|
+
function bindPoint(input: unknown): [number, number] | typeof InvalidSymbol | undefined {
|
|
10
|
+
if (Array.isArray(input) && input.length === 2) {
|
|
11
|
+
const [a, b] = input.map(value => DataUtil.coerceType(value, Number, false));
|
|
12
|
+
return [a, b];
|
|
13
|
+
} else {
|
|
14
|
+
return InvalidSymbol;
|
|
16
15
|
}
|
|
16
|
+
}
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return [a, b];
|
|
25
|
-
} else {
|
|
26
|
-
return InvalidSymbol;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
18
|
+
/**
|
|
19
|
+
* Validate we have an actual point
|
|
20
|
+
*/
|
|
21
|
+
function validatePoint(input: unknown): 'type' | undefined {
|
|
22
|
+
const bound = bindPoint(input);
|
|
23
|
+
return bound !== InvalidSymbol && bound && !isNaN(bound[0]) && !isNaN(bound[1]) ? undefined : 'type';
|
|
29
24
|
}
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Point Contract
|
|
28
|
+
*/
|
|
29
|
+
export class PointContract { }
|
|
30
|
+
|
|
31
|
+
SchemaTypeUtil.setSchemaTypeConfig(PointContract, {
|
|
32
|
+
validate: validatePoint,
|
|
33
|
+
bind: bindPoint,
|
|
34
|
+
});
|
|
35
|
+
Object.defineProperty(PointContract, 'name', { value: 'Point' });
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { RegistryAdapter } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeError, BinaryUtil, castKey, castTo, type Class, describeFunction, safeAssign } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
type SchemaClassConfig, type SchemaMethodConfig, type SchemaFieldConfig,
|
|
6
6
|
type SchemaParameterConfig, type SchemaInputConfig, type SchemaFieldMap, type SchemaCoreConfig,
|
|
7
|
-
CONSTRUCTOR_PROPERTY
|
|
7
|
+
type SchemaBasicType, CONSTRUCTOR_PROPERTY
|
|
8
8
|
} from './types.ts';
|
|
9
9
|
|
|
10
10
|
export type SchemaDiscriminatedInfo = Required<Pick<SchemaClassConfig, 'discriminatedType' | 'discriminatedField' | 'discriminatedBase'>>;
|
|
@@ -32,6 +32,12 @@ function combineCore<T extends SchemaCoreConfig>(base: T, config: Partial<T>): T
|
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function ensureBinary<T extends SchemaBasicType>(config?: T): void {
|
|
36
|
+
if (config?.type) {
|
|
37
|
+
config.binary = BinaryUtil.isBinaryTypeReference(config.type);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
35
41
|
function combineInputs<T extends SchemaInputConfig>(base: T, configs: Partial<T>[]): T {
|
|
36
42
|
for (const config of configs) {
|
|
37
43
|
if (config) {
|
|
@@ -48,6 +54,7 @@ function combineInputs<T extends SchemaInputConfig>(base: T, configs: Partial<T>
|
|
|
48
54
|
});
|
|
49
55
|
}
|
|
50
56
|
combineCore(base, config);
|
|
57
|
+
ensureBinary(base);
|
|
51
58
|
}
|
|
52
59
|
return base;
|
|
53
60
|
}
|
|
@@ -65,6 +72,7 @@ function combineMethods<T extends SchemaMethodConfig>(base: T, configs: Partial<
|
|
|
65
72
|
safeAssign(base.parameters[param.index], param);
|
|
66
73
|
}
|
|
67
74
|
}
|
|
75
|
+
ensureBinary(config.returnType);
|
|
68
76
|
}
|
|
69
77
|
return base;
|
|
70
78
|
}
|
|
@@ -290,7 +298,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
290
298
|
getMethod(method: string): SchemaMethodConfig {
|
|
291
299
|
const methodConfig = this.#config.methods[method];
|
|
292
300
|
if (!methodConfig) {
|
|
293
|
-
throw new
|
|
301
|
+
throw new RuntimeError(`Unknown method ${String(method)} on class ${this.#cls.Ⲑid}`);
|
|
294
302
|
}
|
|
295
303
|
return methodConfig;
|
|
296
304
|
}
|
|
@@ -304,7 +312,7 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
304
312
|
return this.#config.fields;
|
|
305
313
|
}
|
|
306
314
|
if (!this.#views.has(view)) {
|
|
307
|
-
throw new
|
|
315
|
+
throw new RuntimeError(`Unknown view ${view} for class ${this.#cls.Ⲑid}`);
|
|
308
316
|
}
|
|
309
317
|
return this.#views.get(view)!;
|
|
310
318
|
}
|
|
@@ -313,14 +321,13 @@ export class SchemaRegistryAdapter implements RegistryAdapter<SchemaClassConfig>
|
|
|
313
321
|
* Provides the prototype-derived descriptor for a property
|
|
314
322
|
*/
|
|
315
323
|
getAccessorDescriptor(field: string): PropertyDescriptor {
|
|
316
|
-
|
|
324
|
+
return this.#accessorDescriptors.getOrInsertComputed(field, () => {
|
|
317
325
|
let proto = this.#cls.prototype;
|
|
318
326
|
while (proto && !Object.hasOwn(proto, field)) {
|
|
319
327
|
proto = proto.prototype;
|
|
320
328
|
}
|
|
321
|
-
|
|
322
|
-
}
|
|
323
|
-
return this.#accessorDescriptors.get(field)!;
|
|
329
|
+
return Object.getOwnPropertyDescriptor(proto, field)!;
|
|
330
|
+
});
|
|
324
331
|
}
|
|
325
332
|
|
|
326
333
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type RegistrationMethods, type RegistryIndex, RegistryIndexStore, Registry } from '@travetto/registry';
|
|
2
|
-
import {
|
|
2
|
+
import { RuntimeError, castKey, castTo, type Class, classConstruct, getParentClass } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import type { SchemaFieldConfig, SchemaClassConfig } from './types.ts';
|
|
5
5
|
import { type SchemaDiscriminatedInfo, SchemaRegistryAdapter } from './registry-adapter.ts';
|
|
@@ -75,10 +75,7 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
77
|
const base = this.getBaseClass(cls);
|
|
78
|
-
|
|
79
|
-
this.#byDiscriminatedTypes.set(base, new Map());
|
|
80
|
-
}
|
|
81
|
-
this.#byDiscriminatedTypes.get(base)!.set(config.discriminatedType, cls);
|
|
78
|
+
this.#byDiscriminatedTypes.getOrInsert(base, new Map()).set(config.discriminatedType, cls);
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
beforeChangeSetComplete(): void {
|
|
@@ -97,7 +94,7 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
97
94
|
* Find base schema class for a given class
|
|
98
95
|
*/
|
|
99
96
|
getBaseClass(cls: Class): Class {
|
|
100
|
-
|
|
97
|
+
return this.#baseSchema.getOrInsertComputed(cls, () => {
|
|
101
98
|
let config = this.getClassConfig(cls);
|
|
102
99
|
let parent: Class | undefined = cls;
|
|
103
100
|
while (parent && config.discriminatedType && !config.discriminatedBase) {
|
|
@@ -106,9 +103,8 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
106
103
|
config = this.store.getOptional(parent)?.get() ?? config;
|
|
107
104
|
}
|
|
108
105
|
}
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
return this.#baseSchema.get(cls)!;
|
|
106
|
+
return config.class;
|
|
107
|
+
});
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
/**
|
|
@@ -125,14 +121,14 @@ export class SchemaRegistryIndex implements RegistryIndex {
|
|
|
125
121
|
const map = this.#byDiscriminatedTypes.get(base);
|
|
126
122
|
const type = castTo<string>(item[castKey<T>(discriminatedField)]) ?? discriminatedType;
|
|
127
123
|
if (!type) {
|
|
128
|
-
throw new
|
|
124
|
+
throw new RuntimeError(`Unable to resolve discriminated type for class ${base.name} without a type`);
|
|
129
125
|
}
|
|
130
126
|
if (!map?.has(type)) {
|
|
131
|
-
throw new
|
|
127
|
+
throw new RuntimeError(`Unable to resolve discriminated type '${type}' for class ${base.name}`);
|
|
132
128
|
}
|
|
133
129
|
const requested = map.get(type)!;
|
|
134
130
|
if (!(classConstruct(requested) instanceof targetClass)) {
|
|
135
|
-
throw new
|
|
131
|
+
throw new RuntimeError(`Resolved discriminated type '${type}' for class ${base.name} is not an instance of requested type ${targetClass.name}`);
|
|
136
132
|
}
|
|
137
133
|
return requested;
|
|
138
134
|
}
|
package/src/service/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Any, Class, Primitive } from '@travetto/runtime';
|
|
1
|
+
import type { Any, Class, IntrinsicType, NumericLikeIntrinsic, Primitive } from '@travetto/runtime';
|
|
2
2
|
|
|
3
3
|
import type { MethodValidatorFn, ValidatorFn } from '../validate/types.ts';
|
|
4
4
|
|
|
@@ -19,13 +19,14 @@ export type SchemaBasicType = {
|
|
|
19
19
|
* Is the type an array
|
|
20
20
|
*/
|
|
21
21
|
array?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Is the type a binary type
|
|
24
|
+
*/
|
|
25
|
+
binary?: boolean;
|
|
22
26
|
/**
|
|
23
27
|
* The class tied to the type
|
|
24
28
|
*/
|
|
25
|
-
type: Class
|
|
26
|
-
bindSchema?(input: unknown): undefined | unknown;
|
|
27
|
-
validateSchema?(input: unknown): string | undefined;
|
|
28
|
-
};
|
|
29
|
+
type: Class;
|
|
29
30
|
/**
|
|
30
31
|
* Is the field a foreign type
|
|
31
32
|
*/
|
|
@@ -167,11 +168,11 @@ export interface SchemaInputConfig extends SchemaCoreConfig, SchemaBasicType {
|
|
|
167
168
|
/**
|
|
168
169
|
* Minimum value configuration
|
|
169
170
|
*/
|
|
170
|
-
min?: { limit:
|
|
171
|
+
min?: { limit: NumericLikeIntrinsic, message?: string };
|
|
171
172
|
/**
|
|
172
173
|
* Maximum value configuration
|
|
173
174
|
*/
|
|
174
|
-
max?: { limit:
|
|
175
|
+
max?: { limit: NumericLikeIntrinsic, message?: string };
|
|
175
176
|
/**
|
|
176
177
|
* Minimum length configuration
|
|
177
178
|
*/
|
|
@@ -183,11 +184,11 @@ export interface SchemaInputConfig extends SchemaCoreConfig, SchemaBasicType {
|
|
|
183
184
|
/**
|
|
184
185
|
* Enumerated values
|
|
185
186
|
*/
|
|
186
|
-
enum?: { values:
|
|
187
|
+
enum?: { values: Primitive[], message: string };
|
|
187
188
|
/**
|
|
188
189
|
* Default value
|
|
189
190
|
*/
|
|
190
|
-
default?:
|
|
191
|
+
default?: IntrinsicType | [];
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
/**
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isUint8Array, isUint16Array, isUint32Array, isArrayBuffer } from 'node:util/types';
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
3
|
+
|
|
4
|
+
import { type Class, type BinaryArray, type BinaryType, type BinaryStream, BinaryUtil, toConcrete } from '@travetto/runtime';
|
|
5
|
+
|
|
6
|
+
type SchemaTypeConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* Controls how inbound data is typed
|
|
9
|
+
*/
|
|
10
|
+
bind?(input: unknown): undefined | unknown;
|
|
11
|
+
/**
|
|
12
|
+
* Controls how provided data is validated
|
|
13
|
+
*/
|
|
14
|
+
validate?(input: unknown): string | undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Utility for managing schema type configuration
|
|
19
|
+
*/
|
|
20
|
+
export class SchemaTypeUtil {
|
|
21
|
+
static cache = new Map<Function, SchemaTypeConfig>();
|
|
22
|
+
|
|
23
|
+
static {
|
|
24
|
+
// Primitive Types
|
|
25
|
+
this.register(Date, value => value instanceof Date && !Number.isNaN(value.getTime()));
|
|
26
|
+
// Binary Types
|
|
27
|
+
this.register(Buffer, Buffer.isBuffer);
|
|
28
|
+
this.register(Uint8Array, isUint8Array);
|
|
29
|
+
this.register(Uint16Array, isUint16Array);
|
|
30
|
+
this.register(Uint32Array, isUint32Array);
|
|
31
|
+
this.register(ArrayBuffer, isArrayBuffer);
|
|
32
|
+
this.register(ReadableStream, value => value instanceof ReadableStream);
|
|
33
|
+
this.register(Readable, value => value instanceof Readable);
|
|
34
|
+
this.register(toConcrete<BinaryType>(), BinaryUtil.isBinaryType);
|
|
35
|
+
this.register(toConcrete<BinaryArray>(), BinaryUtil.isBinaryArray);
|
|
36
|
+
this.register(toConcrete<BinaryStream>(), BinaryUtil.isBinaryStream);
|
|
37
|
+
this.register(Blob, value => value instanceof Blob);
|
|
38
|
+
this.register(File, value => value instanceof File);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static register(type: Class | Function, fn: (value: unknown) => boolean): void {
|
|
42
|
+
SchemaTypeUtil.setSchemaTypeConfig(type, {
|
|
43
|
+
validate: (item: unknown) => fn(item) ? undefined : 'type'
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
static getSchemaTypeConfig(type: Function): SchemaTypeConfig | undefined {
|
|
48
|
+
return this.cache.get(type);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static setSchemaTypeConfig(type: Function, config: SchemaTypeConfig): void {
|
|
52
|
+
this.cache.set(type, config);
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/validate/error.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Class,
|
|
1
|
+
import { type Class, RuntimeError } from '@travetto/runtime';
|
|
2
2
|
import type { ValidationError } from './types.ts';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -6,7 +6,7 @@ import type { ValidationError } from './types.ts';
|
|
|
6
6
|
*
|
|
7
7
|
* Hold all the validation errors for a given schema validation
|
|
8
8
|
*/
|
|
9
|
-
export class ValidationResultError extends
|
|
9
|
+
export class ValidationResultError extends RuntimeError<{ errors: ValidationError[] }> {
|
|
10
10
|
constructor(errors: ValidationError[]) {
|
|
11
11
|
super('Validation errors have occurred', { category: 'data', details: { errors } });
|
|
12
12
|
}
|
|
@@ -16,7 +16,7 @@ export class ValidationResultError extends AppError<{ errors: ValidationError[]
|
|
|
16
16
|
* Represents when a requested objects's type doesn't match the class being used to request.
|
|
17
17
|
* Primarily applies to polymorphic types
|
|
18
18
|
*/
|
|
19
|
-
export class TypeMismatchError extends
|
|
19
|
+
export class TypeMismatchError extends RuntimeError {
|
|
20
20
|
constructor(cls: Class | string, type: string) {
|
|
21
21
|
super(`Expected ${typeof cls === 'string' ? cls : cls.name} but found ${type}`, { category: 'data' });
|
|
22
22
|
}
|
package/src/validate/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { NumericLikeIntrinsic } from '@travetto/runtime';
|
|
2
|
+
|
|
1
3
|
export type ValidationKindCore = 'required' | 'match' | 'min' | 'max' | 'minlength' | 'maxlength' | 'enum' | 'type';
|
|
2
4
|
export type ValidationKind = ValidationKindCore | string;
|
|
3
5
|
|
|
@@ -29,7 +31,7 @@ export interface ValidationError {
|
|
|
29
31
|
/**
|
|
30
32
|
* Number to compare against
|
|
31
33
|
*/
|
|
32
|
-
limit?:
|
|
34
|
+
limit?: NumericLikeIntrinsic;
|
|
33
35
|
/**
|
|
34
36
|
* The type of the field
|
|
35
37
|
*/
|
|
@@ -67,7 +69,7 @@ export interface ValidationResult {
|
|
|
67
69
|
/**
|
|
68
70
|
* Number to compare against
|
|
69
71
|
*/
|
|
70
|
-
limit?:
|
|
72
|
+
limit?: NumericLikeIntrinsic;
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
type OrPromise<T> = T | Promise<T>;
|
|
@@ -7,6 +7,11 @@ import { isValidationError, TypeMismatchError, ValidationResultError } from './e
|
|
|
7
7
|
import { DataUtil } from '../data.ts';
|
|
8
8
|
import { CommonRegexToName } from './regex.ts';
|
|
9
9
|
import { SchemaRegistryIndex } from '../service/registry-index.ts';
|
|
10
|
+
import { SchemaTypeUtil } from '../type-config.ts';
|
|
11
|
+
import { UnknownType } from '../types.ts';
|
|
12
|
+
|
|
13
|
+
const PrimitiveTypes = new Set<Function>([String, Number, BigInt, Boolean]);
|
|
14
|
+
type NumericComparable = number | bigint | Date;
|
|
10
15
|
|
|
11
16
|
/**
|
|
12
17
|
* Get the schema config for Class/Schema config, including support for polymorphism
|
|
@@ -22,8 +27,12 @@ function isClassInstance<T>(value: unknown): value is ClassInstance<T> {
|
|
|
22
27
|
return !DataUtil.isPlainObject(value) && value !== null && typeof value === 'object' && !!value.constructor;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
|
-
function isRangeValue(value: unknown): value is
|
|
26
|
-
return typeof value === '
|
|
30
|
+
function isRangeValue(value: unknown): value is NumericComparable {
|
|
31
|
+
return typeof value === 'number' || typeof value === 'bigint' || value instanceof Date;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isLengthValue(value: unknown): value is { length: number } {
|
|
35
|
+
return (typeof value === 'string' || (typeof value === 'object' && !!value && 'length' in value && typeof value.length === 'number'));
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
/**
|
|
@@ -72,7 +81,7 @@ export class SchemaValidator {
|
|
|
72
81
|
const { type, array } = input;
|
|
73
82
|
const complex = SchemaRegistryIndex.has(type);
|
|
74
83
|
|
|
75
|
-
if (type === Object) {
|
|
84
|
+
if (type === Object || type === UnknownType) {
|
|
76
85
|
return [];
|
|
77
86
|
} else if (array) {
|
|
78
87
|
if (!Array.isArray(value)) {
|
|
@@ -105,13 +114,10 @@ export class SchemaValidator {
|
|
|
105
114
|
* @param key The bounds to check
|
|
106
115
|
* @param value The value to validate
|
|
107
116
|
*/
|
|
108
|
-
static #validateRange(input: SchemaInputConfig, key: 'min' | 'max', value:
|
|
117
|
+
static #validateRange(input: SchemaInputConfig, key: 'min' | 'max', value: NumericComparable): boolean {
|
|
109
118
|
const config = input[key]!;
|
|
110
|
-
const parsed = (
|
|
111
|
-
|
|
112
|
-
(value instanceof Date ? value.getTime() : value);
|
|
113
|
-
|
|
114
|
-
const boundary = (typeof config.limit === 'number' ? config.limit : config.limit.getTime());
|
|
119
|
+
const parsed = (value instanceof Date ? value.getTime() : value);
|
|
120
|
+
const boundary = (config.limit instanceof Date) ? config.limit.getTime() : config.limit;
|
|
115
121
|
return key === 'min' ? parsed < boundary : parsed > boundary;
|
|
116
122
|
}
|
|
117
123
|
|
|
@@ -122,37 +128,37 @@ export class SchemaValidator {
|
|
|
122
128
|
* @param value The actual value
|
|
123
129
|
*/
|
|
124
130
|
static #validateInput(input: SchemaInputConfig, value: unknown): ValidationResult[] {
|
|
125
|
-
const criteria:
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
(input.type === String && (typeof value !== 'string')) ||
|
|
129
|
-
(input.type === Number && ((typeof value !== 'number') || Number.isNaN(value))) ||
|
|
130
|
-
(input.type === Date && (!(value instanceof Date) || Number.isNaN(value.getTime()))) ||
|
|
131
|
-
(input.type === Boolean && typeof value !== 'boolean')
|
|
132
|
-
) {
|
|
133
|
-
criteria.push(['type']);
|
|
134
|
-
return [{ kind: 'type', type: input.type.name.toLowerCase() }];
|
|
135
|
-
}
|
|
131
|
+
const criteria: [string, SchemaInputConfig[ValidationKindCore]][] = [];
|
|
132
|
+
const config = SchemaTypeUtil.getSchemaTypeConfig(input.type);
|
|
136
133
|
|
|
137
|
-
if (
|
|
138
|
-
const kind =
|
|
134
|
+
if (config?.validate) {
|
|
135
|
+
const kind = config.validate(value);
|
|
139
136
|
switch (kind) {
|
|
140
137
|
case undefined: break;
|
|
141
138
|
case 'type': return [{ kind, type: input.type.name }];
|
|
142
|
-
default:
|
|
143
|
-
|
|
139
|
+
default: return [{ kind, value }];
|
|
140
|
+
}
|
|
141
|
+
} else if (PrimitiveTypes.has(input.type)) {
|
|
142
|
+
if (typeof value !== input.type.name.toLowerCase()) {
|
|
143
|
+
return [{ kind: 'type', type: input.type.name.toLowerCase() }];
|
|
144
|
+
} else if (Number.isNaN(value)) {
|
|
145
|
+
return [{ kind: 'type', type: 'number' }];
|
|
146
|
+
}
|
|
147
|
+
} else if (SchemaRegistryIndex.has(input.type)) {
|
|
148
|
+
if (!(value instanceof input.type)) { // If not an instance of the type
|
|
149
|
+
return [{ kind: 'type', type: input.type.name }];
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
|
|
147
|
-
if (input.match && !input.match.regex.test(
|
|
153
|
+
if (input.match && (typeof value !== 'string' || !input.match.regex.test(value))) {
|
|
148
154
|
criteria.push(['match', input.match]);
|
|
149
155
|
}
|
|
150
156
|
|
|
151
|
-
if (input.minlength &&
|
|
157
|
+
if (input.minlength && (!isLengthValue(value) || value.length < input.minlength.limit)) {
|
|
152
158
|
criteria.push(['minlength', input.minlength]);
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
if (input.maxlength &&
|
|
161
|
+
if (input.maxlength && (!isLengthValue(value) || value.length > input.maxlength.limit)) {
|
|
156
162
|
criteria.push(['maxlength', input.maxlength]);
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -168,12 +174,7 @@ export class SchemaValidator {
|
|
|
168
174
|
criteria.push(['max', input.max]);
|
|
169
175
|
}
|
|
170
176
|
|
|
171
|
-
|
|
172
|
-
for (const [key, block] of criteria) {
|
|
173
|
-
errors.push({ ...block, kind: key, value });
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return errors;
|
|
177
|
+
return criteria.map(([key, block]) => ({ ...block, kind: key, value }));
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
/**
|
|
@@ -215,7 +215,7 @@ class ${uniqueId} extends ${type.mappedClassName} {
|
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
const resolved = this.toConcreteType(state, typeExpr, node, config?.root ?? node);
|
|
218
|
-
const type = typeExpr.key === 'foreign' ? state.getConcreteType(node) :
|
|
218
|
+
const type = typeExpr.key === 'foreign' ? state.getConcreteType(node, state.factory.createIdentifier('Object')) :
|
|
219
219
|
ts.isArrayLiteralExpression(resolved) ? resolved.elements[0] : resolved;
|
|
220
220
|
|
|
221
221
|
params.unshift(LiteralUtil.fromLiteral(state.factory, {
|