From e5d5ec051587510b24fab4e1f8b4146b08ba93a0 Mon Sep 17 00:00:00 2001 From: Haydn Paterson Date: Wed, 2 Oct 2024 22:00:16 +0900 Subject: [PATCH] Revision 0.33.13 (#1011) * Correctly Deref Union Variant in Default * Version * ChangeLog --- changelog/0.33.0.md | 2 ++ package-lock.json | 4 +-- package.json | 2 +- src/value/convert/convert.ts | 8 ++---- src/value/create/create.ts | 10 ++----- src/value/default/default.ts | 12 +++----- src/value/deref/deref.ts | 14 ++++++++-- src/value/transform/decode.ts | 8 ++---- src/value/transform/encode.ts | 8 ++---- src/value/transform/has.ts | 10 ++----- test/runtime/value/default/recursive.ts | 37 ++++++++++++++++++++++++- 11 files changed, 68 insertions(+), 47 deletions(-) diff --git a/changelog/0.33.0.md b/changelog/0.33.0.md index 74913d5ec..71155ab8a 100644 --- a/changelog/0.33.0.md +++ b/changelog/0.33.0.md @@ -1,4 +1,6 @@ ### 0.33.0 +- [Revision 0.33.13](https://github.com/sinclairzx81/typebox/pull/1011) + - [1010](https://github.com/sinclairzx81/typebox/pull/1011) Fixes Value.Parse fails with recursive types - [Revision 0.33.12](https://github.com/sinclairzx81/typebox/pull/999) - [998](https://github.com/sinclairzx81/typebox/issues/998) Avoid losing precision when converting to bigints - [Revision 0.33.11](https://github.com/sinclairzx81/typebox/pull/994) diff --git a/package-lock.json b/package-lock.json index 7ee28d5b4..103ce872d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.33.12", + "version": "0.33.13", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.33.12", + "version": "0.33.13", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.13.2", diff --git a/package.json b/package.json index 062077bbc..375d8ab30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.33.12", + "version": "0.33.13", "description": "Json Schema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/value/convert/convert.ts b/src/value/convert/convert.ts index 46dae1b3f..92e6c5811 100644 --- a/src/value/convert/convert.ts +++ b/src/value/convert/convert.ts @@ -28,7 +28,7 @@ THE SOFTWARE. import { Clone } from '../clone/index' import { Check } from '../check/index' -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { Kind } from '../../type/symbols/index' import type { TSchema } from '../../type/schema/index' @@ -249,12 +249,8 @@ function FromUnion(schema: TUnion, references: TSchema[], value: any): unknown { } 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) ? AddReference(references, schema) : references + const references_ = Pushref(schema, references) const schema_ = schema as any switch (schema[Kind]) { case 'Array': diff --git a/src/value/create/create.ts b/src/value/create/create.ts index 952c35751..a1c24c578 100644 --- a/src/value/create/create.ts +++ b/src/value/create/create.ts @@ -26,10 +26,10 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { HasPropertyKey, IsString } from '../guard/index' +import { HasPropertyKey } from '../guard/index' import { Check } from '../check/index' import { Clone } from '../clone/index' -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { TemplateLiteralGenerate, IsTemplateLiteralFinite } from '../../type/template-literal/index' import { PatternStringExact, PatternNumberExact } from '../../type/patterns/index' import { TypeRegistry } from '../../type/registry/index' @@ -391,12 +391,8 @@ 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) ? AddReference(references, schema) : references + const references_ = Pushref(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 d16d9235c..d12b40338 100644 --- a/src/value/default/default.ts +++ b/src/value/default/default.ts @@ -28,7 +28,7 @@ THE SOFTWARE. import { Check } from '../check/index' import { Clone } from '../clone/index' -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { Kind } from '../../type/symbols/index' import type { TSchema } from '../../type/schema/index' @@ -44,7 +44,7 @@ import type { TUnion } from '../../type/union/index' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsString, IsFunction, IsObject, IsArray, IsUndefined, HasPropertyKey } from '../guard/index' +import { IsFunction, IsObject, IsArray, IsUndefined, HasPropertyKey } from '../guard/index' // ------------------------------------------------------------------ // TypeGuard // ------------------------------------------------------------------ @@ -143,18 +143,14 @@ 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, Clone(defaulted)) - if (Check(inner, result)) { + if (Check(inner, references, 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) ? AddReference(references, schema) : references + const references_ = Pushref(schema, references) const schema_ = schema as any switch (schema_[Kind]) { case 'Array': diff --git a/src/value/deref/deref.ts b/src/value/deref/deref.ts index 91a77915c..2f7d594ad 100644 --- a/src/value/deref/deref.ts +++ b/src/value/deref/deref.ts @@ -31,10 +31,10 @@ import type { TRef } from '../../type/ref/index' import type { TThis } from '../../type/recursive/index' import { TypeBoxError } from '../../type/error/index' import { Kind } from '../../type/symbols/index' - +import { IsString } from '../guard/guard' export class TypeDereferenceError extends TypeBoxError { constructor(public readonly schema: TRef | TThis) { - super(`Unable to dereference schema with $id '${schema.$id}'`) + super(`Unable to dereference schema with $id '${schema.$ref}'`) } } function Resolve(schema: TThis | TRef, references: TSchema[]): TSchema { @@ -42,7 +42,15 @@ function Resolve(schema: TThis | TRef, references: TSchema[]): TSchema { if (target === undefined) throw new TypeDereferenceError(schema) return Deref(target, references) } -/** Dereferences a schema from the references array or throws if not found */ + +/** `[Internal]` Pushes a schema onto references if the schema has an $id and does not exist on references */ +export function Pushref(schema: TSchema, references: TSchema[]): TSchema[] { + if (!IsString(schema.$id) || references.some((target) => target.$id === schema.$id)) return references + references.push(schema) + return references +} + +/** `[Internal]` Dereferences a schema from the references array or throws if not found */ export function Deref(schema: TSchema, references: TSchema[]): TSchema { // prettier-ignore return (schema[Kind] === 'This' || schema[Kind] === 'Ref') diff --git a/src/value/transform/decode.ts b/src/value/transform/decode.ts index 5bac20ecf..be56d12bc 100644 --- a/src/value/transform/decode.ts +++ b/src/value/transform/decode.ts @@ -31,7 +31,7 @@ import { Kind, TransformKind } from '../../type/symbols/index' import { TypeBoxError } from '../../type/error/index' import { ValueError } from '../../errors/index' import { KeyOfPropertyKeys, KeyOfPropertyEntries } from '../../type/keyof/index' -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { Check } from '../check/index' import type { TSchema } from '../../type/schema/index' @@ -192,13 +192,9 @@ 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' ? AddReference(references, schema) : references + const references_ = Pushref(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 a39aaa0ea..cb626f916 100644 --- a/src/value/transform/encode.ts +++ b/src/value/transform/encode.ts @@ -31,7 +31,7 @@ import { Kind, TransformKind } from '../../type/symbols/index' import { TypeBoxError } from '../../type/error/index' import { ValueError } from '../../errors/index' import { KeyOfPropertyKeys, KeyOfPropertyEntries } from '../../type/keyof/index' -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { Check } from '../check/index' import type { TSchema } from '../../type/schema/index' @@ -203,13 +203,9 @@ 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' ? AddReference(references, schema) : references + const references_ = Pushref(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 27d731554..7481d86a1 100644 --- a/src/value/transform/has.ts +++ b/src/value/transform/has.ts @@ -26,7 +26,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ -import { Deref } from '../deref/index' +import { Deref, Pushref } from '../deref/index' import { Kind } from '../../type/symbols/index' import type { TSchema } from '../../type/schema/index' @@ -52,7 +52,7 @@ import { IsTransform, IsSchema } from '../../type/guard/type' // ------------------------------------------------------------------ // ValueGuard // ------------------------------------------------------------------ -import { IsString, IsUndefined } from '../guard/index' +import { IsUndefined } from '../guard/index' // prettier-ignore function FromArray(schema: TArray, references: TSchema[]): boolean { @@ -120,13 +120,9 @@ 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) ? AddReference(references, schema) : references + const references_ = Pushref(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/test/runtime/value/default/recursive.ts b/test/runtime/value/default/recursive.ts index 417727bda..492f92d24 100644 --- a/test/runtime/value/default/recursive.ts +++ b/test/runtime/value/default/recursive.ts @@ -1,5 +1,5 @@ import { Value } from '@sinclair/typebox/value' -import { Type } from '@sinclair/typebox' +import { TSchema, Type } from '@sinclair/typebox' import { Assert } from '../../assert/index' // prettier-ignore @@ -55,4 +55,39 @@ describe('value/default/Recursive', () => { id: 1 }) }) + // ---------------------------------------------------------------- + // https://github.com/sinclairzx81/typebox/issues/1010 + // ---------------------------------------------------------------- + it('Should default Recursive Union', () => { + const Binary = (node: Node) => Type.Object({ + type: Type.Literal('Binary'), + left: node, + right: node + }) + const Node = Type.Object({ + type: Type.Literal('Node'), + value: Type.String({ default: 'X' }) + }) + const Expr = Type.Recursive(This => Type.Union([Binary(This), Node])) + const R = Value.Default(Expr, { + type: 'Binary', + left: { + type: 'Node' + }, + right: { + type: 'Node' + } + }) + Assert.IsEqual(R, { + type: 'Binary', + left: { + type: 'Node', + value: 'X' + }, + right: { + type: 'Node', + value: 'X' + } + }) + }) })