From 03cf2fe78087248b266f81c6f1507aaee1fc678a Mon Sep 17 00:00:00 2001 From: sinclair Date: Sun, 29 Oct 2023 20:23:32 +0900 Subject: [PATCH 1/2] Extends Optional Property Check --- src/typebox.ts | 78 ++++++++++++++++------------- test/runtime/type/extends/object.ts | 39 +++++++++++++++ 2 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/typebox.ts b/src/typebox.ts index 700e1c3ad..0f9d2e03c 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 @@ -1533,40 +1536,42 @@ export namespace TypeGuard { } /** Returns true if the given value is TSchema */ export function TSchema(schema: unknown): schema is TSchema { + // prettier-ignore return ( - ValueGuard.IsObject(schema) && - (TAny(schema) || - TArray(schema) || - TBoolean(schema) || - TBigInt(schema) || - TAsyncIterator(schema) || - TConstructor(schema) || - TDate(schema) || - TFunction(schema) || - TInteger(schema) || - TIntersect(schema) || - TIterator(schema) || - TLiteral(schema) || - TNever(schema) || - TNot(schema) || - TNull(schema) || - TNumber(schema) || - TObject(schema) || - TPromise(schema) || - TRecord(schema) || - TRef(schema) || - TString(schema) || - TSymbol(schema) || - TTemplateLiteral(schema) || - TThis(schema) || - TTuple(schema) || - TUndefined(schema) || - TUnion(schema) || - TUint8Array(schema) || - TUnknown(schema) || - TUnsafe(schema) || - TVoid(schema) || - (TKind(schema) && TypeRegistry.Has(schema[Kind] as any))) + ValueGuard.IsObject(schema) + ) && ( + TAny(schema) || + TArray(schema) || + TBoolean(schema) || + TBigInt(schema) || + TAsyncIterator(schema) || + TConstructor(schema) || + TDate(schema) || + TFunction(schema) || + TInteger(schema) || + TIntersect(schema) || + TIterator(schema) || + TLiteral(schema) || + TNever(schema) || + TNot(schema) || + TNull(schema) || + TNumber(schema) || + TObject(schema) || + TPromise(schema) || + TRecord(schema) || + TRef(schema) || + TString(schema) || + TSymbol(schema) || + TTemplateLiteral(schema) || + TThis(schema) || + TTuple(schema) || + TUndefined(schema) || + TUnion(schema) || + TUint8Array(schema) || + TUnknown(schema) || + TUnsafe(schema) || + TVoid(schema) || + (TKind(schema) && TypeRegistry.Has(schema[Kind] as any)) ) } } @@ -1986,7 +1991,12 @@ export namespace TypeExtends { !TypeGuard.TObject(right) ? TypeExtendsResult.False : (() => { for (const key of Object.getOwnPropertyNames(right.properties)) { - if (!(key in left.properties)) return TypeExtendsResult.False + if (!(key in left.properties) && !TypeGuard.TOptional(right.properties[key])) { + return TypeExtendsResult.False + } + if(TypeGuard.TOptional(right.properties[key])) { + return TypeExtendsResult.True + } if (Property(left.properties[key], right.properties[key]) === TypeExtendsResult.False) { return TypeExtendsResult.False } diff --git a/test/runtime/type/extends/object.ts b/test/runtime/type/extends/object.ts index 172eb46bf..fc57cbabd 100644 --- a/test/runtime/type/extends/object.ts +++ b/test/runtime/type/extends/object.ts @@ -174,4 +174,43 @@ describe('type/extends/Object', () => { const R = TypeExtends.Extends(Type.Object({ a: Type.Number() }), Type.Date()) Assert.IsEqual(R, TypeExtendsResult.False) }) + // ---------------------------------------------------------------- + // Issues + // ---------------------------------------------------------------- + it('Should extend optional 1', () => { + const A = Type.Object({ + x: Type.Optional(Type.Number()), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Number(), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.False) + }) + it('Should extend optional 2', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Number(), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.False) + }) + it('Should extend optional 3', () => { + const A = Type.Object({ + x: Type.Number(), + y: Type.Number(), + }) + const B = Type.Object({ + y: Type.Number(), + z: Type.Optional(Type.Number()), + }) + const R = TypeExtends.Extends(A, B) + Assert.IsEqual(R, TypeExtendsResult.True) + }) }) From 8a391cfe1122fb87e0ab3e6c9faa1a776a9503a9 Mon Sep 17 00:00:00 2001 From: sinclair Date: Sun, 29 Oct 2023 20:31:22 +0900 Subject: [PATCH 2/2] Additional Tests and Version --- package-lock.json | 4 ++-- package.json | 2 +- test/runtime/type/extends/object.ts | 18 +++++++++++++++--- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 49319b749..613c25ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.18.0", diff --git a/package.json b/package.json index 42fe99b9f..0b8deb405 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.31.20", + "version": "0.31.21", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/test/runtime/type/extends/object.ts b/test/runtime/type/extends/object.ts index fc57cbabd..8bc3a6e63 100644 --- a/test/runtime/type/extends/object.ts +++ b/test/runtime/type/extends/object.ts @@ -175,9 +175,21 @@ describe('type/extends/Object', () => { Assert.IsEqual(R, TypeExtendsResult.False) }) // ---------------------------------------------------------------- - // Issues + // Optional // ---------------------------------------------------------------- it('Should extend optional 1', () => { + const A = Type.Object({ a: Type.Number() }) + const B = Type.Object({ a: Type.Optional(Type.Number()) }) + const C = TypeExtends.Extends(A, B) + Assert.IsEqual(C, TypeExtendsResult.True) + }) + it('Should extend optional 2', () => { + const A = Type.Object({ a: Type.Number() }) + const B = Type.Object({ a: Type.Optional(Type.Number()) }) + const C = TypeExtends.Extends(B, A) + Assert.IsEqual(C, TypeExtendsResult.False) + }) + it('Should extend optional 3', () => { const A = Type.Object({ x: Type.Optional(Type.Number()), y: Type.Number(), @@ -189,7 +201,7 @@ describe('type/extends/Object', () => { const R = TypeExtends.Extends(A, B) Assert.IsEqual(R, TypeExtendsResult.False) }) - it('Should extend optional 2', () => { + it('Should extend optional 4', () => { const A = Type.Object({ x: Type.Number(), y: Type.Number(), @@ -201,7 +213,7 @@ describe('type/extends/Object', () => { const R = TypeExtends.Extends(A, B) Assert.IsEqual(R, TypeExtendsResult.False) }) - it('Should extend optional 3', () => { + it('Should extend optional 5', () => { const A = Type.Object({ x: Type.Number(), y: Type.Number(),