From b8ea2899b7fcfe32ff66271f47f97dadf4d6564e Mon Sep 17 00:00:00 2001 From: sinclair Date: Wed, 29 Nov 2023 18:32:39 +0900 Subject: [PATCH] Revision 0.32.0 --- examples/index.ts | 62 ++++++++++----------------- src/value/transform/decode.ts | 9 ++-- test/runtime/assert/assert.ts | 6 ++- test/runtime/value/transform/union.ts | 43 ++++++++++++++++++- 4 files changed, 73 insertions(+), 47 deletions(-) diff --git a/examples/index.ts b/examples/index.ts index 73de170ea..c8c065668 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,43 +1,25 @@ -import Type, { type Static } from '@sinclair/typebox' +import Type, { FormatRegistry, type Static } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import { TypeCompiler } from '@sinclair/typebox/compiler' -const A = Type.Number({}) - -// console.log(AA) - -// const X = TypeCompiler.Code( -// Type.Object({ -// x: Type.String(), -// y: Type.BigInt(), -// z: Type.Array(Type.Null()), -// }), -// ) - -// console.log(X) - -// const A = Type.Transform(Type.String()) -// .Decode((value: string): Date => new Date(value)) -// .Encode((value: Date): string => value.toISOString()) - -// const B = Type.Union([Type.Object({ date: A }), Type.Number()]) - -// const C = Type.Transform(B) -// .Decode((value) => { -// console.log(value) -// if (typeof value === 'object') { -// // date is supposed to be a Date according to type inference, but it's a string when it's logged. -// console.log('union', typeof value.date) // string -// } -// return value -// }) -// .Encode((o) => o) - -// const D = Type.Transform(Type.Object({ date: A })) -// .Decode((o) => { -// console.log('simple', typeof o.date) // object -// return o -// }) -// .Encode((o) => o) - -// const R1 = Value.Decode(C, [], { date: new Date().toISOString() }) +const A = Type.Transform(Type.String()) + .Decode((value: string) => new Date(value)) + .Encode((value: Date) => value.toISOString()) + +const B = Type.Union([Type.Object({ date: A }), Type.Number()]) +const T1 = Type.Transform(B) + .Decode((value) => { + // expect date + return value + }) + .Encode((value) => value) +const T2 = Type.Transform(B) + .Decode((value) => { + // expect number + console.log(value) + return value + }) + .Encode((value) => value) + +const R1 = Value.Decode(T1, { date: new Date().toISOString() }) +const R2 = Value.Decode(T2, 1) diff --git a/src/value/transform/decode.ts b/src/value/transform/decode.ts index 7ffe3d899..7652cc08d 100644 --- a/src/value/transform/decode.ts +++ b/src/value/transform/decode.ts @@ -162,12 +162,13 @@ function TTuple(schema: TTuple, references: TSchema[], value: any) { } // prettier-ignore function TUnion(schema: TUnion, references: TSchema[], value: any) { - const defaulted = Default(schema, value) for (const subschema of schema.anyOf) { - if (!Check(subschema, references, defaulted)) continue - return Visit(subschema, references, defaulted) + if (!Check(subschema, references, value)) continue + // note: ensure interior is decoded first + const decoded = Visit(subschema, references, value) + return Default(schema, decoded) } - return defaulted + return Default(schema, value) } // prettier-ignore function Visit(schema: TSchema, references: TSchema[], value: any): any { diff --git a/test/runtime/assert/assert.ts b/test/runtime/assert/assert.ts index 2e9a92944..315eec216 100644 --- a/test/runtime/assert/assert.ts +++ b/test/runtime/assert/assert.ts @@ -1,6 +1,10 @@ import * as assert from 'assert' export namespace Assert { + export function HasProperty(value: unknown, key: K): asserts value is Record { + if (typeof value === 'object' && value !== null && key in value) return + throw new Error(`Expected value to have property '${key as string}'`) + } export function IsTrue(value: boolean): asserts value is true { return assert.strictEqual(value, true) } @@ -46,7 +50,7 @@ export namespace Assert { if (value instanceof constructor) return throw Error(`Value is not instance of ${constructor}`) } - export function IsTypeOf(value: any, type: any) { + export function IsTypeOf(value: any, type: T) { if (typeof value === type) return throw Error(`Value is not typeof ${type}`) } diff --git a/test/runtime/value/transform/union.ts b/test/runtime/value/transform/union.ts index 93eeff81c..43152417f 100644 --- a/test/runtime/value/transform/union.ts +++ b/test/runtime/value/transform/union.ts @@ -1,7 +1,6 @@ import * as Encoder from './_encoder' import { Assert } from '../../assert' -import { Value } from '@sinclair/typebox/value' -import { Type, TSchema } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' describe('value/transform/Union', () => { // -------------------------------------------------------- @@ -199,4 +198,44 @@ describe('value/transform/Union', () => { it('Should throw on interior union encode', () => { Assert.Throws(() => Encoder.Encode(T52, 1)) }) + // prettier-ignore + { // https://github.com/sinclairzx81/typebox/issues/676 + // interior-type + const S = Type.Transform(Type.String()) + .Decode((value: string) => new globalThis.Date(value)) + .Encode((value: Date) => value.toISOString()) + // union-type + const U = Type.Union([ + Type.Object({ date: S }), + Type.Number() + ]) + // expect date on decode + const T1 = Type.Transform(U) + .Decode((value) => { + Assert.IsTypeOf(value, 'object') + Assert.HasProperty(value, 'date') + Assert.IsInstanceOf(value.date, globalThis.Date); + return value + }) + .Encode((value) => value) + // expect number on decode + const T2 = Type.Transform(U) + .Decode((value) => { + Assert.IsTypeOf(value, 'number') + return value + }) + .Encode((value) => value) + + it('Should decode interior union 1', () => { + const R = Encoder.Decode(T1, { date: new globalThis.Date().toISOString() }) + Assert.IsTypeOf(R, 'object') + Assert.HasProperty(R, 'date') + Assert.IsInstanceOf(R.date, globalThis.Date); + }) + it('Should decode interior union 2', () => { + const R = Encoder.Decode(T2, 123) + Assert.IsTypeOf(R, 'number') + Assert.IsEqual(R, 123) + }) + } })