Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Transformer support for Unsafe types #631

Closed
simedia-fabianamhof opened this issue Oct 16, 2023 · 6 comments
Closed

Better Transformer support for Unsafe types #631

simedia-fabianamhof opened this issue Oct 16, 2023 · 6 comments

Comments

@simedia-fabianamhof
Copy link

Unfortunately unsafe types are not working with Type.Transform.
For example

import { Static, StaticDecode, TSchema, Type } from "@sinclair/typebox";

const Nullable = <T extends TSchema>(schema: T) => Type.Unsafe<Static<T> | null>({ 
  ...schema, nullable: true,
});

const DateType = () => Type.Transform(Type.String({format: "date"})).Decode((value) => new Date(value)).Encode((value) => value.toISOString());
const Test = Nullable(DateType());

type Test = StaticDecode<typeof Test>; //test is string | null instead of Date | null
@simedia-fabianamhof simedia-fabianamhof changed the title Better support for Transformer for Unsafe type Better Transformer support for Unsafe type Oct 16, 2023
@simedia-fabianamhof simedia-fabianamhof changed the title Better Transformer support for Unsafe type Better Transformer support for Unsafe types Oct 16, 2023
@sinclairzx81
Copy link
Owner

@simedia-fabianamhof Hi, Try the following

TypeScript Link Here

const Nullable = <T extends TSchema>(schema: T) => Type.Unsafe<StaticDecode<T> | null>({ 
  ...schema, nullable: true,
});

@simedia-fabianamhof
Copy link
Author

@sinclairzx81 Thanks for the fast answer!
The Problem does still exist in your solution but now in the opposite direction.

As far as I understand the Transform Type it should behave the following way:

type Test = StaticDecode<typeof Test>; //Test should be Date | null
type Test = StaticEncode<typeof Test> //Test should be string | null

@sinclairzx81
Copy link
Owner

@simedia-fabianamhof Hi,

See below for updated types. Would recommend the Union Form.


Unsafe Form

Using Transforms can be a bit non-trivial when using Unsafe types (if you want both encode and decode inference). You can try the following which wraps the unsafe nullable in an outer transform. This enables the inference to correctly resolve the encoded and decoded form.

TypeScript Link Here

import { Static, StaticDecode, StaticEncode, TSchema, Type, FormatRegistry } from "@sinclair/typebox";
import { Value } from '@sinclair/typebox/value'

// Note: You will need to register the 'date' format check for validation to pass. See 
// https://github.com/sinclairzx81/typebox/blob/master/examples/formats/date.ts
FormatRegistry.Set('date', (value) => true) // required

const Nullable = <T extends TSchema>(schema: T) => 
  Type.Transform(Type.Unsafe<Static<T> | null>({ ...schema, nullable: true }))
    .Decode(value => value as StaticDecode<T> | null)
    .Encode(value => value as StaticEncode<T> | null)

const DateType = () => Type.Transform(Type.String({ format: 'date' }))
  .Decode((value) => new Date(value))
  .Encode((value) => value.toISOString())

const T = Nullable(DateType())
type A = StaticDecode<typeof T> // ok
type B = StaticEncode<typeof T> // ok

console.log(Value.Decode(T, '2020-12-12'))  // ok
console.log(Value.Decode(T, null))          // note: can never decode as nullable is an unknown keyword

However this implementation has a problem where TypeBox will always fail to decode the null. This is because the nullable keyword is unknown to TypeBox. Note that nullable is a non-standard extension keyword that was added to OpenAPI. I believe this keyword was deprecated in OpenAPI 3.1.


Union Form

The following is a TypeBox supported version of this type. It replaces the unsafe nullable with a union of type T | null.

TypeScript Link Here

import { StaticDecode, StaticEncode, TSchema, Type, FormatRegistry } from "@sinclair/typebox";
import { Value } from '@sinclair/typebox/value'

// Note: You will need to register the 'date' format check for validation to pass. See 
// https://github.com/sinclairzx81/typebox/blob/master/examples/formats/date.ts
FormatRegistry.Set('date', (value) => true) // required

const Nullable = <T extends TSchema>(schema: T) => Type.Union([Type.Null(), schema])

const DateType = () => Type.Transform(Type.String({ format: 'date' }))
  .Decode((value) => new Date(value))
  .Encode((value) => value.toISOString())

const T = Nullable(DateType())
type A = StaticDecode<typeof T> // ok
type B = StaticEncode<typeof T> // ok

console.log(Value.Decode(T, '2020-12-12'))  // ok
console.log(Value.Decode(T, null))          // ok

Again, would recommend the Union form here as Decode currently runs a validation check prior to decoding (and can't successfully validate for null when using nullable). There would be a way to make this work by involving the TypeRegistry to register nullable as a specialized type, but the complexity of which would be fairly untenable.

This issue actually raises an interesting edge case when using TB Transforms. Because Encode and Decode both validate (and require TypeBox aware types to achieve that), it makes cross integration with Ajv somewhat difficult. I might actually flag this issue for consideration in a later update (potentially removing the validation step entirely), but do recommend the Union form on the 0.31.x revision.

Hope this helps
S

@simedia-fabianamhof
Copy link
Author

Unfortunately none of the provided solutions works properly with decode AND encode.
https://codesandbox.io/s/typescript-playground-export-forked-c5tggk?file=/index.ts:409-659

Value.Encode(T, new Date())

leads to Error: Unable to encode due to invalid value

@sinclairzx81
Copy link
Owner

@simedia-fabianamhof Hi, that looks like a bug :/

I have pushed a fix for this on 0.31.18. This should resolve the union encode issue you see. Associated PR can be found #635

Let me know if that helps
Cheers
S

@sinclairzx81
Copy link
Owner

@simedia-fabianamhof Hiya,

Hey, might close off this issue as there's not much else TypeBox can implement here with respect to Transforms and Unsafe types (at this time). Although give 0.31.18 a try (as it should resolve the transform union encode error).

Feel free to reply on this thread if you run into any additional issues with the 0.31.18 update, will close off this issue for now.
All the best
S

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants