ts-fortress 2.0.4 → 2.1.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 +56 -1
- package/dist/record/record.d.mts +2 -0
- package/dist/record/record.d.mts.map +1 -1
- package/dist/record/record.mjs +19 -3
- package/dist/record/record.mjs.map +1 -1
- package/package.json +1 -1
- package/src/array/array-of-length.test.mts +16 -1
- package/src/array/array.test.mts +16 -1
- package/src/array/non-empty-array.test.mts +16 -1
- package/src/array/tuple.test.mts +18 -1
- package/src/compose/intersection.test.mts +43 -1
- package/src/compose/merge-records.test.mts +16 -1
- package/src/compose/union.test.mts +16 -1
- package/src/enum/enum.test.mts +16 -1
- package/src/enum/uint-range.test.mts +14 -1
- package/src/primitives/null.test.mts +170 -0
- package/src/primitives/number.test.mts +284 -0
- package/src/primitives/string.test.mts +341 -0
- package/src/primitives/undefined.test.mts +179 -0
- package/src/record/key-value-record.test.mts +27 -1
- package/src/record/keyof.test.mts +12 -0
- package/src/record/partial.test.mts +18 -0
- package/src/record/record-allow-excess-properties.test.mts +180 -0
- package/src/record/record-strict-composition-simple.test.mts +134 -0
- package/src/record/record-strict-composition.test.mts +403 -0
- package/src/record/record.mts +52 -25
- package/src/record/record.test.mts +71 -1
package/README.md
CHANGED
|
@@ -541,6 +541,35 @@ const UserProfileType = t.record({
|
|
|
541
541
|
}),
|
|
542
542
|
),
|
|
543
543
|
});
|
|
544
|
+
|
|
545
|
+
// Strict validation (disallow excess properties)
|
|
546
|
+
const StrictUserType = t.record(
|
|
547
|
+
{
|
|
548
|
+
id: t.string(''),
|
|
549
|
+
name: t.string(''),
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
allowExcessProperties: false, // Reject any properties not defined in schema
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
// Permissive validation (allow excess properties) - this is the default
|
|
557
|
+
const PermissiveUserType = t.record(
|
|
558
|
+
{
|
|
559
|
+
id: t.string(''),
|
|
560
|
+
name: t.string(''),
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
allowExcessProperties: true, // Allow additional properties (default behavior)
|
|
564
|
+
},
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
// Example usage
|
|
568
|
+
const strictData = { id: '123', name: 'John', extra: 'not allowed' };
|
|
569
|
+
console.log(StrictUserType.is(strictData)); // false - 'extra' property causes rejection
|
|
570
|
+
|
|
571
|
+
const permissiveData = { id: '123', name: 'John', extra: 'allowed' };
|
|
572
|
+
console.log(PermissiveUserType.is(permissiveData)); // true - 'extra' property is allowed
|
|
544
573
|
```
|
|
545
574
|
|
|
546
575
|
### Branded Types
|
|
@@ -649,6 +678,31 @@ try {
|
|
|
649
678
|
} catch (error) {
|
|
650
679
|
console.error('Validation failed:', error.message);
|
|
651
680
|
}
|
|
681
|
+
|
|
682
|
+
// Excess property validation example
|
|
683
|
+
const StrictType = t.record(
|
|
684
|
+
{
|
|
685
|
+
name: t.string(''),
|
|
686
|
+
age: t.number(0),
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
allowExcessProperties: false,
|
|
690
|
+
},
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
const dataWithExcess = { name: 'John', age: 30, extra: 'not allowed' };
|
|
694
|
+
const strictResult = StrictType.validate(dataWithExcess);
|
|
695
|
+
|
|
696
|
+
if (Result.isErr(strictResult)) {
|
|
697
|
+
console.log(strictResult.value);
|
|
698
|
+
// [{
|
|
699
|
+
// path: ['extra'],
|
|
700
|
+
// actualValue: 'not allowed',
|
|
701
|
+
// expectedType: '{ name: string, age: number }',
|
|
702
|
+
// message: 'Excess property "extra" is not allowed',
|
|
703
|
+
// typeName: '{ name: string, age: number }'
|
|
704
|
+
// }]
|
|
705
|
+
}
|
|
652
706
|
```
|
|
653
707
|
|
|
654
708
|
### ValidationError Structure
|
|
@@ -684,7 +738,8 @@ type ValidationError = Readonly<{
|
|
|
684
738
|
|
|
685
739
|
### Objects
|
|
686
740
|
|
|
687
|
-
- `t.record(schema)` - Object validation
|
|
741
|
+
- `t.record(schema, options?)` - Object validation
|
|
742
|
+
- `options.allowExcessProperties?: boolean` - Allow properties not defined in schema (default: true)
|
|
688
743
|
- `t.keyValueRecord(keyType, valueType)` - Corresponding to the `Record<K, V>` type
|
|
689
744
|
- `t.partial(recordType)` - Make all fields optional
|
|
690
745
|
- `t.optional(type)` - Optional field wrapper
|
package/dist/record/record.d.mts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type Type, type TypeOf } from '../type.mjs';
|
|
2
2
|
export declare const record: <const R extends Record<string, Type<unknown>>>(source: R, options?: Partial<Readonly<{
|
|
3
3
|
typeName: string;
|
|
4
|
+
/** @default true */
|
|
5
|
+
allowExcessProperties: boolean;
|
|
4
6
|
}>>) => Type<RecordTypeValue<R>>;
|
|
5
7
|
type RecordTypeValue<R extends Record<string, Type<unknown>>> = TsFortressInternal.RecordTypeValueImpl<R>;
|
|
6
8
|
declare namespace TsFortressInternal {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAClE,QAAQ,CAAC,EACT,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"record.d.mts","sourceRoot":"","sources":["../../src/record/record.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AASrD,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EAClE,QAAQ,CAAC,EACT,UAAU,OAAO,CACf,QAAQ,CAAC;IACP,QAAQ,EAAE,MAAM,CAAC;IAEjB,oBAAoB;IACpB,qBAAqB,EAAE,OAAO,CAAC;CAChC,CAAC,CACH,KACA,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC,CAwGzB,CAAC;AAEF,KAAK,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAC1D,kBAAkB,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;AAE5C,kBAAU,kBAAkB,CAAC;IAC3B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IACrE,sBAAsB,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC;IAEjD,KAAK,sBAAsB,CACzB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,EACvC,YAAY,SAAS,MAAM,CAAC,IAC1B,QAAQ,CACV;SACG,GAAG,IAAI,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACvC,GAAG;SACD,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACxD,CACF,CAAC;IAEF,KAAK,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI;SAC9D,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;YAAE,QAAQ,EAAE,IAAI,CAAA;SAAE,GAAG,CAAC,GAAG,KAAK;KAC5D,CAAC,MAAM,CAAC,CAAC,CAAC;;CACZ"}
|
package/dist/record/record.mjs
CHANGED
|
@@ -5,10 +5,12 @@ import { createIsFn } from '../utils/create-is-fn.mjs';
|
|
|
5
5
|
import { createPrimitiveValidationError, prependPathToValidationErrors } from '../validation-error.mjs';
|
|
6
6
|
|
|
7
7
|
const record = (source, options) => {
|
|
8
|
+
const sourceKeys = new Set(Object.keys(source));
|
|
8
9
|
const typeNameFilled = options?.typeName ??
|
|
9
10
|
`{ ${Object.entries(source)
|
|
10
11
|
.map(([k, v]) => `${k}: ${v.typeName}`)
|
|
11
12
|
.join(', ')} }`;
|
|
13
|
+
const allowExcessProperties = options?.allowExcessProperties ?? true;
|
|
12
14
|
const defaultValue =
|
|
13
15
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
14
16
|
Object.fromEntries(Object.entries(source).map(([key, value]) => tp(key, value.defaultValue)));
|
|
@@ -22,7 +24,7 @@ const record = (source, options) => {
|
|
|
22
24
|
}),
|
|
23
25
|
]);
|
|
24
26
|
}
|
|
25
|
-
const
|
|
27
|
+
const defaultErrors = Object.entries(source).flatMap(([k, valueType]) => {
|
|
26
28
|
if (!Object.hasOwn(a, k)) {
|
|
27
29
|
if (source[k]?.optional === true)
|
|
28
30
|
return [];
|
|
@@ -43,8 +45,22 @@ const record = (source, options) => {
|
|
|
43
45
|
}
|
|
44
46
|
return [];
|
|
45
47
|
});
|
|
46
|
-
if
|
|
47
|
-
|
|
48
|
+
// Check for excess properties if allowExcessProperties is false
|
|
49
|
+
if (!allowExcessProperties) {
|
|
50
|
+
const excessKeys = Object.keys(a).filter((key) => !sourceKeys.has(key));
|
|
51
|
+
if (excessKeys.length > 0) {
|
|
52
|
+
const excessErrors = excessKeys.map((key) => ({
|
|
53
|
+
path: [key],
|
|
54
|
+
actualValue: a[key],
|
|
55
|
+
typeName: typeNameFilled,
|
|
56
|
+
expectedType: typeNameFilled,
|
|
57
|
+
message: `Excess property "${key}" is not allowed`,
|
|
58
|
+
}));
|
|
59
|
+
return Result.err([...defaultErrors, ...excessErrors]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (defaultErrors.length > 0) {
|
|
63
|
+
return Result.err(defaultErrors);
|
|
48
64
|
}
|
|
49
65
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
|
|
50
66
|
return Result.ok(a);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;MAUa,MAAM,GAAG,CACpB,MAAS,EACT,
|
|
1
|
+
{"version":3,"file":"record.mjs","sources":["../../src/record/record.mts"],"sourcesContent":[null],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { expectType, Result } from 'ts-data-forge';
|
|
2
2
|
import { number } from '../primitives/index.mjs';
|
|
3
3
|
import { type TypeOf } from '../type.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ValidationError,
|
|
6
|
+
validationErrorsToMessages,
|
|
7
|
+
} from '../validation-error.mjs';
|
|
5
8
|
import { arrayOfLength } from './array-of-length.mjs';
|
|
6
9
|
|
|
7
10
|
describe('arrayOfLength', () => {
|
|
@@ -74,6 +77,18 @@ describe('arrayOfLength', () => {
|
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
describe('validate', () => {
|
|
80
|
+
test('truthy case', () => {
|
|
81
|
+
const ys: unknown = [5, 6, 7, 8];
|
|
82
|
+
|
|
83
|
+
const result = xs.validate(ys);
|
|
84
|
+
expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
|
|
85
|
+
expect(Result.isOk(result)).toBe(true);
|
|
86
|
+
|
|
87
|
+
if (Result.isOk(result)) {
|
|
88
|
+
expect(result.value).toStrictEqual([5, 6, 7, 8]);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
77
92
|
test('falsy case 1', () => {
|
|
78
93
|
const ys: unknown = [];
|
|
79
94
|
|
package/src/array/array.test.mts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { expectType, Result } from 'ts-data-forge';
|
|
2
2
|
import { number } from '../primitives/index.mjs';
|
|
3
3
|
import { type TypeOf } from '../type.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ValidationError,
|
|
6
|
+
validationErrorsToMessages,
|
|
7
|
+
} from '../validation-error.mjs';
|
|
5
8
|
import { array } from './array.mjs';
|
|
6
9
|
|
|
7
10
|
describe('array', () => {
|
|
@@ -58,6 +61,18 @@ describe('array', () => {
|
|
|
58
61
|
});
|
|
59
62
|
|
|
60
63
|
describe('validate', () => {
|
|
64
|
+
test('truthy case', () => {
|
|
65
|
+
const ys: unknown = [1, 2, 3];
|
|
66
|
+
|
|
67
|
+
const result = xs.validate(ys);
|
|
68
|
+
expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
|
|
69
|
+
expect(Result.isOk(result)).toBe(true);
|
|
70
|
+
|
|
71
|
+
if (Result.isOk(result)) {
|
|
72
|
+
expect(result.value).toStrictEqual([1, 2, 3]);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
61
76
|
test('falsy case', () => {
|
|
62
77
|
const ys: unknown = ['1', '', 3];
|
|
63
78
|
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { expectType, Result } from 'ts-data-forge';
|
|
2
2
|
import { number } from '../primitives/index.mjs';
|
|
3
3
|
import { type TypeOf } from '../type.mjs';
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ValidationError,
|
|
6
|
+
validationErrorsToMessages,
|
|
7
|
+
} from '../validation-error.mjs';
|
|
5
8
|
import { nonEmptyArray } from './non-empty-array.mjs';
|
|
6
9
|
|
|
7
10
|
describe('nonEmptyArray', () => {
|
|
@@ -74,6 +77,18 @@ describe('nonEmptyArray', () => {
|
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
describe('validate', () => {
|
|
80
|
+
test('truthy case', () => {
|
|
81
|
+
const ys: unknown = [1, 2, 3];
|
|
82
|
+
|
|
83
|
+
const result = xs.validate(ys);
|
|
84
|
+
expectType<typeof result, Result<Xs, readonly ValidationError[]>>('=');
|
|
85
|
+
expect(Result.isOk(result)).toBe(true);
|
|
86
|
+
|
|
87
|
+
if (Result.isOk(result)) {
|
|
88
|
+
expect(result.value).toStrictEqual([1, 2, 3]);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
77
92
|
test('falsy case 1', () => {
|
|
78
93
|
const ys: unknown = [];
|
|
79
94
|
|
package/src/array/tuple.test.mts
CHANGED
|
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { number, numberLiteral, stringLiteral } from '../primitives/index.mjs';
|
|
3
3
|
import { record } from '../record/index.mjs';
|
|
4
4
|
import { type TypeOf } from '../type.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type ValidationError,
|
|
7
|
+
validationErrorsToMessages,
|
|
8
|
+
} from '../validation-error.mjs';
|
|
6
9
|
import { tuple } from './tuple.mjs';
|
|
7
10
|
|
|
8
11
|
describe('tuple', () => {
|
|
@@ -72,6 +75,20 @@ describe('tuple', () => {
|
|
|
72
75
|
});
|
|
73
76
|
|
|
74
77
|
describe('validate', () => {
|
|
78
|
+
test('truthy case', () => {
|
|
79
|
+
const x: unknown = [{ x: 1, y: 2 }, 3, '2'];
|
|
80
|
+
|
|
81
|
+
const result = targetType.validate(x);
|
|
82
|
+
expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
|
|
83
|
+
'=',
|
|
84
|
+
);
|
|
85
|
+
expect(Result.isOk(result)).toBe(true);
|
|
86
|
+
|
|
87
|
+
if (Result.isOk(result)) {
|
|
88
|
+
expect(result.value).toStrictEqual([{ x: 1, y: 2 }, 3, '2']);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
75
92
|
test('falsy case - not array', () => {
|
|
76
93
|
const x: unknown = 'not an array';
|
|
77
94
|
|
|
@@ -3,7 +3,10 @@ import { uintRange } from '../enum/index.mjs';
|
|
|
3
3
|
import { number } from '../primitives/index.mjs';
|
|
4
4
|
import { record } from '../record/index.mjs';
|
|
5
5
|
import { type TypeOf } from '../type.mjs';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
type ValidationError,
|
|
8
|
+
validationErrorsToMessages,
|
|
9
|
+
} from '../validation-error.mjs';
|
|
7
10
|
import { intersection } from './intersection.mjs';
|
|
8
11
|
|
|
9
12
|
describe('intersection', () => {
|
|
@@ -74,6 +77,19 @@ describe('intersection', () => {
|
|
|
74
77
|
});
|
|
75
78
|
|
|
76
79
|
describe('validate', () => {
|
|
80
|
+
test('truthy case', () => {
|
|
81
|
+
const result = targetType.validate({ x: 0, y: 1, z: 2, w: 3 });
|
|
82
|
+
expectType<
|
|
83
|
+
typeof result,
|
|
84
|
+
Result<TargetType, readonly ValidationError[]>
|
|
85
|
+
>('=');
|
|
86
|
+
expect(Result.isOk(result)).toBe(true);
|
|
87
|
+
|
|
88
|
+
if (Result.isOk(result)) {
|
|
89
|
+
expect(result.value).toStrictEqual({ x: 0, y: 1, z: 2, w: 3 });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
77
93
|
test('falsy case', () => {
|
|
78
94
|
const result = targetType.validate({ x: 0, y: 1 });
|
|
79
95
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -165,6 +181,19 @@ describe('intersection', () => {
|
|
|
165
181
|
});
|
|
166
182
|
|
|
167
183
|
describe('validate', () => {
|
|
184
|
+
test('truthy case', () => {
|
|
185
|
+
const result = targetType.validate(0);
|
|
186
|
+
expectType<
|
|
187
|
+
typeof result,
|
|
188
|
+
Result<TargetType, readonly ValidationError[]>
|
|
189
|
+
>('=');
|
|
190
|
+
expect(Result.isOk(result)).toBe(true);
|
|
191
|
+
|
|
192
|
+
if (Result.isOk(result)) {
|
|
193
|
+
expect(result.value).toBe(0);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
168
197
|
test('falsy case', () => {
|
|
169
198
|
const result = targetType.validate('aaa');
|
|
170
199
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -278,6 +307,19 @@ describe('intersection', () => {
|
|
|
278
307
|
});
|
|
279
308
|
|
|
280
309
|
describe('validate', () => {
|
|
310
|
+
test('truthy case', () => {
|
|
311
|
+
const result = targetType.validate(3);
|
|
312
|
+
expectType<
|
|
313
|
+
typeof result,
|
|
314
|
+
Result<TargetType, readonly ValidationError[]>
|
|
315
|
+
>('=');
|
|
316
|
+
expect(Result.isOk(result)).toBe(true);
|
|
317
|
+
|
|
318
|
+
if (Result.isOk(result)) {
|
|
319
|
+
expect(result.value).toBe(3);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
281
323
|
test('falsy case', () => {
|
|
282
324
|
const result = targetType.validate(7);
|
|
283
325
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { number } from '../primitives/index.mjs';
|
|
3
3
|
import { record } from '../record/index.mjs';
|
|
4
4
|
import { type TypeOf } from '../type.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type ValidationError,
|
|
7
|
+
validationErrorsToMessages,
|
|
8
|
+
} from '../validation-error.mjs';
|
|
6
9
|
import { mergeRecords } from './merge-records.mjs';
|
|
7
10
|
|
|
8
11
|
describe('mergeRecords', () => {
|
|
@@ -67,6 +70,18 @@ describe('mergeRecords', () => {
|
|
|
67
70
|
});
|
|
68
71
|
|
|
69
72
|
describe('validate', () => {
|
|
73
|
+
test('truthy case', () => {
|
|
74
|
+
const result = targetType.validate({ x: 0, y: 1, z: 2, w: 3 });
|
|
75
|
+
expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
|
|
76
|
+
'=',
|
|
77
|
+
);
|
|
78
|
+
expect(Result.isOk(result)).toBe(true);
|
|
79
|
+
|
|
80
|
+
if (Result.isOk(result)) {
|
|
81
|
+
expect(result.value).toStrictEqual({ x: 0, y: 1, z: 2, w: 3 });
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
70
85
|
test('falsy case', () => {
|
|
71
86
|
const result = targetType.validate({ x: 0, y: 1 });
|
|
72
87
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -2,7 +2,10 @@ import { expectType, Result } from 'ts-data-forge';
|
|
|
2
2
|
import { number, numberLiteral, stringLiteral } from '../primitives/index.mjs';
|
|
3
3
|
import { record } from '../record/index.mjs';
|
|
4
4
|
import { type TypeOf } from '../type.mjs';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
type ValidationError,
|
|
7
|
+
validationErrorsToMessages,
|
|
8
|
+
} from '../validation-error.mjs';
|
|
6
9
|
import { union } from './union.mjs';
|
|
7
10
|
|
|
8
11
|
describe('union', () => {
|
|
@@ -70,6 +73,18 @@ describe('union', () => {
|
|
|
70
73
|
});
|
|
71
74
|
|
|
72
75
|
describe('validate', () => {
|
|
76
|
+
test('truthy case', () => {
|
|
77
|
+
const result = targetType.validate(3);
|
|
78
|
+
expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
|
|
79
|
+
'=',
|
|
80
|
+
);
|
|
81
|
+
expect(Result.isOk(result)).toBe(true);
|
|
82
|
+
|
|
83
|
+
if (Result.isOk(result)) {
|
|
84
|
+
expect(result.value).toBe(3);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
73
88
|
test('falsy case', () => {
|
|
74
89
|
const result = targetType.validate(5);
|
|
75
90
|
expect(Result.isErr(result)).toBe(true);
|
package/src/enum/enum.test.mts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { expectType, Result } from 'ts-data-forge';
|
|
2
2
|
import { type TypeOf } from '../type.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type ValidationError,
|
|
5
|
+
validationErrorsToMessages,
|
|
6
|
+
} from '../validation-error.mjs';
|
|
4
7
|
import { enumType } from './enum.mjs';
|
|
5
8
|
|
|
6
9
|
describe('enumType', () => {
|
|
@@ -41,6 +44,18 @@ describe('enumType', () => {
|
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
describe('validate', () => {
|
|
47
|
+
test('truthy case', () => {
|
|
48
|
+
const result = targetType.validate(3);
|
|
49
|
+
expectType<typeof result, Result<TargetType, readonly ValidationError[]>>(
|
|
50
|
+
'=',
|
|
51
|
+
);
|
|
52
|
+
expect(Result.isOk(result)).toBe(true);
|
|
53
|
+
|
|
54
|
+
if (Result.isOk(result)) {
|
|
55
|
+
expect(result.value).toBe(3);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
44
59
|
test('falsy case', () => {
|
|
45
60
|
const result = targetType.validate(5);
|
|
46
61
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { expectType, Result } from 'ts-data-forge';
|
|
2
2
|
import { type TypeOf } from '../type.mjs';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type ValidationError,
|
|
5
|
+
validationErrorsToMessages,
|
|
6
|
+
} from '../validation-error.mjs';
|
|
4
7
|
import { uintRange } from './uint-range.mjs';
|
|
5
8
|
|
|
6
9
|
describe('uintRange', () => {
|
|
@@ -44,6 +47,16 @@ describe('uintRange', () => {
|
|
|
44
47
|
});
|
|
45
48
|
|
|
46
49
|
describe('validate', () => {
|
|
50
|
+
test('truthy case', () => {
|
|
51
|
+
const result = month.validate(5);
|
|
52
|
+
expectType<typeof result, Result<Month, readonly ValidationError[]>>('=');
|
|
53
|
+
expect(Result.isOk(result)).toBe(true);
|
|
54
|
+
|
|
55
|
+
if (Result.isOk(result)) {
|
|
56
|
+
expect(result.value).toBe(5);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
47
60
|
test('falsy case', () => {
|
|
48
61
|
const result = month.validate(13);
|
|
49
62
|
expect(Result.isErr(result)).toBe(true);
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { expectType, Result } from 'ts-data-forge';
|
|
2
|
+
import { type TypeOf } from '../type.mjs';
|
|
3
|
+
import { nullType } from './null.mjs';
|
|
4
|
+
|
|
5
|
+
describe('nullType', () => {
|
|
6
|
+
type Null = TypeOf<typeof nullType>;
|
|
7
|
+
|
|
8
|
+
expectType<Null, null>('=');
|
|
9
|
+
expectType<typeof nullType.defaultValue, Null>('=');
|
|
10
|
+
|
|
11
|
+
describe('default value', () => {
|
|
12
|
+
test('is null', () => {
|
|
13
|
+
expect(nullType.defaultValue).toBeNull();
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe('is', () => {
|
|
18
|
+
test('null returns true', () => {
|
|
19
|
+
const value: unknown = null;
|
|
20
|
+
|
|
21
|
+
if (nullType.is(value)) {
|
|
22
|
+
expectType<typeof value, Null>('=');
|
|
23
|
+
} else {
|
|
24
|
+
expectType<typeof value, {} | undefined>('=');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
expect(nullType.is(value)).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('undefined returns false', () => {
|
|
31
|
+
const value: unknown = undefined;
|
|
32
|
+
|
|
33
|
+
if (nullType.is(value)) {
|
|
34
|
+
expectType<typeof value, Null>('=');
|
|
35
|
+
} else {
|
|
36
|
+
expectType<typeof value, {} | undefined>('=');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(nullType.is(value)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('other values return false', () => {
|
|
43
|
+
const values = [0, '', false, Number.NaN, {}, [], 'null', 42, true];
|
|
44
|
+
|
|
45
|
+
for (const value of values) {
|
|
46
|
+
const v: unknown = value;
|
|
47
|
+
|
|
48
|
+
if (nullType.is(v)) {
|
|
49
|
+
expectType<typeof v, Null>('=');
|
|
50
|
+
} else {
|
|
51
|
+
expectType<typeof v, {} | undefined>('=');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
expect(nullType.is(v)).toBe(false);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('assertIs', () => {
|
|
60
|
+
test('null does not throw', () => {
|
|
61
|
+
const value: unknown = null;
|
|
62
|
+
expect(() => {
|
|
63
|
+
nullType.assertIs(value);
|
|
64
|
+
}).not.toThrow();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('non-null throws', () => {
|
|
68
|
+
const value: unknown = undefined;
|
|
69
|
+
expect(() => {
|
|
70
|
+
nullType.assertIs(value);
|
|
71
|
+
}).toThrow('Expected null, got undefined');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('cast', () => {
|
|
76
|
+
test('null returns null', () => {
|
|
77
|
+
const value: unknown = null;
|
|
78
|
+
const result = nullType.cast(value);
|
|
79
|
+
expect(result).toBeNull();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('non-null throws error', () => {
|
|
83
|
+
const value: unknown = undefined;
|
|
84
|
+
expect(() => nullType.cast(value)).toThrow(
|
|
85
|
+
'Expected null, got undefined',
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('any non-null value throws error', () => {
|
|
90
|
+
const values = [0, '', false, undefined, 42, 'hello'];
|
|
91
|
+
|
|
92
|
+
for (const value of values) {
|
|
93
|
+
expect(() => nullType.cast(value)).toThrow(
|
|
94
|
+
/Expected null, got (number|string|boolean|undefined)/u,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe('fill', () => {
|
|
101
|
+
test('null returns null', () => {
|
|
102
|
+
const value: unknown = null;
|
|
103
|
+
const result = nullType.fill(value);
|
|
104
|
+
expect(result).toBeNull();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('undefined returns default (null)', () => {
|
|
108
|
+
const value: unknown = undefined;
|
|
109
|
+
const result = nullType.fill(value);
|
|
110
|
+
expect(result).toBeNull();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test('other values return default (null)', () => {
|
|
114
|
+
const values = [0, '', false, 42, 'hello', {}];
|
|
115
|
+
|
|
116
|
+
for (const value of values) {
|
|
117
|
+
const result = nullType.fill(value);
|
|
118
|
+
expect(result).toBeNull();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('validate', () => {
|
|
124
|
+
test('null is valid', () => {
|
|
125
|
+
const value: unknown = null;
|
|
126
|
+
const result = nullType.validate(value);
|
|
127
|
+
|
|
128
|
+
expect(Result.isOk(result)).toBe(true);
|
|
129
|
+
if (Result.isOk(result)) {
|
|
130
|
+
expect(result.value).toBeNull();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('undefined is invalid', () => {
|
|
135
|
+
const value: unknown = undefined;
|
|
136
|
+
const result = nullType.validate(value);
|
|
137
|
+
|
|
138
|
+
expect(Result.isErr(result)).toBe(true);
|
|
139
|
+
if (Result.isErr(result)) {
|
|
140
|
+
expect(result.value).toStrictEqual([
|
|
141
|
+
{
|
|
142
|
+
path: [],
|
|
143
|
+
actualValue: undefined,
|
|
144
|
+
expectedType: 'null',
|
|
145
|
+
typeName: 'null',
|
|
146
|
+
message: undefined,
|
|
147
|
+
},
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test('other values are invalid', () => {
|
|
153
|
+
const value: unknown = 0;
|
|
154
|
+
const result = nullType.validate(value);
|
|
155
|
+
|
|
156
|
+
expect(Result.isErr(result)).toBe(true);
|
|
157
|
+
if (Result.isErr(result)) {
|
|
158
|
+
expect(result.value).toStrictEqual([
|
|
159
|
+
{
|
|
160
|
+
path: [],
|
|
161
|
+
actualValue: 0,
|
|
162
|
+
expectedType: 'null',
|
|
163
|
+
typeName: 'null',
|
|
164
|
+
message: undefined,
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|