sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.85k stars 155 forks source link

Coerce inside TypeCompiler #347

Closed boehs closed 1 year ago

boehs commented 1 year ago

Hi, I have an issue similar to #335, except I'm using TypeCompiler, which does not support casting/coercion as far as I'm aware. I understand if this is out of scope and I should just use Ajv, but it would be a killer feature 😁

sinclairzx81 commented 1 year ago

@boehs Hi!

There's a new feature releasing later this week that will help provide support for value coercion. It's important to note that this functionality can't really be added to the TypeCompiler (as TypeCompiler must treat the checked value as immutable). Because of this, this functionality will be offered by way of a new Convert function added Value.* that will convert a clone of the original value.

Example

The following creates a compiler Parse function that will automatically coerce and check values.

import { Type, Static, TSchema } from '@sinclair/typebox'
import { TypeCompiler } from '@sinclair/typebox/compiler'
import { Value } from '@sinclair/typebox/value'

// ---------------------------------------------------------------------------
// CompileParse: Compiles a schema for parsing & automatic value coercion
// ---------------------------------------------------------------------------
function CompileParse<T extends TSchema>(schema: T) {

  const check = TypeCompiler.Compile(schema)

  return (input: unknown): Static<T> => {        // rule! we must treat 'input' as immutable.

    const convert = Value.Convert(schema, input) // 1) value conversion here, return type is 'unknown'

    const checked = check.Check(convert)         // 2) check the converted value

    if(checked) return convert                   // 3) if checked ok, return converted

    const { path, message } = check.Errors(input).First()!

    throw Error(`${path} ${message}`)            // 4) if check failed, throw error for input type.
  }
}

// ---------------------------------------------------------------------------
// Example
// ---------------------------------------------------------------------------

const QueryString = Type.Object({ a: Type.Number(), b: Type.Number() })

const parse = CompileParse(QueryString)

const output = parse({ a: '42', b: '3.14' })     // const output = { a: 42, b: 3.14 }

In contrast to Ajv's coerceTypes, this approach is a bit more inline with TypeBox's preference for providing distinct (functional pure) operations that perform some action on a value. In the case of value coercion, the thinking here is that coercion usually only makes sense for a few cases (like when receiving a querystring or headers with numerics) but not for asserting a body. This setup should provide implementers the most flexibility to define exactly how they want assert / check / parse a given value, and offer ways to create custom assertion pipelines specific to an application.

There are also some considerations around using the new Convert function to bolster options for value Encode/Decode in later revisions.

Provisional Release Notes for version 0.26.0 can be found on the PR located https://github.com/sinclairzx81/typebox/pull/346

Hope this helps! S

boehs commented 1 year ago

Thank you so much! I'll keep this issue open until 0.26 hits

sinclairzx81 commented 1 year ago

@boehs New revision has been published on @sinclair/typebox@0.26.0.

This is a BIG revision for the project, let me know how you get on.

Cheers S

boehs commented 1 year ago

Thank you for all your work! I'll try it out and report back

sinclairzx81 commented 1 year ago

@boehs Hey, might close off this issue for now as the new Value.Convert() function should be ready on 0.26.0. If you're interested though, there's an additional discussion happening on https://github.com/sinclairzx81/typebox/issues/343 which talks about future enhancements to the Value.Convert() module to include some form of programmable Encode/Decode.

I do actually think there's some cross over between value coercion and general codecs, so would be good to get your inputs on what the design on of Value.Convert() should be (TypeBox integrators have good insights into requirements here). I'll be looking deeper at value coercion over the course of 0.26.0, so all insights welcome.

Btw, hono looks awesome! :)

All the best! S