From 92851ea30a361288665ee65fa8e11f43ea3d1f77 Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Sat, 10 Aug 2024 18:16:09 +0900 Subject: [PATCH] Revision 0.33.4 (#953) * Add Assert and Parse Functions * Documentation * Version * Benchmarks --- package-lock.json | 4 +- package.json | 2 +- readme.md | 244 ++++++++++++------------- src/compiler/compiler.ts | 9 +- src/value/assert/assert.ts | 73 ++++++++ src/value/assert/index.ts | 29 +++ src/value/cast/cast.ts | 4 +- src/value/clean/clean.ts | 15 +- src/value/clone/clone.ts | 32 ++-- src/value/convert/convert.ts | 41 ++--- src/value/create/create.ts | 6 +- src/value/default/default.ts | 30 ++- src/value/equal/equal.ts | 6 +- src/value/hash/hash.ts | 4 +- src/value/index.ts | 2 + src/value/mutate/mutate.ts | 10 +- src/value/parse/index.ts | 29 +++ src/value/parse/parse.ts | 68 +++++++ src/value/transform/decode.ts | 14 +- src/value/transform/encode.ts | 16 +- src/value/transform/has.ts | 6 +- src/value/value/value.ts | 42 +++-- test/runtime/value/assert/assert.ts | 35 ++++ test/runtime/value/assert/index.ts | 1 + test/runtime/value/default/union.ts | 12 +- test/runtime/value/index.ts | 2 + test/runtime/value/parse/index.ts | 1 + test/runtime/value/parse/parse.ts | 90 +++++++++ test/runtime/value/transform/record.ts | 1 - 29 files changed, 592 insertions(+), 236 deletions(-) create mode 100644 src/value/assert/assert.ts create mode 100644 src/value/assert/index.ts create mode 100644 src/value/parse/index.ts create mode 100644 src/value/parse/parse.ts create mode 100644 test/runtime/value/assert/assert.ts create mode 100644 test/runtime/value/assert/index.ts create mode 100644 test/runtime/value/parse/index.ts create mode 100644 test/runtime/value/parse/parse.ts diff --git a/package-lock.json b/package-lock.json index e2eb7ab..01590d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.33.3", + "version": "0.33.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.33.3", + "version": "0.33.4", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.13.2", diff --git a/package.json b/package.json index 07be5d3..04c5b2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.33.3", + "version": "0.33.4", "description": "Json Schema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/readme.md b/readme.md index 6d1aa23..1fca892 100644 --- a/readme.md +++ b/readme.md @@ -74,13 +74,12 @@ License MIT - [Indexed](#types-indexed) - [Mapped](#types-mapped) - [Conditional](#types-conditional) - - [Intrinsic](#types-intrinsic) - [Transform](#types-transform) - - [Rest](#types-rest) - [Guard](#types-guard) - [Unsafe](#types-unsafe) - [Strict](#types-strict) - [Values](#values) + - [Assert](#values-assert) - [Create](#values-create) - [Clone](#values-clone) - [Check](#values-check) @@ -90,6 +89,7 @@ License MIT - [Cast](#values-cast) - [Decode](#values-decode) - [Encode](#values-decode) + - [Parse](#values-parse) - [Equal](#values-equal) - [Hash](#values-hash) - [Diff](#values-diff) @@ -176,19 +176,17 @@ type T = Static // type T = { //-------------------------------------------------------------------------------------------- // -// ... then use the type both as Json Schema and as a TypeScript type. +// ... or use the type to parse JavaScript values. // //-------------------------------------------------------------------------------------------- import { Value } from '@sinclair/typebox/value' -function receive(value: T) { // ... as a Static Type - - if(Value.Check(T, value)) { // ... as a Json Schema - - // ok... - } -} +const R = Value.Parse(T, value) // const R: { + // id: string, + // name: string, + // timestamp: number + // } ``` @@ -1000,38 +998,6 @@ const C = Type.Exclude( // type C = Exclude<1 | 2 | ) // ]> ``` - - -### Intrinsic Types - -TypeBox supports the TypeScript intrinsic string manipulation types Uppercase, Lowercase, Capitalize and Uncapitalize. These types can be used to remap Literal, Template Literal and Union of Literal types. - -```typescript -// TypeScript -type A = Capitalize<'hello'> // type A = 'Hello' - -type B = Capitalize<'hello' | 'world'> // type C = 'Hello' | 'World' - -type C = Capitalize<`hello${1|2|3}`> // type B = 'Hello1' | 'Hello2' | 'Hello3' - -// TypeBox -const A = Type.Capitalize(Type.Literal('hello')) // const A: TLiteral<'Hello'> - -const B = Type.Capitalize(Type.Union([ // const B: TUnion<[ - Type.Literal('hello'), // TLiteral<'Hello'>, - Type.Literal('world') // TLiteral<'World'> -])) // ]> - -const C = Type.Capitalize( // const C: TTemplateLiteral<[ - Type.TemplateLiteral('hello${1|2|3}') // TLiteral<'Hello'>, -) // TUnion<[ - // TLiteral<'1'>, - // TLiteral<'2'>, - // TLiteral<'3'> - // ]> - // ]> -``` - ### Transform Types @@ -1061,26 +1027,6 @@ type E = StaticEncode // type E = Array type T = Static // type T = Array ``` - - -### Rest Types - -TypeBox provides the Rest type to uniformly extract variadic tuples from Intersect, Union and Tuple types. This type can be useful to remap variadic types into different forms. The following uses Rest to remap a Tuple into a Union. - -```typescript -const T = Type.Tuple([ // const T: TTuple<[ - Type.String(), // TString, - Type.Number() // TNumber -]) // ]> - -const R = Type.Rest(T) // const R: [TString, TNumber] - -const U = Type.Union(R) // const T: TUnion<[ - // TString, - // TNumber - // ]> -``` - ### Unsafe Types @@ -1171,6 +1117,18 @@ TypeBox provides an optional Value submodule that can be used to perform structu import { Value } from '@sinclair/typebox/value' ``` + + +### Assert + +Use the Assert function to assert a value is valid. + +```typescript +let value: unknown = 1 + +Value.Assert(Type.Number(), value) // throws AssertError if invalid +``` + ### Create @@ -1296,6 +1254,32 @@ const A = Value.Encode(Type.String(), 'hello') // const A = 'hello' const B = Value.Encode(Type.String(), 42) // throw ``` + + +### Parse + +Use the Parse function to parse a value or throw if invalid. This function internally uses Default, Clean, Convert and Decode to make a best effort attempt to parse the value into the expected type. This function should not be used in performance critical code paths. + +```typescript +const T = Type.Object({ x: Type.Number({ default: 0 }), y: Type.Number({ default: 0 }) }) + +// Default + +const A = Value.Parse(T, { }) // const A = { x: 0, y: 0 } + +// Convert + +const B = Value.Parse(T, { x: '1', y: '2' }) // const B = { x: 1, y: 2 } + +// Clean + +const C = Value.Parse(T, { x: 1, y: 2, z: 3 }) // const C = { x: 1, y: 2 } + +// Assert + +const D = Value.Parse(T, undefined) // throws AssertError +``` + ### Equal @@ -1716,37 +1700,37 @@ This benchmark measures compilation performance for varying types. ```typescript ┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┐ -│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │ +│ (index) │ Iterations │ Ajv │ TypeCompiler │ Performance │ ├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┤ -│ Literal_String │ 1000 │ ' 242 ms' │ ' 10 ms' │ ' 24.20 x' │ -│ Literal_Number │ 1000 │ ' 200 ms' │ ' 8 ms' │ ' 25.00 x' │ -│ Literal_Boolean │ 1000 │ ' 168 ms' │ ' 6 ms' │ ' 28.00 x' │ -│ Primitive_Number │ 1000 │ ' 165 ms' │ ' 8 ms' │ ' 20.63 x' │ -│ Primitive_String │ 1000 │ ' 154 ms' │ ' 6 ms' │ ' 25.67 x' │ -│ Primitive_String_Pattern │ 1000 │ ' 208 ms' │ ' 14 ms' │ ' 14.86 x' │ -│ Primitive_Boolean │ 1000 │ ' 142 ms' │ ' 6 ms' │ ' 23.67 x' │ -│ Primitive_Null │ 1000 │ ' 143 ms' │ ' 6 ms' │ ' 23.83 x' │ -│ Object_Unconstrained │ 1000 │ ' 1217 ms' │ ' 31 ms' │ ' 39.26 x' │ -│ Object_Constrained │ 1000 │ ' 1275 ms' │ ' 26 ms' │ ' 49.04 x' │ -│ Object_Vector3 │ 1000 │ ' 405 ms' │ ' 12 ms' │ ' 33.75 x' │ -│ Object_Box3D │ 1000 │ ' 1833 ms' │ ' 27 ms' │ ' 67.89 x' │ -│ Tuple_Primitive │ 1000 │ ' 475 ms' │ ' 13 ms' │ ' 36.54 x' │ -│ Tuple_Object │ 1000 │ ' 1267 ms' │ ' 30 ms' │ ' 42.23 x' │ -│ Composite_Intersect │ 1000 │ ' 604 ms' │ ' 18 ms' │ ' 33.56 x' │ -│ Composite_Union │ 1000 │ ' 545 ms' │ ' 20 ms' │ ' 27.25 x' │ -│ Math_Vector4 │ 1000 │ ' 829 ms' │ ' 12 ms' │ ' 69.08 x' │ -│ Math_Matrix4 │ 1000 │ ' 405 ms' │ ' 10 ms' │ ' 40.50 x' │ -│ Array_Primitive_Number │ 1000 │ ' 372 ms' │ ' 12 ms' │ ' 31.00 x' │ -│ Array_Primitive_String │ 1000 │ ' 327 ms' │ ' 5 ms' │ ' 65.40 x' │ -│ Array_Primitive_Boolean │ 1000 │ ' 300 ms' │ ' 4 ms' │ ' 75.00 x' │ -│ Array_Object_Unconstrained │ 1000 │ ' 1755 ms' │ ' 21 ms' │ ' 83.57 x' │ -│ Array_Object_Constrained │ 1000 │ ' 1516 ms' │ ' 20 ms' │ ' 75.80 x' │ -│ Array_Tuple_Primitive │ 1000 │ ' 825 ms' │ ' 14 ms' │ ' 58.93 x' │ -│ Array_Tuple_Object │ 1000 │ ' 1616 ms' │ ' 16 ms' │ ' 101.00 x' │ -│ Array_Composite_Intersect │ 1000 │ ' 776 ms' │ ' 16 ms' │ ' 48.50 x' │ -│ Array_Composite_Union │ 1000 │ ' 820 ms' │ ' 14 ms' │ ' 58.57 x' │ -│ Array_Math_Vector4 │ 1000 │ ' 1166 ms' │ ' 15 ms' │ ' 77.73 x' │ -│ Array_Math_Matrix4 │ 1000 │ ' 695 ms' │ ' 8 ms' │ ' 86.88 x' │ +│ Literal_String │ 1000 │ ' 211 ms' │ ' 8 ms' │ ' 26.38 x' │ +│ Literal_Number │ 1000 │ ' 185 ms' │ ' 5 ms' │ ' 37.00 x' │ +│ Literal_Boolean │ 1000 │ ' 195 ms' │ ' 4 ms' │ ' 48.75 x' │ +│ Primitive_Number │ 1000 │ ' 149 ms' │ ' 7 ms' │ ' 21.29 x' │ +│ Primitive_String │ 1000 │ ' 135 ms' │ ' 5 ms' │ ' 27.00 x' │ +│ Primitive_String_Pattern │ 1000 │ ' 193 ms' │ ' 10 ms' │ ' 19.30 x' │ +│ Primitive_Boolean │ 1000 │ ' 152 ms' │ ' 4 ms' │ ' 38.00 x' │ +│ Primitive_Null │ 1000 │ ' 147 ms' │ ' 4 ms' │ ' 36.75 x' │ +│ Object_Unconstrained │ 1000 │ ' 1065 ms' │ ' 26 ms' │ ' 40.96 x' │ +│ Object_Constrained │ 1000 │ ' 1183 ms' │ ' 26 ms' │ ' 45.50 x' │ +│ Object_Vector3 │ 1000 │ ' 407 ms' │ ' 9 ms' │ ' 45.22 x' │ +│ Object_Box3D │ 1000 │ ' 1777 ms' │ ' 24 ms' │ ' 74.04 x' │ +│ Tuple_Primitive │ 1000 │ ' 485 ms' │ ' 11 ms' │ ' 44.09 x' │ +│ Tuple_Object │ 1000 │ ' 1344 ms' │ ' 17 ms' │ ' 79.06 x' │ +│ Composite_Intersect │ 1000 │ ' 606 ms' │ ' 14 ms' │ ' 43.29 x' │ +│ Composite_Union │ 1000 │ ' 522 ms' │ ' 17 ms' │ ' 30.71 x' │ +│ Math_Vector4 │ 1000 │ ' 851 ms' │ ' 9 ms' │ ' 94.56 x' │ +│ Math_Matrix4 │ 1000 │ ' 406 ms' │ ' 10 ms' │ ' 40.60 x' │ +│ Array_Primitive_Number │ 1000 │ ' 367 ms' │ ' 6 ms' │ ' 61.17 x' │ +│ Array_Primitive_String │ 1000 │ ' 339 ms' │ ' 7 ms' │ ' 48.43 x' │ +│ Array_Primitive_Boolean │ 1000 │ ' 325 ms' │ ' 5 ms' │ ' 65.00 x' │ +│ Array_Object_Unconstrained │ 1000 │ ' 1863 ms' │ ' 21 ms' │ ' 88.71 x' │ +│ Array_Object_Constrained │ 1000 │ ' 1535 ms' │ ' 18 ms' │ ' 85.28 x' │ +│ Array_Tuple_Primitive │ 1000 │ ' 829 ms' │ ' 14 ms' │ ' 59.21 x' │ +│ Array_Tuple_Object │ 1000 │ ' 1674 ms' │ ' 14 ms' │ ' 119.57 x' │ +│ Array_Composite_Intersect │ 1000 │ ' 789 ms' │ ' 13 ms' │ ' 60.69 x' │ +│ Array_Composite_Union │ 1000 │ ' 822 ms' │ ' 15 ms' │ ' 54.80 x' │ +│ Array_Math_Vector4 │ 1000 │ ' 1129 ms' │ ' 14 ms' │ ' 80.64 x' │ +│ Array_Math_Matrix4 │ 1000 │ ' 673 ms' │ ' 9 ms' │ ' 74.78 x' │ └────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┘ ``` @@ -1758,39 +1742,39 @@ This benchmark measures validation performance for varying types. ```typescript ┌────────────────────────────┬────────────┬──────────────┬──────────────┬──────────────┬──────────────┐ -│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │ +│ (index) │ Iterations │ ValueCheck │ Ajv │ TypeCompiler │ Performance │ ├────────────────────────────┼────────────┼──────────────┼──────────────┼──────────────┼──────────────┤ -│ Literal_String │ 1000000 │ ' 18 ms' │ ' 5 ms' │ ' 4 ms' │ ' 1.25 x' │ -│ Literal_Number │ 1000000 │ ' 16 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │ -│ Literal_Boolean │ 1000000 │ ' 15 ms' │ ' 19 ms' │ ' 10 ms' │ ' 1.90 x' │ -│ Primitive_Number │ 1000000 │ ' 21 ms' │ ' 19 ms' │ ' 10 ms' │ ' 1.90 x' │ -│ Primitive_String │ 1000000 │ ' 22 ms' │ ' 18 ms' │ ' 9 ms' │ ' 2.00 x' │ -│ Primitive_String_Pattern │ 1000000 │ ' 155 ms' │ ' 41 ms' │ ' 34 ms' │ ' 1.21 x' │ -│ Primitive_Boolean │ 1000000 │ ' 18 ms' │ ' 17 ms' │ ' 9 ms' │ ' 1.89 x' │ -│ Primitive_Null │ 1000000 │ ' 19 ms' │ ' 17 ms' │ ' 9 ms' │ ' 1.89 x' │ -│ Object_Unconstrained │ 1000000 │ ' 1003 ms' │ ' 32 ms' │ ' 24 ms' │ ' 1.33 x' │ -│ Object_Constrained │ 1000000 │ ' 1265 ms' │ ' 49 ms' │ ' 38 ms' │ ' 1.29 x' │ -│ Object_Vector3 │ 1000000 │ ' 418 ms' │ ' 22 ms' │ ' 13 ms' │ ' 1.69 x' │ -│ Object_Box3D │ 1000000 │ ' 2035 ms' │ ' 56 ms' │ ' 49 ms' │ ' 1.14 x' │ -│ Object_Recursive │ 1000000 │ ' 5243 ms' │ ' 326 ms' │ ' 157 ms' │ ' 2.08 x' │ -│ Tuple_Primitive │ 1000000 │ ' 153 ms' │ ' 20 ms' │ ' 12 ms' │ ' 1.67 x' │ -│ Tuple_Object │ 1000000 │ ' 781 ms' │ ' 28 ms' │ ' 18 ms' │ ' 1.56 x' │ -│ Composite_Intersect │ 1000000 │ ' 742 ms' │ ' 25 ms' │ ' 14 ms' │ ' 1.79 x' │ -│ Composite_Union │ 1000000 │ ' 558 ms' │ ' 24 ms' │ ' 13 ms' │ ' 1.85 x' │ -│ Math_Vector4 │ 1000000 │ ' 246 ms' │ ' 22 ms' │ ' 11 ms' │ ' 2.00 x' │ -│ Math_Matrix4 │ 1000000 │ ' 1052 ms' │ ' 43 ms' │ ' 28 ms' │ ' 1.54 x' │ -│ Array_Primitive_Number │ 1000000 │ ' 272 ms' │ ' 22 ms' │ ' 12 ms' │ ' 1.83 x' │ -│ Array_Primitive_String │ 1000000 │ ' 235 ms' │ ' 24 ms' │ ' 14 ms' │ ' 1.71 x' │ -│ Array_Primitive_Boolean │ 1000000 │ ' 134 ms' │ ' 23 ms' │ ' 14 ms' │ ' 1.64 x' │ -│ Array_Object_Unconstrained │ 1000000 │ ' 6280 ms' │ ' 65 ms' │ ' 59 ms' │ ' 1.10 x' │ -│ Array_Object_Constrained │ 1000000 │ ' 6076 ms' │ ' 130 ms' │ ' 119 ms' │ ' 1.09 x' │ -│ Array_Object_Recursive │ 1000000 │ ' 22738 ms' │ ' 1730 ms' │ ' 635 ms' │ ' 2.72 x' │ -│ Array_Tuple_Primitive │ 1000000 │ ' 689 ms' │ ' 35 ms' │ ' 30 ms' │ ' 1.17 x' │ -│ Array_Tuple_Object │ 1000000 │ ' 3266 ms' │ ' 63 ms' │ ' 52 ms' │ ' 1.21 x' │ -│ Array_Composite_Intersect │ 1000000 │ ' 3310 ms' │ ' 44 ms' │ ' 36 ms' │ ' 1.22 x' │ -│ Array_Composite_Union │ 1000000 │ ' 2432 ms' │ ' 69 ms' │ ' 33 ms' │ ' 2.09 x' │ -│ Array_Math_Vector4 │ 1000000 │ ' 1158 ms' │ ' 37 ms' │ ' 24 ms' │ ' 1.54 x' │ -│ Array_Math_Matrix4 │ 1000000 │ ' 5435 ms' │ ' 132 ms' │ ' 92 ms' │ ' 1.43 x' │ +│ Literal_String │ 1000000 │ ' 17 ms' │ ' 5 ms' │ ' 5 ms' │ ' 1.00 x' │ +│ Literal_Number │ 1000000 │ ' 14 ms' │ ' 18 ms' │ ' 9 ms' │ ' 2.00 x' │ +│ Literal_Boolean │ 1000000 │ ' 14 ms' │ ' 20 ms' │ ' 9 ms' │ ' 2.22 x' │ +│ Primitive_Number │ 1000000 │ ' 17 ms' │ ' 19 ms' │ ' 9 ms' │ ' 2.11 x' │ +│ Primitive_String │ 1000000 │ ' 17 ms' │ ' 18 ms' │ ' 10 ms' │ ' 1.80 x' │ +│ Primitive_String_Pattern │ 1000000 │ ' 172 ms' │ ' 46 ms' │ ' 41 ms' │ ' 1.12 x' │ +│ Primitive_Boolean │ 1000000 │ ' 14 ms' │ ' 19 ms' │ ' 10 ms' │ ' 1.90 x' │ +│ Primitive_Null │ 1000000 │ ' 16 ms' │ ' 19 ms' │ ' 9 ms' │ ' 2.11 x' │ +│ Object_Unconstrained │ 1000000 │ ' 437 ms' │ ' 28 ms' │ ' 14 ms' │ ' 2.00 x' │ +│ Object_Constrained │ 1000000 │ ' 653 ms' │ ' 46 ms' │ ' 37 ms' │ ' 1.24 x' │ +│ Object_Vector3 │ 1000000 │ ' 201 ms' │ ' 22 ms' │ ' 12 ms' │ ' 1.83 x' │ +│ Object_Box3D │ 1000000 │ ' 961 ms' │ ' 37 ms' │ ' 19 ms' │ ' 1.95 x' │ +│ Object_Recursive │ 1000000 │ ' 3715 ms' │ ' 363 ms' │ ' 174 ms' │ ' 2.09 x' │ +│ Tuple_Primitive │ 1000000 │ ' 107 ms' │ ' 23 ms' │ ' 11 ms' │ ' 2.09 x' │ +│ Tuple_Object │ 1000000 │ ' 375 ms' │ ' 28 ms' │ ' 15 ms' │ ' 1.87 x' │ +│ Composite_Intersect │ 1000000 │ ' 377 ms' │ ' 22 ms' │ ' 12 ms' │ ' 1.83 x' │ +│ Composite_Union │ 1000000 │ ' 337 ms' │ ' 30 ms' │ ' 17 ms' │ ' 1.76 x' │ +│ Math_Vector4 │ 1000000 │ ' 137 ms' │ ' 23 ms' │ ' 11 ms' │ ' 2.09 x' │ +│ Math_Matrix4 │ 1000000 │ ' 576 ms' │ ' 37 ms' │ ' 28 ms' │ ' 1.32 x' │ +│ Array_Primitive_Number │ 1000000 │ ' 145 ms' │ ' 23 ms' │ ' 12 ms' │ ' 1.92 x' │ +│ Array_Primitive_String │ 1000000 │ ' 152 ms' │ ' 22 ms' │ ' 13 ms' │ ' 1.69 x' │ +│ Array_Primitive_Boolean │ 1000000 │ ' 131 ms' │ ' 20 ms' │ ' 13 ms' │ ' 1.54 x' │ +│ Array_Object_Unconstrained │ 1000000 │ ' 2821 ms' │ ' 62 ms' │ ' 45 ms' │ ' 1.38 x' │ +│ Array_Object_Constrained │ 1000000 │ ' 2958 ms' │ ' 119 ms' │ ' 134 ms' │ ' 0.89 x' │ +│ Array_Object_Recursive │ 1000000 │ ' 14695 ms' │ ' 1621 ms' │ ' 635 ms' │ ' 2.55 x' │ +│ Array_Tuple_Primitive │ 1000000 │ ' 478 ms' │ ' 35 ms' │ ' 28 ms' │ ' 1.25 x' │ +│ Array_Tuple_Object │ 1000000 │ ' 1623 ms' │ ' 63 ms' │ ' 48 ms' │ ' 1.31 x' │ +│ Array_Composite_Intersect │ 1000000 │ ' 1582 ms' │ ' 43 ms' │ ' 30 ms' │ ' 1.43 x' │ +│ Array_Composite_Union │ 1000000 │ ' 1331 ms' │ ' 76 ms' │ ' 40 ms' │ ' 1.90 x' │ +│ Array_Math_Vector4 │ 1000000 │ ' 564 ms' │ ' 38 ms' │ ' 24 ms' │ ' 1.58 x' │ +│ Array_Math_Matrix4 │ 1000000 │ ' 2382 ms' │ ' 111 ms' │ ' 83 ms' │ ' 1.34 x' │ └────────────────────────────┴────────────┴──────────────┴──────────────┴──────────────┴──────────────┘ ``` @@ -1802,13 +1786,13 @@ The following table lists esbuild compiled and minified sizes for each TypeBox m ```typescript ┌──────────────────────┬────────────┬────────────┬─────────────┐ -│ (index) │ Compiled │ Minified │ Compression │ +│ (index) │ Compiled │ Minified │ Compression │ ├──────────────────────┼────────────┼────────────┼─────────────┤ -│ typebox/compiler │ '126.9 kb' │ ' 55.7 kb' │ '2.28 x' │ -│ typebox/errors │ ' 46.1 kb' │ ' 20.8 kb' │ '2.22 x' │ -│ typebox/system │ ' 4.7 kb' │ ' 2.0 kb' │ '2.33 x' │ -│ typebox/value │ '152.2 kb' │ ' 64.5 kb' │ '2.36 x' │ -│ typebox │ ' 95.7 kb' │ ' 39.8 kb' │ '2.40 x' │ +│ typebox/compiler │ '119.6 kb' │ ' 52.6 kb' │ '2.27 x' │ +│ typebox/errors │ ' 48.6 kb' │ ' 21.9 kb' │ '2.22 x' │ +│ typebox/system │ ' 7.4 kb' │ ' 3.2 kb' │ '2.33 x' │ +│ typebox/value │ '157.8 kb' │ ' 66.6 kb' │ '2.37 x' │ +│ typebox │ ' 98.3 kb' │ ' 40.9 kb' │ '2.40 x' │ └──────────────────────┴────────────┴────────────┴─────────────┘ ``` diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 8e939f1..f412034 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -71,6 +71,7 @@ import type { TSymbol } from '../type/symbol/index' import type { TUndefined } from '../type/undefined/index' import type { TUint8Array } from '../type/uint8array/index' import type { TVoid } from '../type/void/index' + // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ @@ -104,15 +105,15 @@ export class TypeCheck { return this.checkFunc(value) } /** Decodes a value or throws if error */ - public Decode(value: unknown): StaticDecode { + public Decode>(value: unknown): R { if (!this.checkFunc(value)) throw new TransformDecodeCheckError(this.schema, value, this.Errors(value).First()!) - return this.hasTransform ? TransformDecode(this.schema, this.references, value) : value + return (this.hasTransform ? TransformDecode(this.schema, this.references, value) : value) as never } /** Encodes a value or throws if error */ - public Encode(value: unknown): StaticEncode { + public Encode>(value: unknown): R { const encoded = this.hasTransform ? TransformEncode(this.schema, this.references, value) : value if (!this.checkFunc(encoded)) throw new TransformEncodeCheckError(this.schema, value, this.Errors(value).First()!) - return encoded + return encoded as never } } // ------------------------------------------------------------------ diff --git a/src/value/assert/assert.ts b/src/value/assert/assert.ts new file mode 100644 index 0000000..07a50db --- /dev/null +++ b/src/value/assert/assert.ts @@ -0,0 +1,73 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/value + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { Errors, ValueErrorIterator, ValueError } from '../../errors/index' +import { TypeBoxError } from '../../type/error/error' +import { TSchema } from '../../type/schema/index' +import { Static } from '../../type/static/index' +import { Check } from '../check/check' + +// ------------------------------------------------------------------ +// AssertError +// ------------------------------------------------------------------ +export class AssertError extends TypeBoxError { + readonly #iterator: ValueErrorIterator + error: ValueError | undefined + constructor(iterator: ValueErrorIterator) { + const error = iterator.First() + super(error === undefined ? 'Invalid Value' : error.message) + this.#iterator = iterator + this.error = error + } + /** Returns an iterator for each error in this value. */ + public Errors(): ValueErrorIterator { + return new ValueErrorIterator(this.#Iterator()) + } + *#Iterator(): IterableIterator { + if (this.error) yield this.error + yield* this.#iterator + } +} +// ------------------------------------------------------------------ +// AssertValue +// ------------------------------------------------------------------ +function AssertValue(schema: TSchema, references: TSchema[], value: unknown): unknown { + if (Check(schema, references, value)) return + throw new AssertError(Errors(schema, references, value)) +} +// ------------------------------------------------------------------ +// Assert +// ------------------------------------------------------------------ +/** Asserts a value matches the given type or throws an `AssertError` if invalid */ +export function Assert(schema: T, references: TSchema[], value: unknown): asserts value is Static +/** Asserts a value matches the given type or throws an `AssertError` if invalid */ +export function Assert(schema: T, value: unknown): asserts value is Static +/** Asserts a value matches the given type or throws an `AssertError` if invalid */ +export function Assert(...args: any[]): unknown { + return args.length === 3 ? AssertValue(args[0], args[1], args[2]) : AssertValue(args[0], [], args[1]) +} diff --git a/src/value/assert/index.ts b/src/value/assert/index.ts new file mode 100644 index 0000000..c2b2c88 --- /dev/null +++ b/src/value/assert/index.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/value + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * from './assert' diff --git a/src/value/cast/cast.ts b/src/value/cast/cast.ts index c3313f7..78ac754 100644 --- a/src/value/cast/cast.ts +++ b/src/value/cast/cast.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IsStandardObject, IsArray, IsString, IsNumber, IsNull } from '../guard/index' +import { IsObject, IsArray, IsString, IsNumber, IsNull } from '../guard/index' import { TypeBoxError } from '../../type/error/index' import { Kind } from '../../type/symbols/index' import { Create } from '../create/index' @@ -135,7 +135,7 @@ function FromConstructor(schema: TConstructor, references: TSchema[], value: any } function FromIntersect(schema: TIntersect, references: TSchema[], value: any): any { const created = Create(schema, references) - const mapped = IsStandardObject(created) && IsStandardObject(value) ? { ...(created as any), ...value } : value + const mapped = IsObject(created) && IsObject(value) ? { ...(created as any), ...value } : value return Check(schema, references, mapped) ? mapped : Create(schema, references) } function FromNever(schema: TNever, references: TSchema[], value: any): any { diff --git a/src/value/clean/clean.ts b/src/value/clean/clean.ts index 589d317..d3be4a7 100644 --- a/src/value/clean/clean.ts +++ b/src/value/clean/clean.ts @@ -47,6 +47,7 @@ import type { TUnion } from '../../type/union/index' // ------------------------------------------------------------------ // prettier-ignore import { + HasPropertyKey, IsString, IsObject, IsArray, @@ -57,13 +58,13 @@ import { // ------------------------------------------------------------------ // prettier-ignore import { - IsSchema -} from '../../type/guard/type' + IsKind +} from '../../type/guard/kind' // ------------------------------------------------------------------ // IsCheckable // ------------------------------------------------------------------ function IsCheckable(schema: unknown): boolean { - return IsSchema(schema) && schema[Kind] !== 'Unsafe' + return IsKind(schema) && schema[Kind] !== 'Unsafe' } // ------------------------------------------------------------------ // Types @@ -76,7 +77,7 @@ function FromIntersect(schema: TIntersect, references: TSchema[], value: unknown const unevaluatedProperties = schema.unevaluatedProperties as TSchema const intersections = schema.allOf.map((schema) => Visit(schema, references, Clone(value))) const composite = intersections.reduce((acc: any, value: any) => (IsObject(value) ? { ...acc, ...value } : value), {}) - if (!IsObject(value) || !IsObject(composite) || !IsSchema(unevaluatedProperties)) return composite + if (!IsObject(value) || !IsObject(composite) || !IsKind(unevaluatedProperties)) return composite const knownkeys = KeyOfPropertyKeys(schema) as string[] for (const key of Object.getOwnPropertyNames(value)) { if (knownkeys.includes(key)) continue @@ -90,11 +91,11 @@ function FromObject(schema: TObject, references: TSchema[], value: unknown): any if (!IsObject(value) || IsArray(value)) return value // Check IsArray for AllowArrayObject configuration const additionalProperties = schema.additionalProperties as TSchema for (const key of Object.getOwnPropertyNames(value)) { - if (key in schema.properties) { + if (HasPropertyKey(schema.properties, key)) { value[key] = Visit(schema.properties[key], references, value[key]) continue } - if (IsSchema(additionalProperties) && Check(additionalProperties, references, value[key])) { + if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) { value[key] = Visit(additionalProperties, references, value[key]) continue } @@ -113,7 +114,7 @@ function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any value[key] = Visit(propertySchema, references, value[key]) continue } - if (IsSchema(additionalProperties) && Check(additionalProperties, references, value[key])) { + if (IsKind(additionalProperties) && Check(additionalProperties, references, value[key])) { value[key] = Visit(additionalProperties, references, value[key]) continue } diff --git a/src/value/clone/clone.ts b/src/value/clone/clone.ts index 3bec35c..370fdac 100644 --- a/src/value/clone/clone.ts +++ b/src/value/clone/clone.ts @@ -26,16 +26,16 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index' +import type { ObjectType as FromObject, ArrayType as FromArray, TypedArrayType, ValueType } from '../guard/index' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsArray, IsDate, IsStandardObject, IsTypedArray, IsValueType } from '../guard/index' +import { IsArray, IsDate, IsMap, IsSet, IsObject, IsTypedArray, IsValueType } from '../guard/index' // ------------------------------------------------------------------ // Clonable // ------------------------------------------------------------------ -function ObjectType(value: ObjectType): any { +function FromObject(value: FromObject): any { const Acc = {} as Record for (const key of Object.getOwnPropertyNames(value)) { Acc[key] = Clone(value[key]) @@ -45,16 +45,22 @@ function ObjectType(value: ObjectType): any { } return Acc } -function ArrayType(value: ArrayType): any { +function FromArray(value: FromArray): any { return value.map((element: any) => Clone(element)) } -function TypedArrayType(value: TypedArrayType): any { +function FromTypedArray(value: TypedArrayType): any { return value.slice() } -function DateType(value: Date): any { +function FromMap(value: Map): any { + return new Map(Clone([...value.entries()])) +} +function FromSet(value: Set): any { + return new Set(Clone([...value.entries()])) +} +function FromDate(value: Date): any { return new Date(value.toISOString()) } -function ValueType(value: ValueType): any { +function FromValue(value: ValueType): any { return value } // ------------------------------------------------------------------ @@ -62,10 +68,12 @@ function ValueType(value: ValueType): any { // ------------------------------------------------------------------ /** Returns a clone of the given value */ export function Clone(value: T): T { - if (IsArray(value)) return ArrayType(value) - if (IsDate(value)) return DateType(value) - if (IsStandardObject(value)) return ObjectType(value) - if (IsTypedArray(value)) return TypedArrayType(value) - if (IsValueType(value)) return ValueType(value) + if (IsArray(value)) return FromArray(value) + if (IsDate(value)) return FromDate(value) + if (IsTypedArray(value)) return FromTypedArray(value) + if (IsMap(value)) return FromMap(value) + if (IsSet(value)) return FromSet(value) + if (IsObject(value)) return FromObject(value) + if (IsValueType(value)) return FromValue(value) throw new Error('ValueClone: Unable to clone value') } diff --git a/src/value/convert/convert.ts b/src/value/convert/convert.ts index 21d1dbb..e493e24 100644 --- a/src/value/convert/convert.ts +++ b/src/value/convert/convert.ts @@ -106,7 +106,7 @@ function TryConvertLiteral(schema: TLiteral, value: unknown) { IsString(schema.const) ? TryConvertLiteralString(value, schema.const) : IsNumber(schema.const) ? TryConvertLiteralNumber(value, schema.const) : IsBoolean(schema.const) ? TryConvertLiteralBoolean(value, schema.const) : - Clone(value) + value ) } function TryConvertBoolean(value: unknown) { @@ -156,7 +156,7 @@ function TryConvertDate(value: unknown) { // ------------------------------------------------------------------ // Default // ------------------------------------------------------------------ -function Default(value: any) { +function Default(value: unknown): unknown { return value } // ------------------------------------------------------------------ @@ -192,26 +192,21 @@ function FromNumber(schema: TNumber, references: TSchema[], value: any): unknown } // prettier-ignore function FromObject(schema: TObject, references: TSchema[], value: any): unknown { - const isConvertable = IsObject(value) - if(!isConvertable) return value - const result: Record = {} - for(const key of Object.keys(value)) { - result[key] = HasPropertyKey(schema.properties, key) - ? Visit(schema.properties[key], references, value[key]) - : value[key] + if(!IsObject(value)) return value + for(const key of Object.getOwnPropertyNames(schema.properties)) { + value[key] = Visit(schema.properties[key], references, value[key]) } - return result + return value } function FromRecord(schema: TRecord, references: TSchema[], value: any): unknown { const isConvertable = IsObject(value) if (!isConvertable) return value const propertyKey = Object.getOwnPropertyNames(schema.patternProperties)[0] const property = schema.patternProperties[propertyKey] - const result = {} as Record for (const [propKey, propValue] of Object.entries(value)) { - result[propKey] = Visit(property, references, propValue) + value[propKey] = Visit(property, references, propValue) } - return result + return value } function FromRef(schema: TRef, references: TSchema[], value: any): unknown { return Visit(Deref(schema, references), references, value) @@ -233,21 +228,25 @@ function FromTuple(schema: TTuple, references: TSchema[], value: any): unknown { return (index < schema.items!.length) ? Visit(schema.items![index], references, value) : value - }) + }) } function FromUndefined(schema: TUndefined, references: TSchema[], value: any): unknown { return TryConvertUndefined(value) } function FromUnion(schema: TUnion, references: TSchema[], value: any): unknown { for (const subschema of schema.anyOf) { - const converted = Visit(subschema, references, value) + const converted = Visit(subschema, references, Clone(value)) if (!Check(subschema, references, converted)) continue return converted } return value } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} function Visit(schema: TSchema, references: TSchema[], value: any): unknown { - const references_ = IsString(schema.$id) ? [...references, schema] : references + const references_ = IsString(schema.$id) ? AddReference(references, schema) : references const schema_ = schema as any switch (schema[Kind]) { case 'Array': @@ -293,14 +292,12 @@ function Visit(schema: TSchema, references: TSchema[], value: any): unknown { // ------------------------------------------------------------------ // Convert // ------------------------------------------------------------------ -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ export function Convert(schema: TSchema, references: TSchema[], value: unknown): unknown -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ export function Convert(schema: TSchema, value: unknown): unknown -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ // prettier-ignore export function Convert(...args: any[]) { - return args.length === 3 - ? Visit(args[0], args[1], args[2]) - : Visit(args[0], [], args[1]) + return args.length === 3 ? Visit(args[0], args[1], args[2]) : Visit(args[0], [], args[1]) } diff --git a/src/value/create/create.ts b/src/value/create/create.ts index 008105b..b46e837 100644 --- a/src/value/create/create.ts +++ b/src/value/create/create.ts @@ -389,8 +389,12 @@ function FromKind(schema: TSchema, references: TSchema[]): any { throw new Error('User defined types must specify a default value') } } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} function Visit(schema: TSchema, references: TSchema[]): unknown { - const references_ = IsString(schema.$id) ? [...references, schema] : references + const references_ = IsString(schema.$id) ? AddReference(references, schema) : references const schema_ = schema as any switch (schema_[Kind]) { case 'Any': diff --git a/src/value/default/default.ts b/src/value/default/default.ts index b84ce00..6c2d647 100644 --- a/src/value/default/default.ts +++ b/src/value/default/default.ts @@ -48,7 +48,7 @@ import { IsString, IsObject, IsArray, IsUndefined } from '../guard/index' // ------------------------------------------------------------------ // TypeGuard // ------------------------------------------------------------------ -import { IsSchema } from '../../type/guard/type' +import { IsKind } from '../../type/guard/kind' // ------------------------------------------------------------------ // ValueOrDefault // ------------------------------------------------------------------ @@ -56,16 +56,10 @@ function ValueOrDefault(schema: TSchema, value: unknown) { return value === undefined && 'default' in schema ? Clone(schema.default) : value } // ------------------------------------------------------------------ -// IsCheckable +// HasDefaultProperty // ------------------------------------------------------------------ -function IsCheckable(schema: unknown): boolean { - return IsSchema(schema) && schema[Kind] !== 'Unsafe' -} -// ------------------------------------------------------------------ -// IsDefaultSchema -// ------------------------------------------------------------------ -function IsDefaultSchema(value: unknown): value is TSchema { - return IsSchema(value) && 'default' in value +function HasDefaultProperty(schema: unknown): schema is TSchema { + return IsKind(schema) && 'default' in schema } // ------------------------------------------------------------------ // Types @@ -92,11 +86,11 @@ function FromObject(schema: TObject, references: TSchema[], value: unknown): any const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties) // properties for (const key of knownPropertyKeys) { - if (!IsDefaultSchema(schema.properties[key])) continue + if (!HasDefaultProperty(schema.properties[key])) continue defaulted[key] = Visit(schema.properties[key], references, defaulted[key]) } // return if not additional properties - if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted + if (!HasDefaultProperty(additionalPropertiesSchema)) return defaulted // additional properties for (const key of Object.getOwnPropertyNames(defaulted)) { if (knownPropertyKeys.includes(key)) continue @@ -112,11 +106,11 @@ function FromRecord(schema: TRecord, references: TSchema[], value: unknown): any const knownPropertyKey = new RegExp(propertyKeyPattern) // properties for (const key of Object.getOwnPropertyNames(defaulted)) { - if (!(knownPropertyKey.test(key) && IsDefaultSchema(propertySchema))) continue + if (!(knownPropertyKey.test(key) && HasDefaultProperty(propertySchema))) continue defaulted[key] = Visit(propertySchema, references, defaulted[key]) } // return if not additional properties - if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted + if (!HasDefaultProperty(additionalPropertiesSchema)) return defaulted // additional properties for (const key of Object.getOwnPropertyNames(defaulted)) { if (knownPropertyKey.test(key)) continue @@ -143,14 +137,18 @@ function FromUnion(schema: TUnion, references: TSchema[], value: unknown): any { const defaulted = ValueOrDefault(schema, value) for (const inner of schema.anyOf) { const result = Visit(inner, references, defaulted) - if (IsCheckable(inner) && Check(inner, result)) { + if (Check(inner, result)) { return result } } return defaulted } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} function Visit(schema: TSchema, references: TSchema[], value: unknown): any { - const references_ = IsString(schema.$id) ? [...references, schema] : references + const references_ = IsString(schema.$id) ? AddReference(references, schema) : references const schema_ = schema as any switch (schema_[Kind]) { case 'Array': diff --git a/src/value/equal/equal.ts b/src/value/equal/equal.ts index ce64ea9..8646fd7 100644 --- a/src/value/equal/equal.ts +++ b/src/value/equal/equal.ts @@ -26,14 +26,14 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IsStandardObject, IsDate, IsArray, IsTypedArray, IsValueType } from '../guard/index' +import { IsObject, IsDate, IsArray, IsTypedArray, IsValueType } from '../guard/index' import type { ObjectType, ArrayType, TypedArrayType, ValueType } from '../guard/index' // ------------------------------------------------------------------ // Equality Checks // ------------------------------------------------------------------ function ObjectType(left: ObjectType, right: unknown): boolean { - if (!IsStandardObject(right)) return false + if (!IsObject(right)) return false const leftKeys = [...Object.keys(left), ...Object.getOwnPropertySymbols(left)] const rightKeys = [...Object.keys(right), ...Object.getOwnPropertySymbols(right)] if (leftKeys.length !== rightKeys.length) return false @@ -58,10 +58,10 @@ function ValueType(left: ValueType, right: unknown): any { // ------------------------------------------------------------------ /** Returns true if the left value deep-equals the right */ export function Equal(left: T, right: unknown): right is T { - if (IsStandardObject(left)) return ObjectType(left, right) if (IsDate(left)) return DateType(left, right) if (IsTypedArray(left)) return TypedArrayType(left, right) if (IsArray(left)) return ArrayType(left, right) + if (IsObject(left)) return ObjectType(left, right) if (IsValueType(left)) return ValueType(left, right) throw new Error('ValueEquals: Unable to compare value') } diff --git a/src/value/hash/hash.ts b/src/value/hash/hash.ts index faa1f66..2a17342 100644 --- a/src/value/hash/hash.ts +++ b/src/value/hash/hash.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsStandardObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from '../guard/index' +import { IsArray, IsBoolean, IsBigInt, IsDate, IsNull, IsNumber, IsObject, IsString, IsSymbol, IsUint8Array, IsUndefined } from '../guard/index' import { TypeBoxError } from '../../type/error/index' // ------------------------------------------------------------------ @@ -140,7 +140,7 @@ function Visit(value: any) { if (IsDate(value)) return DateType(value) if (IsNull(value)) return NullType(value) if (IsNumber(value)) return NumberType(value) - if (IsStandardObject(value)) return ObjectType(value) + if (IsObject(value)) return ObjectType(value) if (IsString(value)) return StringType(value) if (IsSymbol(value)) return SymbolType(value) if (IsUint8Array(value)) return Uint8ArrayType(value) diff --git a/src/value/index.ts b/src/value/index.ts index 445581a..d422f87 100644 --- a/src/value/index.ts +++ b/src/value/index.ts @@ -37,6 +37,7 @@ export * from './guard/index' // ------------------------------------------------------------------ // Operators // ------------------------------------------------------------------ +export * from './assert/index' export * from './cast/index' export * from './check/index' export * from './clean/index' @@ -48,6 +49,7 @@ export * from './delta/index' export * from './equal/index' export * from './hash/index' export * from './mutate/index' +export * from './parse/index' export * from './pointer/index' export * from './transform/index' // ------------------------------------------------------------------ diff --git a/src/value/mutate/mutate.ts b/src/value/mutate/mutate.ts index 0ae01a6..ed35d9c 100644 --- a/src/value/mutate/mutate.ts +++ b/src/value/mutate/mutate.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { IsStandardObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from '../guard/index' +import { IsObject, IsArray, IsTypedArray, IsValueType, type TypedArrayType } from '../guard/index' import { ValuePointer } from '../pointer/index' import { Clone } from '../clone/index' import { TypeBoxError } from '../../type/error/index' @@ -44,7 +44,7 @@ export class ValueMutateError extends TypeBoxError { // ------------------------------------------------------------------ export type Mutable = { [key: string]: unknown } | unknown[] function ObjectType(root: Mutable, path: string, current: unknown, next: Record) { - if (!IsStandardObject(current)) { + if (!IsObject(current)) { ValuePointer.Set(root, path, Clone(next)) } else { const currentKeys = Object.getOwnPropertyNames(current) @@ -90,7 +90,7 @@ function ValueType(root: Mutable, path: string, current: unknown, next: unknown) function Visit(root: Mutable, path: string, current: unknown, next: unknown) { if (IsArray(next)) return ArrayType(root, path, current, next) if (IsTypedArray(next)) return TypedArrayType(root, path, current, next) - if (IsStandardObject(next)) return ObjectType(root, path, current, next) + if (IsObject(next)) return ObjectType(root, path, current, next) if (IsValueType(next)) return ValueType(root, path, current, next) } // ------------------------------------------------------------------ @@ -102,8 +102,8 @@ function IsNonMutableValue(value: unknown): value is Mutable { function IsMismatchedValue(current: unknown, next: unknown) { // prettier-ignore return ( - (IsStandardObject(current) && IsArray(next)) || - (IsArray(current) && IsStandardObject(next)) + (IsObject(current) && IsArray(next)) || + (IsArray(current) && IsObject(next)) ) } // ------------------------------------------------------------------ diff --git a/src/value/parse/index.ts b/src/value/parse/index.ts new file mode 100644 index 0000000..3d329f1 --- /dev/null +++ b/src/value/parse/index.ts @@ -0,0 +1,29 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/value + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +export * from './parse' diff --git a/src/value/parse/parse.ts b/src/value/parse/parse.ts new file mode 100644 index 0000000..13477a4 --- /dev/null +++ b/src/value/parse/parse.ts @@ -0,0 +1,68 @@ +/*-------------------------------------------------------------------------- + +@sinclair/typebox/value + +The MIT License (MIT) + +Copyright (c) 2017-2024 Haydn Paterson (sinclair) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +---------------------------------------------------------------------------*/ + +import { TransformDecode, HasTransform } from '../transform/index' +import { TSchema } from '../../type/schema/index' +import { StaticDecode } from '../../type/static/index' +import { Assert } from '../assert/assert' +import { Default } from '../default/default' +import { Convert } from '../convert/convert' +import { Clean } from '../clean/clean' +import { Clone } from '../clone/index' + +// ------------------------------------------------------------------ +// ParseReducer +// ------------------------------------------------------------------ +type ReducerFunction = (schema: TSchema, references: TSchema[], value: unknown) => unknown + +// prettier-ignore +const ParseReducer: ReducerFunction[] = [ + (_schema, _references, value) => Clone(value), + (schema, references, value) => Default(schema, references, value), + (schema, references, value) => Clean(schema, references, value), + (schema, references, value) => Convert(schema, references, value), + (schema, references, value) => { Assert(schema, references, value); return value }, + (schema, references, value) => (HasTransform(schema, references) ? TransformDecode(schema, references, value) : value), +] +// ------------------------------------------------------------------ +// ParseValue +// ------------------------------------------------------------------ +function ParseValue>(schema: T, references: TSchema[], value: unknown): R { + return ParseReducer.reduce((value, reducer) => reducer(schema, references, value), value) as R +} +// ------------------------------------------------------------------ +// Parse +// ------------------------------------------------------------------ +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse>(schema: T, references: TSchema[], value: unknown): R +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse>(schema: T, value: unknown): R +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse(...args: any[]): unknown { + return args.length === 3 ? ParseValue(args[0], args[1], args[2]) : ParseValue(args[0], [], args[1]) +} diff --git a/src/value/transform/decode.ts b/src/value/transform/decode.ts index e6ffb9a..a713c62 100644 --- a/src/value/transform/decode.ts +++ b/src/value/transform/decode.ts @@ -48,7 +48,7 @@ import type { TUnion } from '../../type/union/index' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsStandardObject, IsArray, IsValueType } from '../guard/index' +import { IsObject, IsArray, IsValueType } from '../guard/index' // ------------------------------------------------------------------ // TypeGuard // ------------------------------------------------------------------ @@ -97,7 +97,7 @@ function FromArray(schema: TArray, references: TSchema[], path: string, value: a } // prettier-ignore function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) { - if (!IsStandardObject(value) || IsValueType(value)) return Default(schema, path, value) + if (!IsObject(value) || IsValueType(value)) return Default(schema, path, value) const knownEntries = KeyOfPropertyEntries(schema) const knownKeys = knownEntries.map(entry => entry[0]) const knownProperties = { ...value } as Record @@ -120,7 +120,7 @@ function FromNot(schema: TNot, references: TSchema[], path: string, value: any) } // prettier-ignore function FromObject(schema: TObject, references: TSchema[], path: string, value: any) { - if (!IsStandardObject(value)) return Default(schema, path, value) + if (!IsObject(value)) return Default(schema, path, value) const knownKeys = KeyOfPropertyKeys(schema) const knownProperties = { ...value } as Record for(const key of knownKeys) if(key in knownProperties) { @@ -139,7 +139,7 @@ function FromObject(schema: TObject, references: TSchema[], path: string, value: } // prettier-ignore function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) { - if (!IsStandardObject(value)) return Default(schema, path, value) + if (!IsObject(value)) return Default(schema, path, value) const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0] const knownKeys = new RegExp(pattern) const knownProperties = { ...value } as Record @@ -183,9 +183,13 @@ function FromUnion(schema: TUnion, references: TSchema[], path: string, value: a } return Default(schema, path, value) } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} // prettier-ignore function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any { - const references_ = typeof schema.$id === 'string' ? [...references, schema] : references + const references_ = typeof schema.$id === 'string' ? AddReference(references, schema) : references const schema_ = schema as any switch (schema[Kind]) { case 'Array': diff --git a/src/value/transform/encode.ts b/src/value/transform/encode.ts index 39a43cf..2d857b7 100644 --- a/src/value/transform/encode.ts +++ b/src/value/transform/encode.ts @@ -48,7 +48,7 @@ import type { TUnion } from '../../type/union/index' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsStandardObject, IsArray, IsValueType } from '../guard/index' +import { IsObject, IsArray, IsValueType } from '../guard/index' // ------------------------------------------------------------------ // TypeGuard // ------------------------------------------------------------------ @@ -98,7 +98,7 @@ function FromArray(schema: TArray, references: TSchema[], path: string, value: a // prettier-ignore function FromIntersect(schema: TIntersect, references: TSchema[], path: string, value: any) { const defaulted = Default(schema, path, value) - if (!IsStandardObject(value) || IsValueType(value)) return defaulted + if (!IsObject(value) || IsValueType(value)) return defaulted const knownEntries = KeyOfPropertyEntries(schema) const knownKeys = knownEntries.map(entry => entry[0]) const knownProperties = { ...defaulted } as Record @@ -123,7 +123,7 @@ function FromNot(schema: TNot, references: TSchema[], path: string, value: any) // prettier-ignore function FromObject(schema: TObject, references: TSchema[], path: string, value: any) { const defaulted = Default(schema, path, value) - if (!IsStandardObject(defaulted)) return defaulted + if (!IsObject(defaulted)) return defaulted const knownKeys = KeyOfPropertyKeys(schema) as string[] const knownProperties = { ...defaulted } as Record for(const key of knownKeys) if(key in knownProperties) { @@ -143,7 +143,7 @@ function FromObject(schema: TObject, references: TSchema[], path: string, value: // prettier-ignore function FromRecord(schema: TRecord, references: TSchema[], path: string, value: any) { const defaulted = Default(schema, path, value) as Record - if (!IsStandardObject(value)) return defaulted + if (!IsObject(value)) return defaulted const pattern = Object.getOwnPropertyNames(schema.patternProperties)[0] const knownKeys = new RegExp(pattern) const knownProperties = {...defaulted } as Record @@ -151,7 +151,7 @@ function FromRecord(schema: TRecord, references: TSchema[], path: string, value: knownProperties[key] = Visit(schema.patternProperties[pattern], references, `${path}/${key}`, knownProperties[key]) } if (!IsSchema(schema.additionalProperties)) { - return Default(schema, path, knownProperties) + return knownProperties } const unknownKeys = Object.getOwnPropertyNames(knownProperties) const additionalProperties = schema.additionalProperties as TSchema @@ -194,9 +194,13 @@ function FromUnion(schema: TUnion, references: TSchema[], path: string, value: a } return Default(schema, path, value) } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} // prettier-ignore function Visit(schema: TSchema, references: TSchema[], path: string, value: any): any { - const references_ = typeof schema.$id === 'string' ? [...references, schema] : references + const references_ = typeof schema.$id === 'string' ? AddReference(references, schema) : references const schema_ = schema as any switch (schema[Kind]) { case 'Array': diff --git a/src/value/transform/has.ts b/src/value/transform/has.ts index 9aff3d2..27d7315 100644 --- a/src/value/transform/has.ts +++ b/src/value/transform/has.ts @@ -120,9 +120,13 @@ function FromTuple(schema: TTuple, references: TSchema[]) { function FromUnion(schema: TUnion, references: TSchema[]) { return IsTransform(schema) || schema.anyOf.some((schema) => Visit(schema, references)) } +function AddReference(references: TSchema[], schema: TSchema): TSchema[] { + references.push(schema) + return references +} // prettier-ignore function Visit(schema: TSchema, references: TSchema[]): boolean { - const references_ = IsString(schema.$id) ? [...references, schema] : references + const references_ = IsString(schema.$id) ? AddReference(references, schema) : references const schema_ = schema as any if (schema.$id && visited.has(schema.$id)) return false if (schema.$id) visited.add(schema.$id) diff --git a/src/value/value/value.ts b/src/value/value/value.ts index 944d27d..347b14a 100644 --- a/src/value/value/value.ts +++ b/src/value/value/value.ts @@ -27,6 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { HasTransform, TransformDecode, TransformEncode, TransformDecodeCheckError, TransformEncodeCheckError } from '../transform/index' +import { Assert as AssertValue } from '../assert/index' import { Mutate as MutateValue, type Mutable } from '../mutate/index' import { Hash as HashValue } from '../hash/index' import { Equal as EqualValue } from '../equal/index' @@ -36,6 +37,7 @@ import { Convert as ConvertValue } from '../convert/index' import { Create as CreateValue } from '../create/index' import { Clean as CleanValue } from '../clean/index' import { Check as CheckValue } from '../check/index' +import { Parse as ParseValue } from '../parse/index' import { Default as DefaultValue } from '../default/index' import { Diff as DiffValue, Patch as PatchValue, Edit } from '../delta/index' import { Errors as ValueErrors, ValueErrorIterator } from '../../errors/index' @@ -43,12 +45,20 @@ import { Errors as ValueErrors, ValueErrorIterator } from '../../errors/index' import type { TSchema } from '../../type/schema/index' import type { Static, StaticDecode, StaticEncode } from '../../type/static/index' +/** Asserts a value matches the given type or throws an `AssertError` if invalid. */ +export function Assert>(schema: T, references: TSchema[], value: unknown): asserts value is R +/** Asserts a value matches the given type or throws an `AssertError` if invalid. */ +export function Assert>(schema: T, value: unknown): asserts value is R +/** Asserts a value matches the given type or throws an `AssertError` if invalid. */ +export function Assert(...args: any[]): any { + return AssertValue.apply(AssertValue, args as any) +} /** Casts a value into a given type. The return value will retain as much information of the original value as possible. */ export function Cast(schema: T, references: TSchema[], value: unknown): Static /** Casts a value into a given type. The return value will retain as much information of the original value as possible. */ export function Cast(schema: T, value: unknown): Static /** Casts a value into a given type. The return value will retain as much information of the original value as possible. */ -export function Cast(...args: any[]) { +export function Cast(...args: any[]): any { return CastValue.apply(CastValue, args as any) } /** Creates a value from the given type and references */ @@ -56,7 +66,7 @@ export function Create(schema: T, references: TSchema[]): Sta /** Creates a value from the given type */ export function Create(schema: T): Static /** Creates a value from the given type */ -export function Create(...args: any[]) { +export function Create(...args: any[]): any { return CreateValue.apply(CreateValue, args as any) } /** Returns true if the value matches the given type and references */ @@ -64,7 +74,7 @@ export function Check(schema: T, references: TSchema[], value /** Returns true if the value matches the given type */ export function Check(schema: T, value: unknown): value is Static /** Returns true if the value matches the given type */ -export function Check(...args: any[]) { +export function Check(...args: any[]): any { return CheckValue.apply(CheckValue, args as any) } /** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */ @@ -72,15 +82,15 @@ export function Clean(schema: TSchema, references: TSchema[], value: unknown): u /** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */ export function Clean(schema: TSchema, value: unknown): unknown /** `[Mutable]` Removes excess properties from a value and returns the result. This function does not check the value and returns an unknown type. You should Check the result before use. Clean is a mutable operation. To avoid mutation, Clone the value first. */ -export function Clean(...args: any[]) { +export function Clean(...args: any[]): any { return CleanValue.apply(CleanValue, args as any) } -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ export function Convert(schema: TSchema, references: TSchema[], value: unknown): unknown -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ export function Convert(schema: TSchema, value: unknown): unknown -/** Converts any type mismatched values to their target type if a reasonable conversion is possible. */ -export function Convert(...args: any[]) { +/** `[Mutable]` Converts any type mismatched values to their target type if a reasonable conversion is possible. */ +export function Convert(...args: any[]): any { return ConvertValue.apply(ConvertValue, args as any) } /** Returns a structural clone of the given value */ @@ -92,7 +102,7 @@ export function Decode>(schema: T, refere /** Decodes a value or throws if error */ export function Decode>(schema: T, value: unknown): R /** Decodes a value or throws if error */ -export function Decode(...args: any[]) { +export function Decode(...args: any[]): any { const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] if (!Check(schema, references, value)) throw new TransformDecodeCheckError(schema, value, Errors(schema, references, value).First()!) return HasTransform(schema, references) ? TransformDecode(schema, references, value) : value @@ -102,7 +112,7 @@ export function Default(schema: TSchema, references: TSchema[], value: unknown): /** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */ export function Default(schema: TSchema, value: unknown): unknown /** `[Mutable]` Generates missing properties on a value using default schema annotations if available. This function does not check the value and returns an unknown type. You should Check the result before use. Default is a mutable operation. To avoid mutation, Clone the value first. */ -export function Default(...args: any[]) { +export function Default(...args: any[]): any { return DefaultValue.apply(DefaultValue, args as any) } /** Encodes a value or throws if error */ @@ -110,18 +120,26 @@ export function Encode>(schema: T, refere /** Encodes a value or throws if error */ export function Encode>(schema: T, value: unknown): R /** Encodes a value or throws if error */ -export function Encode(...args: any[]) { +export function Encode(...args: any[]): any { const [schema, references, value] = args.length === 3 ? [args[0], args[1], args[2]] : [args[0], [], args[1]] const encoded = HasTransform(schema, references) ? TransformEncode(schema, references, value) : value if (!Check(schema, references, encoded)) throw new TransformEncodeCheckError(schema, encoded, Errors(schema, references, encoded).First()!) return encoded } +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse>(schema: T, references: TSchema[], value: unknown): R +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse>(schema: T, value: unknown): R +/** Parses a value or throws an `AssertError` if invalid. */ +export function Parse(...args: any[]): unknown { + return ParseValue.apply(ParseValue, args as any) +} /** Returns an iterator for each error in this value. */ export function Errors(schema: T, references: TSchema[], value: unknown): ValueErrorIterator /** Returns an iterator for each error in this value. */ export function Errors(schema: T, value: unknown): ValueErrorIterator /** Returns an iterator for each error in this value. */ -export function Errors(...args: any[]) { +export function Errors(...args: any[]): any { return ValueErrors.apply(ValueErrors, args as any) } /** Returns true if left and right values are structurally equal */ diff --git a/test/runtime/value/assert/assert.ts b/test/runtime/value/assert/assert.ts new file mode 100644 index 0000000..fe3afdc --- /dev/null +++ b/test/runtime/value/assert/assert.ts @@ -0,0 +1,35 @@ +import { Value, AssertError } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +describe('value/Assert', () => { + it('Should Assert', () => { + Assert.Throws(() => Value.Assert(Type.String(), 1)) + }) + it('Should throw AssertError', () => { + try { + Value.Assert(Type.String(), 1) + } catch (error) { + if (error instanceof AssertError) { + return + } + throw error + } + }) + it('Should throw AssertError and produce Iterator', () => { + try { + Value.Assert(Type.String(), 1) + } catch (error) { + if (error instanceof AssertError) { + const first = error.Errors().First() + Assert.HasProperty(first, 'type') + Assert.HasProperty(first, 'schema') + Assert.HasProperty(first, 'path') + Assert.HasProperty(first, 'value') + Assert.HasProperty(first, 'message') + return + } + throw error + } + }) +}) diff --git a/test/runtime/value/assert/index.ts b/test/runtime/value/assert/index.ts new file mode 100644 index 0000000..f043fc6 --- /dev/null +++ b/test/runtime/value/assert/index.ts @@ -0,0 +1 @@ +import './assert' diff --git a/test/runtime/value/default/union.ts b/test/runtime/value/default/union.ts index 56b572c..e9f4e68 100644 --- a/test/runtime/value/default/union.ts +++ b/test/runtime/value/default/union.ts @@ -1,5 +1,5 @@ import { Value } from '@sinclair/typebox/value' -import { Type } from '@sinclair/typebox' +import { Type, Kind, TypeRegistry } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('value/default/Union', () => { @@ -86,25 +86,29 @@ describe('value/default/Union', () => { // Interior Unsafe // ---------------------------------------------------------------- it('Should default interior unsafe 1', () => { + TypeRegistry.Set('DefaultUnsafe', (schema, value) => typeof value === 'string') const T = Type.Union([ Type.Object({ x: Type.Number({ default: 1 }), y: Type.Number({ default: 2 }), }), - Type.Unsafe({ default: 'hello' }), + Type.Unsafe({ [Kind]: 'DefaultUnsafe', default: 'hello' }), ]) const R = Value.Default(T, undefined) - Assert.IsEqual(R, undefined) + Assert.IsEqual(R, 'hello') + TypeRegistry.Delete('DefaultUnsafe') }) it('Should default interior unsafe 2', () => { + TypeRegistry.Set('DefaultUnsafe', (schema, value) => typeof value === 'string') const T = Type.Union([ Type.Object({ x: Type.Number({ default: 1 }), y: Type.Number({ default: 2 }), }), - Type.Unsafe({ default: 'hello' }), + Type.Unsafe({ [Kind]: 'DefaultUnsafe', default: 'hello' }), ]) const R = Value.Default(T, 'world') Assert.IsEqual(R, 'world') + TypeRegistry.Delete('DefaultUnsafe') }) }) diff --git a/test/runtime/value/index.ts b/test/runtime/value/index.ts index 4e6ce99..515d5ff 100644 --- a/test/runtime/value/index.ts +++ b/test/runtime/value/index.ts @@ -1,3 +1,4 @@ +import './assert' import './cast' import './check' import './clean' @@ -10,5 +11,6 @@ import './equal' import './guard' import './hash' import './mutate' +import './parse' import './pointer' import './transform' diff --git a/test/runtime/value/parse/index.ts b/test/runtime/value/parse/index.ts new file mode 100644 index 0000000..09cc871 --- /dev/null +++ b/test/runtime/value/parse/index.ts @@ -0,0 +1 @@ +import './parse' diff --git a/test/runtime/value/parse/parse.ts b/test/runtime/value/parse/parse.ts new file mode 100644 index 0000000..5c8be9d --- /dev/null +++ b/test/runtime/value/parse/parse.ts @@ -0,0 +1,90 @@ +import { Value, AssertError } from '@sinclair/typebox/value' +import { Type } from '@sinclair/typebox' +import { Assert } from '../../assert/index' + +// prettier-ignore +describe('value/Parse', () => { + it('Should Parse', () => { + const A = Value.Parse(Type.Literal('hello'), 'hello') + Assert.IsEqual(A, 'hello') + }) + it('Should not Parse', () => { + Assert.Throws(() => Value.Parse(Type.Literal('hello'), 'world')) + }) + it('Should throw AssertError', () => { + try { + Value.Parse(Type.Literal('hello'), 'world') + } catch(error) { + if(error instanceof AssertError) { + return + } + throw error + } + }) + it('Should throw AssertError and produce Iterator', () => { + try { + Value.Parse(Type.Literal('hello'), 'world') + } catch(error) { + if(error instanceof AssertError) { + const first = error.Errors().First() + Assert.HasProperty(first, 'type') + Assert.HasProperty(first, 'schema') + Assert.HasProperty(first, 'path') + Assert.HasProperty(first, 'value') + Assert.HasProperty(first, 'message') + return + } + throw error + } + }) + // ---------------------------------------------------------------- + // Default + // ---------------------------------------------------------------- + it('Should use Default values', () => { + const X = Value.Parse(Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }) + }), { }) + Assert.IsEqual(X, { x: 1, y: 2 }) + }) + it('Should throw on invalid Default values', () => { + Assert.Throws(() => Value.Parse(Type.Object({ + x: Type.Number({ default: null }), + y: Type.Number({ default: null }) + }), { })) + }) + // ---------------------------------------------------------------- + // Convert + // ---------------------------------------------------------------- + it('Should Convert value 1', () => { + const X = Value.Parse(Type.Object({ + x: Type.Number(), + y: Type.Number() + }), { x: '1', y: '2' }) + Assert.IsEqual(X, { x: 1, y: 2 }) + }) + it('Should Convert value 2', () => { + const X = Value.Parse(Type.Array(Type.String()), [1, 2, 3, 4]) + Assert.IsEqual(X, ['1', '2', '3', '4']) + }) + // ---------------------------------------------------------------- + // Clean + // ---------------------------------------------------------------- + it('Should Clean value', () => { + const X = Value.Parse(Type.Object({ + x: Type.Number(), + y: Type.Number() + }), { x: 1, y: 2, z: 3 }) + Assert.IsEqual(X, { x: 1, y: 2 }) + }) + // ---------------------------------------------------------------- + // Decode + // ---------------------------------------------------------------- + it('Should Decode value', () => { + const T = Type.Transform(Type.String()) + .Decode(value => 'hello') + .Encode(value => value) + const X = Value.Parse(T, 'world') + Assert.IsEqual(X, 'hello') + }) +}) diff --git a/test/runtime/value/transform/record.ts b/test/runtime/value/transform/record.ts index a91172d..6f53ef5 100644 --- a/test/runtime/value/transform/record.ts +++ b/test/runtime/value/transform/record.ts @@ -1,6 +1,5 @@ import * as Encoder from './_encoder' import { Assert } from '../../assert' -import { Value } from '@sinclair/typebox/value' import { Type } from '@sinclair/typebox' describe('value/transform/Record', () => {