Skip to content

Commit 6849407

Browse files
authored
Version 1.1.0 (#1532)
- Corrective Parse - Documentation
1 parent 672ff3f commit 6849407

11 files changed

Lines changed: 275 additions & 59 deletions

File tree

changelog/1.1.0.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# 1.1.0
2+
3+
## Overview
4+
5+
Version 1.1.0 introduces a behavioral change to `Value.Parse(...)` and `Validator.Parse(...)` where automatic corrective parsing is now disabled by default. This change addresses performance overhead when handling invalid data and prepares the `Value.*` API for future revisions.
6+
7+
This update introduces a validation semantics change and is published under a minor semver version.
8+
9+
## Corrective Parse
10+
11+
In 1.0, a feature referred to as “Corrective Parse” was trialed. This feature meant that for any invalid value passed to Parse, TypeBox would attempt to Convert, Default, and Clean the value, then re-Check it before throwing an assertion. The feature was introduced in 1.0 in an effort to establish a de facto parsing pipeline for TypeBox, but it has since been observed that it incurs significant performance overhead for large invalid values. Additionally, the correction behavior has generally proven surprising to users, and is therefore better expressed as an opt-in feature.
12+
13+
The following shows the changes from 1.0 to 1.1
14+
15+
```typescript
16+
// 1.0
17+
const A = Value.Parse(Type.Number(), '123') // A = 123
18+
19+
// 1.1
20+
const A = Value.Parse(Type.Number(), '123') // throw! - Expected Number
21+
```
22+
23+
Corrective parsing can be re-enabled by using the `{ correctiveParse: true }` configuration.
24+
25+
```typescript
26+
import { Settings } from 'typebox/system'
27+
28+
// 1.1
29+
Settings.Set({ correctiveParse: true })
30+
31+
const A = Value.Parse(Type.Number(), '123') // A = 123
32+
```

design/website/docs/system/1_settings.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,11 @@ export interface TSettings {
4848
useEval: boolean
4949

5050
/**
51-
* Enables or disables 'exactOptionalPropertyTypes' check semantics. By default, TypeScript
52-
* allows optional properties to be assigned 'undefined'. While this behavior differs from the
53-
* common interpretation of 'optional' as meaning 'key may be absent', TypeBox adopts the default
54-
* TypeScript semantics to remain consistent with the language. This option is provided to align
55-
* runtime check semantics with projects that configure 'exactOptionalPropertyTypes: true' in
51+
* Enables or disables 'exactOptionalPropertyTypes' check semantics. By default, TypeScript
52+
* allows optional properties to be assigned 'undefined'. While this behavior differs from the
53+
* common interpretation of 'optional' as meaning 'key may be absent', TypeBox adopts the default
54+
* TypeScript semantics to remain consistent with the language. This option is provided to align
55+
* runtime check semantics with projects that configure 'exactOptionalPropertyTypes: true' in
5656
* tsconfig.json.
5757
* @default false
5858
*/
@@ -63,5 +63,15 @@ export interface TSettings {
6363
* @default false
6464
*/
6565
enumerableKind: boolean
66+
67+
/**
68+
* Controls whether TypeBox uses corrective Parse. When enabled, TypeBox will attempt to recover invalid
69+
* values during Parse by running a sequence of `Value.*` operations to `Convert`, `Default`, and `Clean`
70+
* the value, followed by a subsequent `Assert`. Enabling this option may incur significant performance
71+
* overhead for invalid values. It is recommended to keep this disabled in performance-sensitive
72+
* applications.
73+
* @default false
74+
*/
75+
correctiveParse: boolean
6676
}
6777
```

design/website/docs/value/parse.md

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
# Parse
22

3-
The Parse function attempts to parse a value and throws an error if the value is invalid. This function is similar to Decode, but it does not execute Decode callbacks. Parse is considered a faster version of Decode, as operations that transform values are skipped when the value already matches the expected type.
4-
5-
> The Parse function first checks a value against the provided type and returns immediately if it matches. If the value does not match, it is processed through a sequence of Clone, Clean, Convert, and Default operations, and then re-checked. If the value remains invalid, a ParseError error is thrown.
3+
The Parse function validates and returns a value that conforms to a given type. If the value does not satisfy the type, a parse error is thrown.
64

75
## Example
86

@@ -11,5 +9,31 @@ Example usage is shown below.
119
```typescript
1210
const R = Value.Parse(Type.String(), 'hello') // const R: string = "hello"
1311

14-
const E = Value.Parse(Type.String(), [{ x: 1 }]) // throws ParseError
12+
const E = Value.Parse(Type.String(), 12345) // throws ParseError
13+
```
14+
15+
## Corrective Parse
16+
17+
TypeBox provides an optional corrective parsing mode that attempts to repair invalid values before failing. When enabled, the parser runs a pipeline consisting of Convert, Default, and Clean, then re-asserts the value after processing. This feature can be useful when parsing environment variables into target types.
18+
19+
> ⚠️ This feature can impact performance. It is not recommended for use in high throughput applications.
20+
21+
This feature can be enabled as follows:
22+
23+
```typescript
24+
import { Settings } from 'typebox/system'
25+
26+
// Corrective Parse: Enable
27+
28+
Settings.Set({ correctiveParse: true })
29+
30+
// Corrective Parse: Convert Value into the target type if reasonable conversion is possible.
31+
32+
const R = Value.Parse(Type.String(), 'hello') // const R: string = "hello"
33+
34+
const S = Value.Parse(Type.String(), 12345) // const S: string = "12345"
35+
36+
// Corrective Parse: Reset (optional)
37+
38+
Settings.Reset()
1539
```

docs/docs/system/1_settings.html

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ <h2>Example</h2>
4040
useEval: boolean
4141

4242
/**
43-
* Enables or disables &#39;exactOptionalPropertyTypes&#39; check semantics. By default, TypeScript
44-
* allows optional properties to be assigned &#39;undefined&#39;. While this behavior differs from the
45-
* common interpretation of &#39;optional&#39; as meaning &#39;key may be absent&#39;, TypeBox adopts the default
46-
* TypeScript semantics to remain consistent with the language. This option is provided to align
47-
* runtime check semantics with projects that configure &#39;exactOptionalPropertyTypes: true&#39; in
43+
* Enables or disables &#39;exactOptionalPropertyTypes&#39; check semantics. By default, TypeScript
44+
* allows optional properties to be assigned &#39;undefined&#39;. While this behavior differs from the
45+
* common interpretation of &#39;optional&#39; as meaning &#39;key may be absent&#39;, TypeBox adopts the default
46+
* TypeScript semantics to remain consistent with the language. This option is provided to align
47+
* runtime check semantics with projects that configure &#39;exactOptionalPropertyTypes: true&#39; in
4848
* tsconfig.json.
4949
* @default false
5050
*/
@@ -55,5 +55,15 @@ <h2>Example</h2>
5555
* @default false
5656
*/
5757
enumerableKind: boolean
58+
59+
/**
60+
* Controls whether TypeBox uses corrective Parse. When enabled, TypeBox will attempt to recover invalid
61+
* values during Parse by running a sequence of `Value.*` operations to `Convert`, `Default`, and `Clean`
62+
* the value, followed by a subsequent `Assert`. Enabling this option may incur significant performance
63+
* overhead for invalid values. It is recommended to keep this disabled in performance-sensitive
64+
* applications.
65+
* @default false
66+
*/
67+
correctiveParse: boolean
5868
}
5969
</code></pre>

docs/docs/value/parse.html

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
<h1>Parse</h1>
2-
<p>The Parse function attempts to parse a value and throws an error if the value is invalid. This function is similar to Decode, but it does not execute Decode callbacks. Parse is considered a faster version of Decode, as operations that transform values are skipped when the value already matches the expected type.</p>
3-
<blockquote>
4-
<p>The Parse function first checks a value against the provided type and returns immediately if it matches. If the value does not match, it is processed through a sequence of Clone, Clean, Convert, and Default operations, and then re-checked. If the value remains invalid, a ParseError error is thrown.</p>
5-
</blockquote>
2+
<p>The Parse function validates and returns a value that conforms to a given type. If the value does not satisfy the type, a parse error is thrown.</p>
63
<h2>Example</h2>
74
<p>Example usage is shown below.</p>
85
<pre><code class="language-typescript">const R = Value.Parse(Type.String(), &#39;hello&#39;) // const R: string = &quot;hello&quot;
96

10-
const E = Value.Parse(Type.String(), [{ x: 1 }]) // throws ParseError
7+
const E = Value.Parse(Type.String(), 12345) // throws ParseError
8+
</code></pre>
9+
<h2>Corrective Parse</h2>
10+
<p>TypeBox provides an optional corrective parsing mode that attempts to repair invalid values before failing. When enabled, the parser runs a pipeline consisting of Convert, Default, and Clean, then re-asserts the value after processing. This feature can be useful when parsing environment variables into target types.</p>
11+
<blockquote>
12+
<p>⚠️ This feature can impact performance. It is not recommended for use in high throughput applications.</p>
13+
</blockquote>
14+
<p>This feature can be enabled as follows:</p>
15+
<pre><code class="language-typescript">import { Settings } from &#39;typebox/system&#39;
16+
17+
// Corrective Parse: Enable
18+
19+
Settings.Set({ correctiveParse: true })
20+
21+
// Corrective Parse: Convert Value into the target type if reasonable conversion is possible.
22+
23+
const R = Value.Parse(Type.String(), &#39;hello&#39;) // const R: string = &quot;hello&quot;
24+
25+
const S = Value.Parse(Type.String(), 12345) // const S: string = &quot;12345&quot;
26+
27+
// Corrective Parse: Reset (optional)
28+
29+
Settings.Reset()
1130
</code></pre>

src/compile/validator.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ THE SOFTWARE.
2828

2929
// deno-fmt-ignore-file
3030

31+
import { Settings } from '../system/settings/index.ts'
3132
import { Arguments } from '../system/arguments/index.ts'
3233
import { Environment } from '../system/environment/index.ts'
3334
import { type TLocalizedValidationError } from '../error/index.ts'
3435
import { type StaticDecode, type StaticEncode, type TProperties, type TSchema, Base } from '../type/index.ts'
35-
import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser } from '../value/index.ts'
36+
import { Errors, Clean, Convert, Create, Default, Decode, Encode, HasCodec, Parser, ParseError } from '../value/index.ts'
3637
import { Build } from '../schema/index.ts'
3738

3839
// ------------------------------------------------------------------
@@ -142,13 +143,12 @@ export class Validator<Context extends TProperties = TProperties, Type extends T
142143
this.check
143144
)
144145
}
145-
// ----------------------------------------------------------------
146-
// Parse | Decode | Encode
147-
// ----------------------------------------------------------------
148146
/** Parses a value */
149147
public Parse(value: unknown): Encode {
150-
const result = this.Check(value) ? value : Parser(this.context, this.type, value)
151-
return result as never
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))
152152
}
153153
/** Decodes a value */
154154
public Decode(value: unknown): Decode {

src/system/settings/settings.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ export interface TSettings {
7676
* @default false
7777
*/
7878
enumerableKind: boolean
79+
80+
/**
81+
* Controls whether TypeBox uses corrective Parse. When enabled, TypeBox will attempt to recover invalid
82+
* values during Parse by running a sequence of `Value.*` operations to `Convert`, `Default`, and `Clean`
83+
* the value, followed by a subsequent `Assert`. Enabling this option may incur significant performance
84+
* overhead for invalid values. It is recommended to keep this disabled in performance-sensitive
85+
* applications.
86+
* @default false
87+
*/
88+
correctiveParse: boolean
7989
}
8090

8191
// Internal mutable state
@@ -84,7 +94,8 @@ const settings: TSettings = {
8494
maxErrors: 8,
8595
useEval: true,
8696
exactOptionalPropertyTypes: false,
87-
enumerableKind: false
97+
enumerableKind: false,
98+
correctiveParse: false
8899
}
89100

90101
/** Resets system settings to defaults */
@@ -94,6 +105,7 @@ export function Reset(): void {
94105
settings.useEval = true
95106
settings.exactOptionalPropertyTypes = false
96107
settings.enumerableKind = false
108+
settings.correctiveParse = false
97109
}
98110

99111
/** Sets system settings */

src/value/parse/parse.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ THE SOFTWARE.
2828

2929
// deno-fmt-ignore-file
3030

31+
import { Settings } from '../../system/system.ts'
3132
import { Arguments } from '../../system/arguments/index.ts'
3233
import { type TLocalizedValidationError } from '../../error/errors.ts'
3334
import { type TProperties, type TSchema, type StaticParse } from '../../type/index.ts'
@@ -63,38 +64,24 @@ export const Parser = Pipeline([
6364
(context, type, value) => Clean(context, type, value),
6465
(context, type, value) => Assert(context, type, value)
6566
])
66-
67-
/**
68-
* Parses a value with the given type. The function will Check the value and return
69-
* early if it matches the provided type. If the value does not match, it is processed
70-
* through a sequence of Clone, Default, Convert, and Clean operations and checked again.
71-
* A `ParseError` is thrown if the value fails to match after the processing sequence.
72-
*/
67+
/** Parses a value with the given type. */
7368
export function Parse<const Type extends TSchema,
7469
Result extends unknown = StaticParse<Type>
7570
>(type: Type, value: unknown): Result
7671

77-
/**
78-
* Parses a value with the given type. The function will Check the value and return
79-
* early if it matches the provided type. If the value does not match, it is processed
80-
* through a sequence of Clone, Default, Convert, and Clean operations and checked again.
81-
* A `ParseError` is thrown if the value fails to match after the processing sequence.
82-
*/
72+
/** Parses a value with the given type. */
8373
export function Parse<Context extends TProperties, const Type extends TSchema,
8474
Result extends unknown = StaticParse<Type, Context>
8575
>(context: Context, type: Type, value: unknown): Result
8676

87-
/**
88-
* Parses a value with the given type. The function will Check the value and return
89-
* early if it matches the provided type. If the value does not match, it is processed
90-
* through a sequence of Clone, Default, Convert, and Clean operations and checked again.
91-
* A `ParseError` is thrown if the value fails to match after the processing sequence.
92-
*/
77+
/** Parses a value with the given type. */
9378
export function Parse(...args: unknown[]): never {
9479
const [context, type, value] = Arguments.Match<[TProperties, TSchema, unknown]>(args, {
9580
3: (context, type, value) => [context, type, value],
9681
2: (type, value) => [{}, type, value],
9782
})
98-
const result = Check(context, type, value) ? value : Parser(context, type, value)
99-
return result as never
83+
const checked = Check(context, type, value)
84+
if(checked) return value as never
85+
if(Settings.Get().correctiveParse) return Parser(context, type, value) as never
86+
throw new ParseError(value, Errors(context, type, value))
10087
}

tasks.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Range } from './task/range/index.ts'
88
import { Metrics } from './task/metrics/index.ts'
99
import { Task } from 'tasksmith'
1010

11-
const Version = '1.0.81'
11+
const Version = '1.1.0'
1212

1313
// ------------------------------------------------------------------
1414
// Build

0 commit comments

Comments
 (0)