Skip to content

Commit c63b553

Browse files
committed
Clean up Compiler and Schema Validators
1 parent 8b398a3 commit c63b553

7 files changed

Lines changed: 101 additions & 154 deletions

File tree

example/index.ts

Lines changed: 2 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -6,65 +6,6 @@ import Schema from 'typebox/schema'
66
import Value from 'typebox/value'
77
import Type from 'typebox'
88

9-
// ------------------------------------------------------------------
10-
// Settings
11-
// ------------------------------------------------------------------
9+
const X = Schema.Compile(Type.String())
1210

13-
System.Settings.Set({ enumerableKind: false })
14-
15-
// ------------------------------------------------------------------
16-
// Guard
17-
// ------------------------------------------------------------------
18-
19-
const A = Guard.GraphemeCount('type-📦') // 6
20-
const B = Guard.HasPropertyKey({ x: 1 }, 'x') // true
21-
22-
// ------------------------------------------------------------------
23-
// Type
24-
// ------------------------------------------------------------------
25-
26-
const T = Type.Object({
27-
x: Type.Number(),
28-
y: Type.Number(),
29-
z: Type.Number()
30-
})
31-
32-
// ------------------------------------------------------------------
33-
// Script
34-
// ------------------------------------------------------------------
35-
36-
const S = Type.Script({ T }, `{
37-
[K in keyof T]: T[K] | null
38-
}`)
39-
40-
// ------------------------------------------------------------------
41-
// Infer
42-
// ------------------------------------------------------------------
43-
44-
type T = Type.Static<typeof T>
45-
type S = Type.Static<typeof S>
46-
47-
// ------------------------------------------------------------------
48-
// Parse
49-
// ------------------------------------------------------------------
50-
51-
const R = Value.Parse(T, { x: 1, y: 2, z: 3 })
52-
53-
// ------------------------------------------------------------------
54-
// Compile
55-
// ------------------------------------------------------------------
56-
const C = Compile(S)
57-
58-
const X = C.Parse({ x: 1, y: 2, z: 3 })
59-
60-
// ------------------------------------------------------------------
61-
// Format
62-
// ------------------------------------------------------------------
63-
64-
const E = Format.IsEmail('user@domain.com')
65-
66-
// ------------------------------------------------------------------
67-
// Schema
68-
// ------------------------------------------------------------------
69-
70-
const D = Schema.Parse({ const: 'hello' }, 'hello')
11+
console.log(X)

src/compile/code.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ function FunctionSection(build: BuildResult): string[] {
7676
// ------------------------------------------------------------------
7777
function ExportSection(build: BuildResult): string[] {
7878
const body = build.UseUnevaluated()
79-
? `const context = new CheckContext({}, {}); return ${build.Call()}`
80-
: `return ${build.Call()}`
79+
? `const context = new CheckContext({}, {}); return ${build.Entry()}`
80+
: `return ${build.Entry()}`
8181
return [
8282
Separator(),
8383
TsIgnore(),

src/compile/validator.ts

Lines changed: 51 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@ THE SOFTWARE.
3030

3131
import { Settings } from '../system/settings/index.ts'
3232
import { Arguments } from '../system/arguments/index.ts'
33-
import { Environment } from '../system/environment/index.ts'
3433
import { type TLocalizedValidationError } from '../error/index.ts'
3534
import { type StaticDecode, type StaticEncode, type TProperties, type TSchema, Base } from '../type/index.ts'
3635
import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser, ParseError } from '../value/index.ts'
37-
import { Build } from '../schema/index.ts'
36+
import { Build, BuildResult, EvaluateResult } from '../schema/index.ts'
3837

3938
// ------------------------------------------------------------------
4039
// Validator<...>
@@ -43,121 +42,117 @@ export class Validator<Context extends TProperties = TProperties, Type extends T
4342
Encode extends unknown = StaticEncode<Type, Context>,
4443
Decode extends unknown = StaticDecode<Type, Context>
4544
> extends Base<Encode> {
46-
private readonly context: Context
47-
private readonly type: Type
48-
private readonly isAccelerated: boolean
4945
private readonly hasCodec: boolean
50-
private readonly code: string
51-
private readonly check: (value: unknown) => boolean
46+
private readonly buildResult: BuildResult
47+
private readonly evaluateResult: EvaluateResult
5248
/** Constructs a Validator with the given Context and Type. */
5349
constructor(context: Context, type: Type)
5450
/** Constructs a Validator with the given arguments. */
55-
constructor(context: Context, type: Type, isEvaluated: boolean, hasCodec: boolean, code: string, check: (value: unknown) => boolean)
51+
constructor(hasCodec: boolean, buildResult: BuildResult, evaluateResult: EvaluateResult)
5652
/** Constructs a Validator. */
5753
constructor(...args: unknown[]) {
5854
super()
59-
const matched: [Context, Type, boolean, boolean, string, (value: unknown) => boolean] | [Context, Type] = Arguments.Match(args, {
60-
6: (context, type, isEvalulated, hasCodec, code, check) => [context, type, isEvalulated, hasCodec, code, check],
55+
const matched: [boolean, BuildResult, EvaluateResult] | [Context, Type] = Arguments.Match(args, {
56+
3: (hasCodec, buildResult, evaluateResult) => [hasCodec, buildResult, evaluateResult],
6157
2: (context, type) => [context, type]
6258
})
63-
if(matched.length === 6) {
64-
const [context, type, isEvaluated, hasCodec, code, check] = matched
65-
this.context = context
66-
this.type = type
67-
this.isAccelerated = isEvaluated
59+
// Note: The Base type requires this Validator to be Clone, but where we cannot safely clone
60+
// the BuildResult or the EvaluateResult. For now we need pass the Validator constructor a
61+
// cloned instance of BuildResult and EvaluateResult such that the Validator clone shares
62+
// the same pre-compiled fields. We should remove this overload when Base is removed.
63+
if(matched.length === 3 && matched[1] instanceof BuildResult && matched[2] instanceof EvaluateResult) {
64+
const [hasCodec, buildResult, evaluateResult] = matched
6865
this.hasCodec = hasCodec
69-
this.code = code
70-
this.check = check
66+
this.buildResult = buildResult
67+
this.evaluateResult = evaluateResult
7168
} else {
7269
const [context, type] = matched as [Context, Type]
73-
const result = Build(context, type).Evaluate()
7470
this.hasCodec = HasCodec(context, type)
75-
this.context = context
76-
this.type = type
77-
this.isAccelerated = result.IsAccelerated
78-
this.code = result.Code
79-
this.check = result.Check as never
71+
this.buildResult = Build(context, type)
72+
this.evaluateResult = this.buildResult.Evaluate()
8073
}
8174
}
8275
// ----------------------------------------------------------------
8376
// IsAccelerated
8477
// ----------------------------------------------------------------
8578
/** Returns true if this Validator is using JIT acceleration. */
8679
public IsAccelerated(): boolean {
87-
return this.isAccelerated
80+
return this.evaluateResult.IsAccelerated()
8881
}
8982
// ----------------------------------------------------------------
90-
// Context | Type
83+
// Context & Type
9184
// ----------------------------------------------------------------
9285
/** Returns the Context for this validator. */
9386
public Context(): Context {
94-
return this.context
87+
return this.buildResult.Context() as never
9588
}
9689
/** Returns the underlying Type used to construct this Validator. */
9790
public Type(): Type {
98-
return this.type
91+
return this.buildResult.Schema() as never
9992
}
10093
// ----------------------------------------------------------------
10194
// Code
10295
// ----------------------------------------------------------------
10396
/** Returns the generated code for this validator. */
10497
public Code(): string {
105-
return this.code
98+
return this.evaluateResult.Code()
10699
}
107100
// ----------------------------------------------------------------
108-
// Base<...>
101+
// Standard Validator
109102
// ----------------------------------------------------------------
110103
/** Performs a type-guard check on the provided value. */
111104
public override Check(value: unknown): value is Encode {
112-
return this.check(value)
105+
return this.evaluateResult.Check(value)
106+
}
107+
/** Validates a value and returns it. Will throw if invalid. */
108+
public Parse(value: unknown): Encode {
109+
const checked = this.Check(value)
110+
if(checked) return value as never
111+
if(Settings.Get().correctiveParse) return Parser(this.Context(), this.Type(), value) as never
112+
throw new ParseError(value, this.Errors(value))
113113
}
114114
/** Inspects a value and returns a detailed list of validation errors. */
115115
public override Errors(value: unknown): TLocalizedValidationError[] {
116-
if (Environment.CanEvaluate() && this.check(value)) return []
117-
return Errors(this.context, this.type, value)
116+
if (this.IsAccelerated() && this.Check(value)) return []
117+
return Errors(this.Context(), this.Type(), value)
118118
}
119+
// ----------------------------------------------------------------
120+
// Value.* Operations
121+
// ----------------------------------------------------------------
119122
/** Cleans a value using the Validator type. */
120123
public override Clean(value: unknown): unknown {
121-
return Clean(this.context, this.type, value)
124+
return Clean(this.Context(), this.Type(), value)
122125
}
123126
/** Converts a value using the Validator type. */
124127
public override Convert(value: unknown): unknown {
125-
return Convert(this.context, this.type, value)
128+
return Convert(this.Context(), this.Type(), value)
126129
}
127130
/** Creates a value using the Validator type. */
128131
public override Create(): Encode {
129-
return Create(this.context, this.type)
132+
return Create(this.Context(), this.Type())
130133
}
131134
/** Creates defaults using the Validator type. */
132135
public override Default(value: unknown): unknown {
133-
return Default(this.context, this.type, value)
134-
}
135-
/** Clones this validator. */
136-
public override Clone(): Validator<Context, Type> {
137-
return new Validator<Context, Type>(
138-
this.context,
139-
this.type,
140-
this.isAccelerated,
141-
this.hasCodec,
142-
this.code,
143-
this.check
144-
)
145-
}
146-
/** Validates a value and returns it. Will throw if invalid. */
147-
public Parse(value: unknown): Encode {
148-
const checked = this.Check(value)
149-
if(checked) return value as never
150-
if(Settings.Get().correctiveParse) return Parser(this.context, this.type, value) as never
151-
throw new ParseError(value, this.Errors(value))
136+
return Default(this.Context(), this.Type(), value)
152137
}
153138
/** Decodes a value */
154139
public Decode(value: unknown): Decode {
155-
const result = this.hasCodec ? Decode(this.context, this.type, value) : this.Parse(value)
140+
const result = this.hasCodec ? Decode(this.Context(), this.Type(), value) : this.Parse(value)
156141
return result as never
157142
}
158143
/** Encodes a value */
159144
public Encode(value: unknown): Encode {
160-
const result = this.hasCodec ? Encode(this.context, this.type, value) : this.Parse(value)
145+
const result = this.hasCodec ? Encode(this.Context(), this.Type(), value) : this.Parse(value)
161146
return result as never
162147
}
148+
// ----------------------------------------------------------------
149+
// Deprecations
150+
// ----------------------------------------------------------------
151+
/**
152+
* @deprecated Validator instances should not support Clone because they are owners of JIT evaluated functions. This function will be
153+
* removed in the next version of TypeBox (relates to Type.Base deprecation)
154+
*/
155+
public override Clone(): Validator<Context, Type> {
156+
return new Validator<Context, Type>(this.hasCodec, this.buildResult, this.evaluateResult)
157+
}
163158
}

src/schema/build.ts

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ import * as Schema from './types/index.ts'
4444
function CreateCode(build: BuildResult): string {
4545
const functions = build.Functions().join(';\n')
4646
const statements = build.UseUnevaluated()
47-
? ['const context = new CheckContext({}, {})', `return ${build.Call()}`]
48-
: [`return ${build.Call()}`]
47+
? ['const context = new CheckContext({}, {})', `return ${build.Entry()}`]
48+
: [`return ${build.Entry()}`]
4949
return `${functions}; return (value) => { ${statements.join('; ')} }`
5050
}
5151
// ------------------------------------------------------------------
@@ -79,10 +79,21 @@ export type CheckFunction = (value: unknown) => boolean
7979
// ------------------------------------------------------------------
8080
// EvaluateResult
8181
// ------------------------------------------------------------------
82-
export interface EvaluateResult {
83-
IsAccelerated: boolean
84-
Code: string
85-
Check: CheckFunction
82+
export class EvaluateResult {
83+
constructor(
84+
private readonly isAccelerated: boolean,
85+
private readonly code: string,
86+
private readonly check: CheckFunction
87+
) {}
88+
public IsAccelerated() {
89+
return this.isAccelerated
90+
}
91+
public Code(): string {
92+
return this.code
93+
}
94+
public Check(value: unknown): boolean {
95+
return this.check(value)
96+
}
8697
}
8798
// ------------------------------------------------------------------
8899
// BuildResult
@@ -93,7 +104,7 @@ export class BuildResult {
93104
private readonly schema: Schema.XSchema,
94105
private readonly external: Engine.TExternal,
95106
private readonly functions: string[],
96-
private readonly call: string,
107+
private readonly entry: string,
97108
private readonly useUnevaluated: boolean
98109
) { }
99110
/** Returns the Context used for this build */
@@ -117,14 +128,14 @@ export class BuildResult {
117128
return this.functions
118129
}
119130
/** Return entry function call. */
120-
public Call(): string {
121-
return this.call
131+
public Entry(): string {
132+
return this.entry
122133
}
123134
/** Evaluates the build into a validation function */
124135
public Evaluate(): EvaluateResult {
125-
const Code = CreateCode(this)
126-
const Check = CreateCheck(this, Code)
127-
return { IsAccelerated: Environment.CanEvaluate(), Code, Check }
136+
const code = CreateCode(this)
137+
const check = CreateCheck(this, code)
138+
return new EvaluateResult(Environment.CanEvaluate(), code, check)
128139
}
129140
}
130141
// ------------------------------------------------------------------

src/schema/compile.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,33 +43,33 @@ import { ParseError } from './parse.ts'
4343
export class Validator<const Schema extends Schema.XSchema = Schema.XSchema,
4444
Value extends unknown = Static<Schema>
4545
> {
46-
readonly #buildResult: Build.BuildResult
47-
readonly #evaluateResult: Build.EvaluateResult
46+
private readonly buildResult: Build.BuildResult
47+
private readonly evaluateResult: Build.EvaluateResult
4848
constructor(context: Record<string, Schema.XSchema>, schema: Schema) {
49-
this.#buildResult = Build.Build(context, schema)
50-
this.#evaluateResult = this.#buildResult.Evaluate()
49+
this.buildResult = Build.Build(context, schema)
50+
this.evaluateResult = this.buildResult.Evaluate()
5151
}
5252
/** Returns true if this Validator is using JIT acceleration. */
5353
public IsAccelerated(): boolean {
54-
return this.#evaluateResult.IsAccelerated
54+
return this.evaluateResult.IsAccelerated()
5555
}
5656
/** Returns the underlying Schema used to construct this Validator. */
5757
public Schema(): Schema {
58-
return this.#buildResult.Schema() as never
58+
return this.buildResult.Schema() as never
5959
}
6060
/** Performs a type-guard check on the provided value. */
6161
public Check(value: unknown): value is Value {
62-
return this.#evaluateResult.Check(value)
62+
return this.evaluateResult.Check(value)
6363
}
6464
/** Validates a value and returns it. Will throw if invalid. */
6565
public Parse(value: unknown): Value {
66-
if(this.#evaluateResult.Check(value)) return value as never
67-
const [_result, errors] = Errors(this.#buildResult.Context(), this.#buildResult.Schema(), value)
68-
throw new ParseError(this.#buildResult.Schema(), value, errors)
66+
if(this.evaluateResult.Check(value)) return value as never
67+
const [_result, errors] = Errors(this.buildResult.Context(), this.buildResult.Schema(), value)
68+
throw new ParseError(this.buildResult.Schema(), value, errors)
6969
}
7070
/** Inspects a value and returns a detailed list of validation errors. */
7171
public Errors(value: unknown): [result: boolean, errors: TLocalizedValidationError[]] {
72-
return Errors(this.#buildResult.Context(), this.#buildResult.Schema(), value)
72+
return Errors(this.buildResult.Context(), this.buildResult.Schema(), value)
7373
}
7474
}
7575
// ------------------------------------------------------------------

test/typebox/runtime/schema/additional.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ const Test = Assert.Context('Schema.Additional')
1313
// When unevaluatedItems is present, prefixItems should register their indices.
1414
// This behavior is used for build-time optimization only.
1515
Test('Should Coverage 1', () => {
16-
const check = Schema.Build({
16+
const evaluateResult = Schema.Build({
1717
prefixItems: [{ const: 1 }, { const: 2 }],
1818
unevaluatedItems: false
19-
}).Evaluate().Check
19+
}).Evaluate()
2020

21-
Assert.IsTrue(check([1, 2]))
22-
Assert.IsFalse(check([1, 2, 3]))
21+
Assert.IsTrue(evaluateResult.Check([1, 2]))
22+
Assert.IsFalse(evaluateResult.Check([1, 2, 3]))
2323
})
2424

2525
// Test 2: Ensures unknown type values are handled correctly.

0 commit comments

Comments
 (0)