diff --git a/readme.md b/readme.md
index 4004ece9f..74b975ccb 100644
--- a/readme.md
+++ b/readme.md
@@ -1325,27 +1325,26 @@ const B = Value.Encode(Type.String(), 42) // throw
### Parse
-Use the Parse function to parse a value or throw if invalid. This function internally uses Default, Clean, Convert and Decode to make a best effort attempt to parse the value into the expected type. This function should not be used in performance critical code paths.
+Use the Parse function to process a value. The Parse function will call a specific sequence of Value functions (operations) to process the value.
```typescript
-const T = Type.Object({ x: Type.Number({ default: 0 }), y: Type.Number({ default: 0 }) })
+const R = Value.Parse(Type.String(), 'hello') // const R: string = "hello"
-// Default
-
-const A = Value.Parse(T, { }) // const A = { x: 0, y: 0 }
-
-// Convert
+const E = Value.Parse(Type.String(), undefined) // throws AssertError
+```
-const B = Value.Parse(T, { x: '1', y: '2' }) // const B = { x: 1, y: 2 }
+The specific sequence of Value operations is `Clone` `Clean`, `Default`, `Convert`, `Assert` and `Decode`. You can specify user defined sequences in the following way.
-// Clean
+```typescript
+// Same as calling the Assert() function
-const C = Value.Parse(T, { x: 1, y: 2, z: 3 }) // const C = { x: 1, y: 2 }
+const E = Value.Parse(['Assert'], Type.String(), 12345)
-// Assert
+// Same as calling the Convert() then Assert() functions.
-const D = Value.Parse(T, undefined) // throws AssertError
+const S = Value.Parse(['Convert', 'Assert'], Type.String(), 12345)
```
+When specifying user defined sequences, the return type of Parse will be unknown.
diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts
index 6e438702b..7450c8aab 100644
--- a/src/compiler/compiler.ts
+++ b/src/compiler/compiler.ts
@@ -97,6 +97,14 @@ export class TypeCheck {
public Code(): string {
return this.code
}
+ /** Returns the schema type used to validate */
+ public Schema(): T {
+ return this.schema
+ }
+ /** Returns reference types used to validate */
+ public References(): TSchema[] {
+ return this.references
+ }
/** Returns an iterator for each error in this value. */
public Errors(value: unknown): ValueErrorIterator {
return Errors(this.schema, this.references, value)
diff --git a/src/value/parse/parse.ts b/src/value/parse/parse.ts
index 13477a4c9..3c9aea0a9 100644
--- a/src/value/parse/parse.ts
+++ b/src/value/parse/parse.ts
@@ -26,7 +26,8 @@ THE SOFTWARE.
---------------------------------------------------------------------------*/
-import { TransformDecode, HasTransform } from '../transform/index'
+import { TypeBoxError } from '../../type/error/index'
+import { TransformDecode, TransformEncode, HasTransform } from '../transform/index'
import { TSchema } from '../../type/schema/index'
import { StaticDecode } from '../../type/static/index'
import { Assert } from '../assert/assert'
@@ -36,33 +37,92 @@ import { Clean } from '../clean/clean'
import { Clone } from '../clone/index'
// ------------------------------------------------------------------
-// ParseReducer
+// Guards
// ------------------------------------------------------------------
-type ReducerFunction = (schema: TSchema, references: TSchema[], value: unknown) => unknown
+import { IsArray, IsUndefined } from '../guard/index'
+
+// ------------------------------------------------------------------
+// Error
+// ------------------------------------------------------------------
+export class ParseError extends TypeBoxError {
+ constructor(message: string) {
+ super(message)
+ }
+}
+
+// ------------------------------------------------------------------
+// ParseRegistry
+// ------------------------------------------------------------------
+export type TParseOperation = 'Clone' | 'Clean' | 'Default' | 'Convert' | 'Assert' | 'Decode' | ({} & string)
+export type TParseFunction = (type: TSchema, references: TSchema[], value: unknown) => unknown
// prettier-ignore
-const ParseReducer: ReducerFunction[] = [
- (_schema, _references, value) => Clone(value),
- (schema, references, value) => Default(schema, references, value),
- (schema, references, value) => Clean(schema, references, value),
- (schema, references, value) => Convert(schema, references, value),
- (schema, references, value) => { Assert(schema, references, value); return value },
- (schema, references, value) => (HasTransform(schema, references) ? TransformDecode(schema, references, value) : value),
-]
+export namespace ParseRegistry {
+ const registry = new Map([
+ ['Clone', (_type, _references, value: unknown) => Clone(value)],
+ ['Clean', (type, references, value: unknown) => Clean(type, references, value)],
+ ['Default', (type, references, value: unknown) => Default(type, references, value)],
+ ['Convert', (type, references, value: unknown) => Convert(type, references, value)],
+ ['Assert', (type, references, value: unknown) => { Assert(type, references, value); return value }],
+ ['Decode', (type, references, value: unknown) => (HasTransform(type, references) ? TransformDecode(type, references, value) : value)],
+ ['Encode', (type, references, value: unknown) => (HasTransform(type, references) ? TransformEncode(type, references, value) : value)],
+ ])
+ // Deletes an entry from the registry
+ export function Delete(key: string): void {
+ registry.delete(key)
+ }
+ // Sets an entry in the registry
+ export function Set(key: string, callback: TParseFunction): void {
+ registry.set(key, callback)
+ }
+ // Gets an entry in the registry
+ export function Get(key: string): TParseFunction | undefined {
+ return registry.get(key)
+ }
+}
+// ------------------------------------------------------------------
+// ParseDefault: Default Sequence
+// ------------------------------------------------------------------
+// prettier-ignore
+export const ParseDefault = [
+ 'Clone',
+ 'Clean',
+ 'Default',
+ 'Convert',
+ 'Assert',
+ 'Decode'
+] as const
+
// ------------------------------------------------------------------
// ParseValue
// ------------------------------------------------------------------
-function ParseValue>(schema: T, references: TSchema[], value: unknown): R {
- return ParseReducer.reduce((value, reducer) => reducer(schema, references, value), value) as R
+function ParseValue = StaticDecode>(keys: TParseOperation[], type: Type, references: TSchema[], value: unknown): Result {
+ return keys.reduce((value, key) => {
+ const operation = ParseRegistry.Get(key)
+ if (IsUndefined(operation)) throw new ParseError(`Unable to find Parse operation '${key}'`)
+ return operation(type, references, value)
+ }, value) as Result
}
+
// ------------------------------------------------------------------
// Parse
// ------------------------------------------------------------------
-/** Parses a value or throws an `AssertError` if invalid. */
-export function Parse>(schema: T, references: TSchema[], value: unknown): R
-/** Parses a value or throws an `AssertError` if invalid. */
-export function Parse>(schema: T, value: unknown): R
-/** Parses a value or throws an `AssertError` if invalid. */
+/** Parses a value using the default parse pipeline. Will throws an `AssertError` if invalid. */
+export function Parse, Result extends Output = Output>(schema: Type, references: TSchema[], value: unknown): Result
+/** Parses a value using the default parse pipeline. Will throws an `AssertError` if invalid. */
+export function Parse, Result extends Output = Output>(schema: Type, value: unknown): Result
+/** Parses a value using the specified operations. */
+export function Parse(operations: TParseOperation[], schema: Type, references: TSchema[], value: unknown): unknown
+/** Parses a value using the specified operations. */
+export function Parse(operations: TParseOperation[], schema: Type, value: unknown): unknown
+/** Parses a value */
export function Parse(...args: any[]): unknown {
- return args.length === 3 ? ParseValue(args[0], args[1], args[2]) : ParseValue(args[0], [], args[1])
+ // prettier-ignore
+ const [reducers, schema, references, value] = (
+ args.length === 4 ? [args[0], args[1], args[2], args[3]] :
+ args.length === 3 ? IsArray(args[0]) ? [args[0], args[1], [], args[2]] : [ParseDefault, args[0], args[1], args[2]] :
+ args.length === 2 ? [ParseDefault, args[0], [], args[1]] :
+ (() => { throw new ParseError('Invalid Arguments') })()
+ )
+ return ParseValue(reducers, schema, references, value)
}
diff --git a/test/runtime/compiler/__members.ts b/test/runtime/compiler/__members.ts
new file mode 100644
index 000000000..6ade7f40b
--- /dev/null
+++ b/test/runtime/compiler/__members.ts
@@ -0,0 +1,20 @@
+import { TypeCompiler } from '@sinclair/typebox/compiler'
+import { Type, TypeGuard, ValueGuard } from '@sinclair/typebox'
+import { Assert } from '../assert/index'
+
+describe('compiler/TypeCheckMembers', () => {
+ it('Should return Schema', () => {
+ const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
+ Assert.IsTrue(TypeGuard.IsNumber(A.Schema()))
+ })
+ it('Should return References', () => {
+ const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
+ Assert.IsTrue(TypeGuard.IsNumber(A.Schema()))
+ Assert.IsTrue(TypeGuard.IsString(A.References()[0]))
+ Assert.IsTrue(TypeGuard.IsBoolean(A.References()[1]))
+ })
+ it('Should return Code', () => {
+ const A = TypeCompiler.Compile(Type.Number(), [Type.String(), Type.Boolean()])
+ Assert.IsTrue(ValueGuard.IsString(A.Code()))
+ })
+})
diff --git a/test/runtime/compiler/index.ts b/test/runtime/compiler/index.ts
index 21decd7fa..a39e51760 100644
--- a/test/runtime/compiler/index.ts
+++ b/test/runtime/compiler/index.ts
@@ -1,3 +1,4 @@
+import './__members'
import './any'
import './array'
import './async-iterator'
diff --git a/test/runtime/value/parse/parse.ts b/test/runtime/value/parse/parse.ts
index 5c8be9dd7..2bf7106de 100644
--- a/test/runtime/value/parse/parse.ts
+++ b/test/runtime/value/parse/parse.ts
@@ -1,5 +1,5 @@
-import { Value, AssertError } from '@sinclair/typebox/value'
-import { Type } from '@sinclair/typebox'
+import { Value, AssertError, ParseRegistry } from '@sinclair/typebox/value'
+import { Type, TypeGuard } from '@sinclair/typebox'
import { Assert } from '../../assert/index'
// prettier-ignore
@@ -87,4 +87,39 @@ describe('value/Parse', () => {
const X = Value.Parse(T, 'world')
Assert.IsEqual(X, 'hello')
})
+ // ----------------------------------------------------------------
+ // Operations
+ // ----------------------------------------------------------------
+ it('Should run operations 1', () => {
+ const A = Type.Object({ x: Type.Number() })
+ const I = { x: 1 }
+ const O = Value.Parse([], A, I)
+ Assert.IsTrue(I === O)
+ })
+ it('Should run operations 2', () => {
+ const A = Type.Object({ x: Type.Number() })
+ const I = { x: 1 }
+ const O = Value.Parse(['Clone'], A, I)
+ Assert.IsTrue(I !== O)
+ })
+ it('Should run operations 3', () => {
+ ParseRegistry.Set('Intercept', ( schema, references, value) => { throw 1 })
+ const A = Type.Object({ x: Type.Number() })
+ Assert.Throws(() => Value.Parse(['Intercept'], A, null))
+ ParseRegistry.Delete('Intercept')
+ const F = ParseRegistry.Get('Intercept')
+ Assert.IsEqual(F, undefined)
+ })
+ it('Should run operations 4', () => {
+ ParseRegistry.Set('Intercept', ( schema, references, value) => {
+ Assert.IsEqual(value, 12345)
+ Assert.IsTrue(TypeGuard.IsNumber(schema))
+ Assert.IsTrue(TypeGuard.IsString(references[0]))
+ })
+ Value.Parse(['Intercept'], Type.Number(), [Type.String()], 12345)
+ ParseRegistry.Delete('Intercept')
+ })
+ it('Should run operations 5', () => {
+ Assert.Throws(() => Value.Parse(['Intercept'], Type.String(), null))
+ })
})