Skip to content

Commit

Permalink
Clean Tests | Updates to Default Handling
Browse files Browse the repository at this point in the history
  • Loading branch information
sinclairzx81 committed Oct 27, 2023
1 parent 4f6f41d commit 959fc8e
Show file tree
Hide file tree
Showing 22 changed files with 275 additions and 32 deletions.
8 changes: 8 additions & 0 deletions examples/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,11 @@ import { Type, TypeGuard, Kind, Static, TSchema } from '@sinclair/typebox'

// Todo: Investigate Union Default (initialize interior types)
// Todo: Implement Value.Clean() Tests

const T = Type.Record(Type.Number(), Type.String({ default: 1 }), {
additionalProperties: Type.Any({ default: 1000 }),
})

const R = Value.Default(T, { y: 2, z: undefined })

console.log(R)
31 changes: 21 additions & 10 deletions src/value/clean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ function Default(value: unknown) {
return value
}
// --------------------------------------------------------------------------
// IsSchema
// --------------------------------------------------------------------------
function IsSchema(value: unknown): value is Types.TSchema {
return Types.TypeGuard.TKind(value) && Types.TypeRegistry.Has(value[Types.Kind])
}
// --------------------------------------------------------------------------
// Structural
// --------------------------------------------------------------------------
function TArray(schema: Types.TArray, references: Types.TSchema[], value: unknown): any {
Expand All @@ -53,23 +59,28 @@ function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value
}
function TObject(schema: Types.TObject, references: Types.TSchema[], value: unknown): any {
if (!IsObject(value)) return Default(value)
return Object.keys(schema.properties).reduce((acc, key) => {
// prettier-ignore
return key in value
? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) }
: acc
const properties = Object.keys(schema.properties).reduce((acc, key) => {
return key in value ? { ...acc, [key]: Visit(schema.properties[key], references, value[key]) } : acc
}, {})
if (!IsSchema(schema.additionalProperties)) {
return properties
}
const additionalProperties = Object.keys(value).reduce((acc, key) => {
if (key in schema.properties) return acc
return Check(schema.additionalProperties as Types.TSchema, value[key]) ? { ...acc, [key]: value[key] } : acc
}, {})
return { ...properties, ...additionalProperties }
}
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: unknown): any {
if (!IsObject(value)) return Default(value)
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const patternRegExp = new RegExp(patternKey)
return Object.keys(value).reduce((acc, key) => {
// prettier-ignore
return patternRegExp.test(key)
? { ...acc, [key]: Visit(patternSchema, references, value[key]) }
: acc
const properties = Object.keys(value).reduce((acc, key) => {
return patternRegExp.test(key) ? { ...acc, [key]: Visit(patternSchema, references, value[key]) } : acc
}, {})
if (!Types.TypeGuard.TKind(schema.additionalProperties)) {
return properties
}
}
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: unknown): any {
const target = Deref(schema, references)
Expand Down
46 changes: 27 additions & 19 deletions src/value/default.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/

import { IsString, IsObject, IsArray, IsUndefined } from './guard'
import { HasPropertyKey, IsString, IsObject, IsArray, IsUndefined } from './guard'
import { Deref } from './deref'
import { Clone } from './clone'
import * as Types from '../typebox'
Expand Down Expand Up @@ -60,31 +60,39 @@ function TIntersect(schema: Types.TIntersect, references: Types.TSchema[], value
function TObject(schema: Types.TObject, references: Types.TSchema[], value: unknown): any {
const object = ValueOrDefault(schema, value)
if (!IsObject(object)) return object
const knownObject = Object.getOwnPropertyNames(schema.properties).reduce((acc, key) => {
const knownPropertyKeys = Object.getOwnPropertyNames(schema.properties)
// Reduce on object using known keys and only map for property types that have
// default values specified. The returned object should be the complete object
// with additional properties unmapped.
const properties = knownPropertyKeys.reduce((acc, key) => {
return HasDefault(schema.properties[key]) ? { ...acc, [key]: Visit(schema.properties[key], references, object[key]) } : acc
}, object)
if (!Types.TypeGuard.TSchema(schema.additionalProperties) || !HasDefault(schema.additionalProperties as Types.TSchema)) {
return knownObject
}
const knownKeys = Object.getOwnPropertyNames(schema.properties)
return Object.getOwnPropertyNames(knownObject).reduce((acc, key) => {
return knownKeys.includes(key) ? acc : { ...acc, [key]: Visit(schema.additionalProperties as Types.TSchema, references, knownObject[key]) }
}, knownObject)
// If additionalProperties not is schema-like with a default property, we exit with properties.
const additionalPropertiesSchema = schema.additionalProperties as Types.TSchema
if (!(IsObject(additionalPropertiesSchema) && HasDefault(additionalPropertiesSchema))) return properties
// Reduce on properties using object key. Only map properties outside the known key set
return Object.getOwnPropertyNames(object).reduce((acc, key) => {
return !knownPropertyKeys.includes(key) ? { ...acc, [key]: Visit(additionalPropertiesSchema, references, object[key]) } : acc
}, properties)
}
function TRecord(schema: Types.TRecord<any, any>, references: Types.TSchema[], value: unknown): any {
const object = ValueOrDefault(schema, value)
if (!IsObject(object)) return object
const [patternKey, patternSchema] = Object.entries(schema.patternProperties)[0]
const patternRegExp = new RegExp(patternKey)
const knownObject = Object.getOwnPropertyNames(value).reduce((acc, key) => {
return HasDefault(patternSchema) && patternRegExp.test(key) ? { ...acc, [key]: Visit(patternSchema, references, object[key]) } : acc
const [propertyKeyPattern, propertySchema] = Object.entries(schema.patternProperties)[0]
const knownPropertyKey = new RegExp(propertyKeyPattern)
// Reduce on object keys using object keys and only map for property types that have
// default values specified. The returned object should be the complete object
// with additional properties unmapped.
const properties = Object.getOwnPropertyNames(object).reduce((acc, key) => {
return knownPropertyKey.test(key) && HasDefault(propertySchema) ? { ...acc, [key]: Visit(propertySchema, references, object[key]) } : { ...acc, [key]: object[key] }
}, object)
if (!Types.TypeGuard.TSchema(schema.additionalProperties) || !HasDefault(schema.additionalProperties as Types.TSchema)) {
return knownObject
}
return Object.getOwnPropertyNames(knownObject).reduce((acc, key) => {
return !patternRegExp.test(key) ? { ...acc, [key]: Visit(schema.additionalProperties as Types.TSchema, references, knownObject[key]) } : acc
}, knownObject)
// If additionalProperties not is schema-like with a default property, we exit with properties.
const additionalPropertiesSchema = schema.additionalProperties as Types.TSchema
if (!(IsObject(additionalPropertiesSchema) && HasDefault(additionalPropertiesSchema))) return properties
// Reduce on properties using object key. Only map properties outside the known key set
return Object.getOwnPropertyNames(object).reduce((acc, key) => {
return !knownPropertyKey.test(key) ? { ...acc, [key]: Visit(additionalPropertiesSchema, references, object[key]) } : acc
}, properties)
}
function TRef(schema: Types.TRef<any>, references: Types.TSchema[], value: unknown): any {
return Visit(Deref(schema, references), references, ValueOrDefault(schema, value))
Expand Down
11 changes: 11 additions & 0 deletions test/runtime/value/clean/any.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Any', () => {
it('Should clean 1', () => {
const T = Type.Any()
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
21 changes: 21 additions & 0 deletions test/runtime/value/clean/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Array', () => {
it('Should clean 1', () => {
const T = Type.Any()
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean 2', () => {
const T = Type.Array(
Type.Object({
x: Type.Number(),
y: Type.Number(),
}),
)
const R = Value.Clean(T, [undefined, null, { x: 1 }, { x: 1, y: 2 }, { x: 1, y: 2, z: 3 }])
Assert.IsEqual(R, [undefined, null, { x: 1 }, { x: 1, y: 2 }, { x: 1, y: 2 }])
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/async-iterator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/AsyncIterator', () => {
it('Should clean 1', () => {
const T = Type.AsyncIterator(Type.Number())
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/bigint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/BigInt', () => {
it('Should clean 1', () => {
const T = Type.BigInt()
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/boolean.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Boolean', () => {
it('Should clean 1', () => {
const T = Type.Boolean()
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/composite.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Composite', () => {
it('Should clean 1', () => {
const T = Type.Composite([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })])
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/constructor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Constructor', () => {
it('Should clean 1', () => {
const T = Type.Constructor([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })], Type.Object({ z: Type.Number() }))
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Enum', () => {
it('Should clean 1', () => {
const T = Type.Enum({ x: 1, y: 2 })
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
11 changes: 11 additions & 0 deletions test/runtime/value/clean/function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Function', () => {
it('Should clean 1', () => {
const T = Type.Function([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })], Type.Object({ z: Type.Number() }))
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
10 changes: 10 additions & 0 deletions test/runtime/value/clean/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import './any'
import './array'
import './async-iterator'
import './bigint'
import './boolean'
import './composite'
import './enum'
import './function'
import './integer'
import './intersect'
11 changes: 11 additions & 0 deletions test/runtime/value/clean/integer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Integer', () => {
it('Should clean 1', () => {
const T = Type.Integer()
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
})
26 changes: 26 additions & 0 deletions test/runtime/value/clean/intersect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/clean/Intersect', () => {
it('Should clean 1', () => {
const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })])
const R = Value.Clean(T, null)
Assert.IsEqual(R, null)
})
it('Should clean 2', () => {
const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })])
const R = Value.Clean(T, {})
Assert.IsEqual(R, {})
})
it('Should clean 3', () => {
const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })])
const R = Value.Clean(T, { x: 1, y: 2 })
Assert.IsEqual(R, { x: 1, y: 2 })
})
it('Should clean 4', () => {
const T = Type.Intersect([Type.Object({ x: Type.Number() }), Type.Object({ y: Type.Number() })])
const R = Value.Clean(T, { x: 1, y: 2, z: 3 })
Assert.IsEqual(R, { x: 1, y: 2 })
})
})
14 changes: 14 additions & 0 deletions test/runtime/value/create/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/create/Date', () => {
it('Should create value', () => {
const T = Type.Date()
Assert.IsInstanceOf(Value.Create(T), Date)
})
it('Should create default', () => {
const T = Type.Date({ default: 1 })
Assert.IsEqual(Value.Create(T), 1)
})
})
1 change: 1 addition & 0 deletions test/runtime/value/create/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './bigint'
import './boolean'
import './composite'
import './constructor'
import './date'
import './enum'
import './function'
import './integer'
Expand Down
16 changes: 16 additions & 0 deletions test/runtime/value/default/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/default/Date', () => {
it('Should use default', () => {
const T = Type.Date({ default: 1 })
const R = Value.Default(T, undefined)
Assert.IsEqual(R, 1)
})
it('Should use value', () => {
const T = Type.Date({ default: 1 })
const R = Value.Default(T, null)
Assert.IsEqual(R, null)
})
})
2 changes: 2 additions & 0 deletions test/runtime/value/default/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './bigint'
import './boolean'
import './composite'
import './constructor'
import './date'
import './enum'
import './function'
import './integer'
Expand All @@ -18,6 +19,7 @@ import './not'
import './null'
import './number'
import './object'
import './promise'
import './record'
import './recursive'
import './ref'
Expand Down
16 changes: 16 additions & 0 deletions test/runtime/value/default/promise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Value } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'
import { Assert } from '../../assert/index'

describe('value/default/Promise', () => {
it('Should use default', () => {
const T = Type.Any({ default: 1 })
const R = Value.Default(T, undefined)
Assert.IsEqual(R, 1)
})
it('Should use value', () => {
const T = Type.Any({ default: 1 })
const R = Value.Default(T, null)
Assert.IsEqual(R, null)
})
})
Loading

0 comments on commit 959fc8e

Please sign in to comment.