Skip to content

Commit

Permalink
Support Unregistered Kinds in ObjectMap (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Apr 15, 2023
1 parent e348464 commit 6cfcdc0
Show file tree
Hide file tree
Showing 9 changed files with 76 additions and 32 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sinclair/typebox",
"version": "0.27.7",
"version": "0.27.8",
"description": "JSONSchema Type Builder with Static Type Resolution for TypeScript",
"keywords": [
"typescript",
Expand Down
22 changes: 11 additions & 11 deletions src/typebox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1832,24 +1832,24 @@ export namespace TypeClone {
// --------------------------------------------------------------------------
export namespace ObjectMap {
function Intersect(schema: TIntersect, callback: (object: TObject) => TObject) {
return Type.Intersect(
schema.allOf.map((inner) => Visit(inner, callback)),
{ ...schema },
)
// prettier-ignore
return Type.Intersect(schema.allOf.map((inner) => Visit(inner, callback)), { ...schema })
}
function Union(schema: TUnion, callback: (object: TObject) => TObject) {
return Type.Union(
schema.anyOf.map((inner) => Visit(inner, callback)),
{ ...schema },
)
// prettier-ignore
return Type.Union(schema.anyOf.map((inner) => Visit(inner, callback)), { ...schema })
}
function Object(schema: TObject, callback: (object: TObject) => TObject) {
return callback(schema)
}
function Visit(schema: TSchema, callback: (object: TObject) => TObject): TSchema {
if (TypeGuard.TIntersect(schema)) return Intersect(schema, callback)
if (TypeGuard.TUnion(schema)) return Union(schema, callback)
if (TypeGuard.TObject(schema)) return Object(schema, callback)
// There are cases where users need to map objects with unregistered kinds. Using a TypeGuard here would
// prevent sub schema mapping as unregistered kinds will not pass TSchema checks. This is notable in the
// case of TObject where unregistered property kinds cause the TObject check to fail. As mapping is only
// used for composition, we use explicit checks instead.
if (schema[Kind] === 'Intersect') return Intersect(schema as TIntersect, callback)
if (schema[Kind] === 'Union') return Union(schema as TUnion, callback)
if (schema[Kind] === 'Object') return Object(schema as TObject, callback)
return schema
}
export function Map<T = TSchema>(schema: TSchema, callback: (object: TObject) => TObject, options: SchemaOptions): T {
Expand Down
4 changes: 2 additions & 2 deletions test/runtime/compiler/omit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Type } from '@sinclair/typebox'
import { Type, Kind } from '@sinclair/typebox'
import { Ok, Fail } from './validate'
import { strictEqual } from 'assert'
import { deepEqual, strictEqual } from 'assert'

describe('type/compiler/Omit', () => {
it('Should omit properties on the source schema', () => {
Expand Down
16 changes: 0 additions & 16 deletions test/runtime/compiler/partial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,4 @@ describe('type/compiler/Partial', () => {
strictEqual(A.additionalProperties, false)
strictEqual(T.additionalProperties, false)
})
it('Should support partial properties of raw TUnsafe', () => {
// https://github.com/sinclairzx81/typebox/issues/364
const T = Type.Partial(Type.Object({ x: Type.Unsafe({ x: 1 }) }))
strictEqual(T.required, undefined)
})
it('Should not support partial properties of unknown TUnsafe', () => {
// https://github.com/sinclairzx81/typebox/issues/364
const T = Type.Partial(Type.Object({ x: Type.Unsafe({ [Kind]: 'UnknownPartialType', x: 1 }) }))
strictEqual(T.required![0], 'x')
})
it('Should support partial properties of custom TUnsafe', () => {
// https://github.com/sinclairzx81/typebox/issues/364
const U = TypeSystem.Type('CustomPartialType', () => true)
const T = Type.Partial(Type.Object({ x: U() }))
strictEqual(T.required, undefined)
})
})
3 changes: 3 additions & 0 deletions test/runtime/type/guard/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import './not'
import './null'
import './number'
import './object'
import './omit'
import './partial'
import './pick'
import './promise'
import './record'
import './ref'
Expand Down
17 changes: 17 additions & 0 deletions test/runtime/type/guard/omit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TypeGuard } from '@sinclair/typebox'
import { Type, Kind } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('type/guard/TOmit', () => {
// -------------------------------------------------------------------------
// case: https://github.com/sinclairzx81/typebox/issues/384
// -------------------------------------------------------------------------
it('Should support TUnsafe omit properties with no Kind', () => {
const T = Type.Omit(Type.Object({ x: Type.Unsafe({ x: 1 }), y: Type.Number() }), ['x'])
Assert.deepEqual(T.required, ['y'])
})
it('Should support TUnsafe omit properties with unregistered Kind', () => {
const T = Type.Omit(Type.Object({ x: Type.Unsafe({ x: 1, [Kind]: 'UnknownOmitType' }), y: Type.Number() }), ['x'])
Assert.deepEqual(T.required, ['y'])
})
})
23 changes: 23 additions & 0 deletions test/runtime/type/guard/partial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { TypeSystem } from '@sinclair/typebox/system'
import { TypeGuard } from '@sinclair/typebox'
import { Type, Kind } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('type/guard/TPartial', () => {
// -------------------------------------------------------------------------
// case: https://github.com/sinclairzx81/typebox/issues/364
// -------------------------------------------------------------------------
it('Should support TUnsafe partial properties with no Kind', () => {
const T = Type.Partial(Type.Object({ x: Type.Unsafe({ x: 1 }) }))
Assert.deepEqual(T.required, undefined)
})
it('Should support TUnsafe partial properties with unregistered Kind', () => {
const T = Type.Partial(Type.Object({ x: Type.Unsafe({ [Kind]: 'UnknownPartialType', x: 1 }) }))
Assert.deepEqual(T.required, undefined)
})
it('Should support TUnsafe partial properties with registered Kind', () => {
const U = TypeSystem.Type('CustomPartialType', () => true)
const T = Type.Partial(Type.Object({ x: U() }))
Assert.deepEqual(T.required, undefined)
})
})
17 changes: 17 additions & 0 deletions test/runtime/type/guard/pick.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { TypeGuard } from '@sinclair/typebox'
import { Type, Kind } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('type/guard/TPick', () => {
// -------------------------------------------------------------------------
// case: https://github.com/sinclairzx81/typebox/issues/384
// -------------------------------------------------------------------------
it('Should support TUnsafe omit properties with no Kind', () => {
const T = Type.Pick(Type.Object({ x: Type.Unsafe({ x: 1 }), y: Type.Number() }), ['x'])
Assert.deepEqual(T.required, ['x'])
})
it('Should support TUnsafe omit properties with unregistered Kind', () => {
const T = Type.Pick(Type.Object({ x: Type.Unsafe({ x: 1, [Kind]: 'UnknownPickType' }), y: Type.Number() }), ['x'])
Assert.deepEqual(T.required, ['x'])
})
})

0 comments on commit 6cfcdc0

Please sign in to comment.