From 48cb15414900bd768ce64bdfd9001a394a02dfe8 Mon Sep 17 00:00:00 2001 From: sinclair Date: Sun, 29 Oct 2023 15:18:46 +0900 Subject: [PATCH] Testing --- examples/benchmark/benchmark.ts | 3 +- examples/index.ts | 39 +++++++----- src/typebox.ts | 3 + src/value/clean.ts | 13 +++- src/value/default.ts | 75 ++++++++++++++--------- test/runtime/value/default/union.ts | 94 +++++++++++++++++++++++++++++ 6 files changed, 179 insertions(+), 48 deletions(-) diff --git a/examples/benchmark/benchmark.ts b/examples/benchmark/benchmark.ts index a654f485c..1980ffcc6 100644 --- a/examples/benchmark/benchmark.ts +++ b/examples/benchmark/benchmark.ts @@ -28,7 +28,6 @@ THE SOFTWARE. import { Stopwatch } from './stopwatch' - export type BenchmarkFunction = () => any export interface BenchmarkOptions { @@ -37,7 +36,7 @@ export interface BenchmarkOptions { export interface BenchmarkResult extends BenchmarkOptions { elapsed: number } -export function Run(callback: BenchmarkFunction, options: BenchmarkOptions): BenchmarkResult { +export function Benchmark(callback: BenchmarkFunction, options: BenchmarkOptions): BenchmarkResult { const stopwatch = new Stopwatch() for (let i = 0; i < options.iterations; i++) { callback() diff --git a/examples/index.ts b/examples/index.ts index 90f6267da..7a01c8f61 100644 --- a/examples/index.ts +++ b/examples/index.ts @@ -1,22 +1,31 @@ import { TypeSystem } from '@sinclair/typebox/system' import { TypeCompiler } from '@sinclair/typebox/compiler' import { Value, ValueGuard } from '@sinclair/typebox/value' -import { Type, TypeGuard, Kind, Static, TSchema, KeyResolver, TObject } from '@sinclair/typebox' -import { Run } from './benchmark' +import { Type, TypeGuard, Kind, Static, TSchema, KeyResolver, TObject, TypeRegistry, StaticDecode } from '@sinclair/typebox' +import { Benchmark } from './benchmark' // Todo: Investigate Union Default (initialize interior types) // Todo: Implement Value.Clean() Tests - Done -const A = Type.Union([ - Type.Number({ default: 1 }), - Type.String({ default: 'hello' }) -]) - -const X = Value.Default(A, undefined) // should pick the first? - -console.log(X) - - - - - +export function Parse(schema: T, value: unknown): StaticDecode { + const converted = Value.Convert(schema, value) + const defaulted = Value.Default(schema, converted) + const decoded = Value.Decode(schema, defaulted) + return Value.Clean(schema, decoded) +} + +const DateNumber = Type.Transform(Type.Number()) + .Decode((value) => new Date(value)) + .Encode((value) => value.getTime()) + +const R = Parse( + Type.Object({ + PROCESSOR_ARCHITECTURE: Type.String(), + PROCESSOR_IDENTIFIER: Type.String(), + PROCESSOR_LEVEL: DateNumber, + PROCESSOR_REVISION: Type.Number(), + }), + process.env, +) + +console.log(R) diff --git a/src/typebox.ts b/src/typebox.ts index 635cb0717..919ff12bf 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -1144,6 +1144,9 @@ export namespace TypeGuard { function IsOptionalSchema(value: unknown): value is boolean | undefined { return ValueGuard.IsUndefined(value) || TSchema(value) } + // ---------------------------------------------------------------- + // Types + // ---------------------------------------------------------------- /** Returns true if the given value is TAny */ export function TAny(schema: unknown): schema is TAny { // prettier-ignore diff --git a/src/value/clean.ts b/src/value/clean.ts index e0bddd64f..835871b20 100644 --- a/src/value/clean.ts +++ b/src/value/clean.ts @@ -28,8 +28,8 @@ THE SOFTWARE. import { IsString, IsObject, IsArray, IsUndefined } from './guard' import { Check } from './check' -import { Deref } from './deref' import { Clone } from './clone' +import { Deref } from './deref' import * as Types from '../typebox' // -------------------------------------------------------------------------- @@ -38,6 +38,12 @@ import * as Types from '../typebox' function IsSchema(schema: unknown): schema is Types.TSchema { return Types.TypeGuard.TSchema(schema) } +// ---------------------------------------------------------------- +// IsCheckable +// ---------------------------------------------------------------- +function IsCheckable(schema: unknown): boolean { + return Types.TypeGuard.TSchema(schema) && schema[Types.Kind] !== 'Unsafe' +} // -------------------------------------------------------------------------- // Types // -------------------------------------------------------------------------- @@ -114,8 +120,9 @@ function TTuple(schema: Types.TTuple, references: Types.TSchema[], value: unknow } function TUnion(schema: Types.TUnion, references: Types.TSchema[], value: unknown): any { for (const inner of schema.anyOf) { - if (!Check(inner, value)) continue - return Visit(inner, references, value) + if (IsCheckable(inner) && Check(inner, value)) { + return Visit(inner, references, value) + } } return value } diff --git a/src/value/default.ts b/src/value/default.ts index 1c35e812b..fc5cf8270 100644 --- a/src/value/default.ts +++ b/src/value/default.ts @@ -27,6 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import { IsString, IsObject, IsArray, IsUndefined } from './guard' +import { Check } from './check' import { Deref } from './deref' import * as Types from '../typebox' @@ -36,6 +37,12 @@ import * as Types from '../typebox' function ValueOrDefault(schema: Types.TSchema, value: unknown) { return !(value === undefined) || !('default' in schema) ? value : schema.default } +// ---------------------------------------------------------------- +// IsCheckable +// ---------------------------------------------------------------- +function IsCheckable(schema: unknown): boolean { + return Types.TypeGuard.TSchema(schema) && schema[Types.Kind] !== 'Unsafe' +} // -------------------------------------------------------------------------- // IsDefaultSchema // -------------------------------------------------------------------------- @@ -46,58 +53,58 @@ function IsDefaultSchema(value: unknown): value is Types.TSchema { // Types // -------------------------------------------------------------------------- function TArray(schema: Types.TArray, references: Types.TSchema[], value: unknown): any { - const elements = ValueOrDefault(schema, value) - if (!IsArray(elements)) return elements - for (let i = 0; i < elements.length; i++) { - elements[i] = Visit(schema.items, references, elements[i]) + const defaulted = ValueOrDefault(schema, value) + if (!IsArray(defaulted)) return defaulted + for (let i = 0; i < defaulted.length; i++) { + defaulted[i] = Visit(schema.items, references, defaulted[i]) } - return elements + return defaulted } function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value: unknown): any { - const result = ValueOrDefault(schema, value) + const defaulted = ValueOrDefault(schema, value) return schema.allOf.reduce((acc, schema) => { - const next = Visit(schema, references, result) + const next = Visit(schema, references, defaulted) return IsObject(next) ? { ...acc, ...next } : next }, {}) } function TObject(schema: Types.TObject, references: Types.TSchema[], value: unknown): any { - const object = ValueOrDefault(schema, value) - if (!IsObject(object)) return object + const defaulted = ValueOrDefault(schema, value) + if (!IsObject(defaulted)) return defaulted const additionalPropertiesSchema = schema.additionalProperties as Types.TSchema const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties) // properties for (const key of knownPropertyKeys) { if (!IsDefaultSchema(schema.properties[key])) continue - object[key] = Visit(schema.properties[key], references, object[key]) + defaulted[key] = Visit(schema.properties[key], references, defaulted[key]) } // return if not additional properties - if (!IsDefaultSchema(additionalPropertiesSchema)) return object + if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted // additional properties - for (const key of Object.getOwnPropertyNames(object)) { + for (const key of Object.getOwnPropertyNames(defaulted)) { if (knownPropertyKeys.includes(key)) continue - object[key] = Visit(additionalPropertiesSchema, references, object[key]) + defaulted[key] = Visit(additionalPropertiesSchema, references, defaulted[key]) } - return object + return defaulted } function TRecord(schema: Types.TRecord, references: Types.TSchema[], value: unknown): any { - const object = ValueOrDefault(schema, value) - if (!IsObject(object)) return object + const defaulted = ValueOrDefault(schema, value) + if (!IsObject(defaulted)) return defaulted const additionalPropertiesSchema = schema.additionalProperties as Types.TSchema const [propertyKeyPattern, propertySchema] = Object.entries(schema.patternProperties)[0] const knownPropertyKey = new RegExp(propertyKeyPattern) // properties - for (const key of Object.getOwnPropertyNames(object)) { + for (const key of Object.getOwnPropertyNames(defaulted)) { if (!(knownPropertyKey.test(key) && IsDefaultSchema(propertySchema))) continue - object[key] = Visit(propertySchema, references, object[key]) + defaulted[key] = Visit(propertySchema, references, defaulted[key]) } // return if not additional properties - if (!IsDefaultSchema(additionalPropertiesSchema)) return object + if (!IsDefaultSchema(additionalPropertiesSchema)) return defaulted // additional properties - for (const key of Object.getOwnPropertyNames(object)) { + for (const key of Object.getOwnPropertyNames(defaulted)) { if (knownPropertyKey.test(key)) continue - object[key] = Visit(additionalPropertiesSchema, references, object[key]) + defaulted[key] = Visit(additionalPropertiesSchema, references, defaulted[key]) } - return object + return defaulted } function TRef(schema: Types.TRef, references: Types.TSchema[], value: unknown): any { return Visit(Deref(schema, references), references, ValueOrDefault(schema, value)) @@ -105,14 +112,24 @@ function TRef(schema: Types.TRef, references: Types.TSchema[], value: unkno function TThis(schema: Types.TThis, references: Types.TSchema[], value: unknown): any { return Visit(Deref(schema, references), references, value) } -function TTuple(schema: Types.TTuple, references: Types.TSchema[], value: unknown): any { - const elements = ValueOrDefault(schema, value) - if (!IsArray(elements) || IsUndefined(schema.items)) return elements - const [items, max] = [schema.items!, Math.max(schema.items!.length, elements.length)] +function TTuple(schema: Types.TTuple, references: Types.TSchema[], value: unknown): any { + const defaulted = ValueOrDefault(schema, value) + if (!IsArray(defaulted) || IsUndefined(schema.items)) return defaulted + const [items, max] = [schema.items!, Math.max(schema.items!.length, defaulted.length)] for (let i = 0; i < max; i++) { - if (i < items.length) elements[i] = Visit(items[i], references, elements[i]) + if (i < items.length) defaulted[i] = Visit(items[i], references, defaulted[i]) + } + return defaulted +} +function TUnion(schema: Types.TUnion, references: Types.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)) { + return result + } } - return elements + return defaulted } function Visit(schema: Types.TSchema, references: Types.TSchema[], value: unknown): any { const references_ = IsString(schema.$id) ? [...references, schema] : references @@ -132,6 +149,8 @@ function Visit(schema: Types.TSchema, references: Types.TSchema[], value: unknow return TThis(schema_, references_, value) case 'Tuple': return TTuple(schema_, references_, value) + case 'Union': + return TUnion(schema_, references_, value) default: return ValueOrDefault(schema_, value) } diff --git a/test/runtime/value/default/union.ts b/test/runtime/value/default/union.ts index e79f973e4..56b572caf 100644 --- a/test/runtime/value/default/union.ts +++ b/test/runtime/value/default/union.ts @@ -13,4 +13,98 @@ describe('value/default/Union', () => { const R = Value.Default(T, null) Assert.IsEqual(R, null) }) + // ---------------------------------------------------------------- + // Interior + // ---------------------------------------------------------------- + it('Should default interior 1', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, null) + Assert.IsEqual(R, null) + }) + it('Should default interior 2', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, undefined) + Assert.IsEqual(R, 'hello') + }) + it('Should default interior 3', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, 'world') + Assert.IsEqual(R, 'world') + }) + it('Should default interior 4', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, {}) + Assert.IsEqual(R, { x: 1, y: 2 }) + }) + it('Should default interior 5', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, { x: 3 }) + Assert.IsEqual(R, { x: 3, y: 2 }) + }) + it('Should default interior 6', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.String({ default: 'hello' }), + ]) + const R = Value.Default(T, { x: 3, y: 4 }) + Assert.IsEqual(R, { x: 3, y: 4 }) + }) + // ---------------------------------------------------------------- + // Interior Unsafe + // ---------------------------------------------------------------- + it('Should default interior unsafe 1', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.Unsafe({ default: 'hello' }), + ]) + const R = Value.Default(T, undefined) + Assert.IsEqual(R, undefined) + }) + it('Should default interior unsafe 2', () => { + const T = Type.Union([ + Type.Object({ + x: Type.Number({ default: 1 }), + y: Type.Number({ default: 2 }), + }), + Type.Unsafe({ default: 'hello' }), + ]) + const R = Value.Default(T, 'world') + Assert.IsEqual(R, 'world') + }) })