Skip to content

Commit

Permalink
Fix for Computed Readonly and Optional Properties (#1096)
Browse files Browse the repository at this point in the history
* Fix importing optional properties

* Readonly and Optional Unwrap for Computed Type

* Comment for Modifier Reapply on Unwrap

---------

Co-authored-by: sinclair <[email protected]>
  • Loading branch information
michaelcollabai and sinclairzx81 authored Nov 23, 2024
1 parent b05c59c commit 81f0a28
Show file tree
Hide file tree
Showing 7 changed files with 341 additions and 2 deletions.
12 changes: 12 additions & 0 deletions src/type/module/compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/

import { CreateType } from '../create/index'
import { Discard } from '../discard/index'
import { Ensure, Evaluate } from '../helpers/index'
import { type TSchema } from '../schema/index'
import { Array, type TArray } from '../array/index'
Expand All @@ -42,15 +43,18 @@ import { Iterator, type TIterator } from '../iterator/index'
import { KeyOf, type TKeyOf } from '../keyof/index'
import { Object, type TObject, type TProperties } from '../object/index'
import { Omit, type TOmit } from '../omit/index'
import { type TOptional } from '../optional/index'
import { Pick, type TPick } from '../pick/index'
import { Never, type TNever } from '../never/index'
import { Partial, TPartial } from '../partial/index'
import { type TReadonly } from '../readonly/index'
import { Record, type TRecordOrObject } from '../record/index'
import { type TRef } from '../ref/index'
import { Required, TRequired } from '../required/index'
import { Tuple, type TTuple } from '../tuple/index'
import { Union, type TUnion, type TUnionEvaluated } from '../union/index'

import { OptionalKind, ReadonlyKind } from '../symbols/index'
// ------------------------------------------------------------------
// KindGuard
// ------------------------------------------------------------------
Expand Down Expand Up @@ -340,6 +344,10 @@ function FromRest<ModuleProperties extends TProperties, Types extends TSchema[]>
// ------------------------------------------------------------------
// prettier-ignore
export type TFromType<ModuleProperties extends TProperties, Type extends TSchema> = (
// Modifier Unwrap
Type extends TOptional<infer Type extends TSchema> ? TOptional<TFromType<ModuleProperties, Type>> :
Type extends TReadonly<infer Type extends TSchema> ? TReadonly<TFromType<ModuleProperties, Type>> :
// Traveral
Type extends TArray<infer Type extends TSchema> ? TFromArray<ModuleProperties, Type> :
Type extends TAsyncIterator<infer Type extends TSchema> ? TFromAsyncIterator<ModuleProperties, Type> :
Type extends TComputed<infer Target extends string, infer Parameters extends TSchema[]> ? TFromComputed<ModuleProperties, Target, Parameters> :
Expand All @@ -356,6 +364,10 @@ export type TFromType<ModuleProperties extends TProperties, Type extends TSchema
// prettier-ignore
export function FromType<ModuleProperties extends TProperties, Type extends TSchema>(moduleProperties: ModuleProperties, type: Type): TFromType<ModuleProperties, Type> {
return (
// Modifier Unwrap - Reapplied via CreateType Options
KindGuard.IsOptional(type) ? CreateType(FromType(moduleProperties, Discard(type, [OptionalKind]) as TSchema) as never, type) :
KindGuard.IsReadonly(type) ? CreateType(FromType(moduleProperties, Discard(type, [ReadonlyKind]) as TSchema) as never, type) :
// Traveral
KindGuard.IsArray(type) ? CreateType(FromArray(moduleProperties, type.items), type) :
KindGuard.IsAsyncIterator(type) ? CreateType(FromAsyncIterator(moduleProperties, type.items), type) :
// Note: The 'as never' is required due to excessive resolution of TIndex. In fact TIndex, TPick, TOmit and
Expand Down
31 changes: 31 additions & 0 deletions test/runtime/compiler-ajv/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,35 @@ describe('compiler-ajv/Module', () => {
Ok(T, 'hello')
Fail(T, 'world')
})
// ----------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------
it('Should validate objects with property modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
Ok(T, { x: null, y: null, w: null })
Ok(T, { y: null, w: null })
Fail(T, { x: 1, y: null, w: null })
})
it('Should validate objects with property modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
Ok(T, { x: [null], y: [null], w: [null] })
Ok(T, { y: [null], w: [null] })
Fail(T, { x: [1], y: [null], w: [null] })
})
})
31 changes: 31 additions & 0 deletions test/runtime/compiler/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,35 @@ describe('compiler/Module', () => {
Ok(T, 'hello')
Fail(T, 'world')
})
// ----------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------
it('Should validate objects with property modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
Ok(T, { x: null, y: null, w: null })
Ok(T, { y: null, w: null })
Fail(T, { x: 1, y: null, w: null })
})
it('Should validate objects with property modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
Ok(T, { x: [null], y: [null], w: [null] })
Ok(T, { y: [null], w: [null] })
Fail(T, { x: [1], y: [null], w: [null] })
})
})
87 changes: 87 additions & 0 deletions test/runtime/type/guard/kind/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,91 @@ describe('guard/kind/TImport', () => {
Assert.IsTrue(T.$defs['R'].anyOf[0].const === 'x')
Assert.IsTrue(T.$defs['R'].anyOf[1].const === 'y')
})
// ----------------------------------------------------------------
// Modifiers: 1
// ----------------------------------------------------------------
it('Should compute for Modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
const R = T.$defs[T.$ref]
Assert.IsTrue(KindGuard.IsObject(R))

Assert.IsTrue(KindGuard.IsNull(R.properties.x))
Assert.IsTrue(KindGuard.IsReadonly(R.properties.x))
Assert.IsTrue(KindGuard.IsOptional(R.properties.x))

Assert.IsTrue(KindGuard.IsNull(R.properties.y))
Assert.IsTrue(KindGuard.IsReadonly(R.properties.y))
Assert.IsFalse(KindGuard.IsOptional(R.properties.y))

Assert.IsTrue(KindGuard.IsNull(R.properties.z))
Assert.IsTrue(KindGuard.IsOptional(R.properties.z))
Assert.IsFalse(KindGuard.IsReadonly(R.properties.z))

Assert.IsTrue(KindGuard.IsNull(R.properties.w))
Assert.IsFalse(KindGuard.IsOptional(R.properties.w))
Assert.IsFalse(KindGuard.IsReadonly(R.properties.w))
})
// ----------------------------------------------------------------
// Modifiers: 2
// ----------------------------------------------------------------
it('Should compute for Modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
const R = T.$defs[T.$ref]
Assert.IsTrue(KindGuard.IsObject(R))

Assert.IsTrue(KindGuard.IsArray(R.properties.x))
Assert.IsTrue(KindGuard.IsNull(R.properties.x.items))
Assert.IsTrue(KindGuard.IsReadonly(R.properties.x))
Assert.IsTrue(KindGuard.IsOptional(R.properties.x))

Assert.IsTrue(KindGuard.IsArray(R.properties.y))
Assert.IsTrue(KindGuard.IsNull(R.properties.y.items))
Assert.IsTrue(KindGuard.IsReadonly(R.properties.y))
Assert.IsFalse(KindGuard.IsOptional(R.properties.y))

Assert.IsTrue(KindGuard.IsArray(R.properties.z))
Assert.IsTrue(KindGuard.IsNull(R.properties.z.items))
Assert.IsTrue(KindGuard.IsOptional(R.properties.z))
Assert.IsFalse(KindGuard.IsReadonly(R.properties.z))

Assert.IsTrue(KindGuard.IsArray(R.properties.w))
Assert.IsTrue(KindGuard.IsNull(R.properties.w.items))
Assert.IsFalse(KindGuard.IsOptional(R.properties.w))
Assert.IsFalse(KindGuard.IsReadonly(R.properties.w))
})
// ----------------------------------------------------------------
// Modifiers: 3
// ----------------------------------------------------------------
it('Should compute for Modifiers 3', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.Array(Type.Null()),
}),
// Computed Partial
U: Type.Partial(Type.Ref('T')),
})
const T = Module.Import('U')
const R = T.$defs[T.$ref]
Assert.IsTrue(KindGuard.IsObject(R))

Assert.IsTrue(KindGuard.IsArray(R.properties.x))
Assert.IsTrue(KindGuard.IsNull(R.properties.x.items))
Assert.IsTrue(KindGuard.IsOptional(R.properties.x))
})
})
89 changes: 87 additions & 2 deletions test/runtime/type/guard/type/import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,6 @@ describe('guard/type/TImport', () => {
R: Type.Record(Type.String(), Type.Ref('T')),
})
const T = Module.Import('R')

console.dir(T, { depth: 100 })
Assert.IsTrue(TypeGuard.IsRecord(T.$defs['R']))
// note: TRecord<TSchema, TRef<...>> are not computed. Only the Key is
// computed as TypeBox needs to make a deferred call to transform from
Expand Down Expand Up @@ -192,4 +190,91 @@ describe('guard/type/TImport', () => {
Assert.IsTrue(T.$defs['R'].anyOf[0].const === 'x')
Assert.IsTrue(T.$defs['R'].anyOf[1].const === 'y')
})
// ----------------------------------------------------------------
// Modifiers: 1
// ----------------------------------------------------------------
it('Should compute for Modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
const R = T.$defs[T.$ref]
Assert.IsTrue(TypeGuard.IsObject(R))

Assert.IsTrue(TypeGuard.IsNull(R.properties.x))
Assert.IsTrue(TypeGuard.IsReadonly(R.properties.x))
Assert.IsTrue(TypeGuard.IsOptional(R.properties.x))

Assert.IsTrue(TypeGuard.IsNull(R.properties.y))
Assert.IsTrue(TypeGuard.IsReadonly(R.properties.y))
Assert.IsFalse(TypeGuard.IsOptional(R.properties.y))

Assert.IsTrue(TypeGuard.IsNull(R.properties.z))
Assert.IsTrue(TypeGuard.IsOptional(R.properties.z))
Assert.IsFalse(TypeGuard.IsReadonly(R.properties.z))

Assert.IsTrue(TypeGuard.IsNull(R.properties.w))
Assert.IsFalse(TypeGuard.IsOptional(R.properties.w))
Assert.IsFalse(TypeGuard.IsReadonly(R.properties.w))
})
// ----------------------------------------------------------------
// Modifiers: 2
// ----------------------------------------------------------------
it('Should compute for Modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
const R = T.$defs[T.$ref]
Assert.IsTrue(TypeGuard.IsObject(R))

Assert.IsTrue(TypeGuard.IsArray(R.properties.x))
Assert.IsTrue(TypeGuard.IsNull(R.properties.x.items))
Assert.IsTrue(TypeGuard.IsReadonly(R.properties.x))
Assert.IsTrue(TypeGuard.IsOptional(R.properties.x))

Assert.IsTrue(TypeGuard.IsArray(R.properties.y))
Assert.IsTrue(TypeGuard.IsNull(R.properties.y.items))
Assert.IsTrue(TypeGuard.IsReadonly(R.properties.y))
Assert.IsFalse(TypeGuard.IsOptional(R.properties.y))

Assert.IsTrue(TypeGuard.IsArray(R.properties.z))
Assert.IsTrue(TypeGuard.IsNull(R.properties.z.items))
Assert.IsTrue(TypeGuard.IsOptional(R.properties.z))
Assert.IsFalse(TypeGuard.IsReadonly(R.properties.z))

Assert.IsTrue(TypeGuard.IsArray(R.properties.w))
Assert.IsTrue(TypeGuard.IsNull(R.properties.w.items))
Assert.IsFalse(TypeGuard.IsOptional(R.properties.w))
Assert.IsFalse(TypeGuard.IsReadonly(R.properties.w))
})
// ----------------------------------------------------------------
// Modifiers: 3
// ----------------------------------------------------------------
it('Should compute for Modifiers 3', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.Array(Type.Null()),
}),
// Computed Partial
U: Type.Partial(Type.Ref('T')),
})
const T = Module.Import('U')
const R = T.$defs[T.$ref]
Assert.IsTrue(TypeGuard.IsObject(R))

Assert.IsTrue(TypeGuard.IsArray(R.properties.x))
Assert.IsTrue(TypeGuard.IsNull(R.properties.x.items))
Assert.IsTrue(TypeGuard.IsOptional(R.properties.x))
})
})
31 changes: 31 additions & 0 deletions test/runtime/value/check/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,35 @@ describe('value/check/Module', () => {
Assert.IsTrue(Value.Check(T, 'hello'))
Assert.IsFalse(Value.Check(T, 'world'))
})
// ----------------------------------------------------------------
// Modifiers
// ----------------------------------------------------------------
it('Should validate objects with property modifiers 1', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Null()),
y: Type.Readonly(Type.Null()),
z: Type.Optional(Type.Null()),
w: Type.Null(),
}),
})
const T = Module.Import('T')
Assert.IsTrue(Value.Check(T, { x: null, y: null, w: null }))
Assert.IsTrue(Value.Check(T, { y: null, w: null }))
Assert.IsFalse(Value.Check(T, { x: 1, y: null, w: null }))
})
it('Should validate objects with property modifiers 2', () => {
const Module = Type.Module({
T: Type.Object({
x: Type.ReadonlyOptional(Type.Array(Type.Null())),
y: Type.Readonly(Type.Array(Type.Null())),
z: Type.Optional(Type.Array(Type.Null())),
w: Type.Array(Type.Null()),
}),
})
const T = Module.Import('T')
Assert.IsTrue(Value.Check(T, { x: [null], y: [null], w: [null] }))
Assert.IsTrue(Value.Check(T, { y: [null], w: [null] }))
Assert.IsFalse(Value.Check(T, { x: [1], y: [null], w: [null] }))
})
})
Loading

0 comments on commit 81f0a28

Please sign in to comment.