sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
5.08k stars 161 forks source link

Equivalent / replacement for valibot's flatten method #1044

Closed ShlokDesai33 closed 3 weeks ago

ShlokDesai33 commented 1 month ago

Hey there!

I'm looking at a potential migration from valibot to typebox. I've looked through the docs and can't seem to find an equivalent for valibot's flatten function (https://valibot.dev/api/flatten/). Is this something I'd have to write myself? If so, where and how do I start?

NiklasPor commented 3 weeks ago

Hey a @ShlokDesai33, would love to know what's the motivation behind moving to TypeBox from Valibot 👀

sinclairzx81 commented 3 weeks ago

@ShlokDesai33 Hiya, apologies for the delay

At this stage, TypeBox doesn't provide a defacto error flattening function. The reason for this is TypeBox errors are intended to be explicitly mapped by end users as well as pulled via iterators. There are numerous ways this could be implemented based on various requirements (hierarchical, linear, finite errors for DoS prevention, etc) . Because of this, TypeBox exposes the iterators to callers, but doesn't provide convenience functions over the the top (as it's not clear if higher level abstractions would implicate future updates to Error generation / design).

TypeBox errors are hierarchical, so you will need to map / flatten them yourself. I'm not familiar with valibots flatten function, but the following implements a basic mapping, pulling from iterators to build up custom errors (specifically pulling union variant iterators into mapped errors)

import { Value, ValueError, ValueErrorIterator } from '@sinclair/typebox/value'
import { Type } from '@sinclair/typebox'

// ------------------------------------------------------------------
// Error Mapping
// ------------------------------------------------------------------

type MappedError = [{ message: string, path: string, errors: MappedError[] }]

/** Maps a value error */
const MapValueError = (error: ValueError) => ({
  message: error.message, 
  path: error.path,
  errors: error.errors.map(iterator => MapValueErrorIterator(iterator))
})

/** Maps a value error iterator */
const MapValueErrorIterator = (iterator: ValueErrorIterator): MappedError[] => (
  [...iterator].map(error => MapValueError(error)) as never
)

// ------------------------------------------------------------------
// Usage
// ------------------------------------------------------------------

const U = Type.Union([
  Type.Literal('A'),
  Type.Literal('B'),
  Type.Literal('C')
])

const T = Type.Object({
  value: U
})

const E = Value.Errors(T, { value: { x: 1 } })

const F = MapValueErrorIterator(E)

console.dir(F, { depth: 100 }) // const R = [
                               //   {
                               //     message: 'Expected union value',
                               //     path: '/value',
                               //     errors: [
                               //       [ { message: "Expected 'A'", path: '/value', errors: [] } ],
                               //       [ { message: "Expected 'B'", path: '/value', errors: [] } ],
                               //       [ { message: "Expected 'C'", path: '/value', errors: [] } ]
                               //     ]
                               //   }
                               // ]

TypeBox may implement some helper functions on Errors in later revisions (aligned with the iterator helper TC39 proposal), but for now, you will need to explicitly map / flatten them yourself.

Hope this helps S

sinclairzx81 commented 3 weeks ago

@ShlokDesai33 Heya,

Will close off this issue for now as the mapping code above should serve as a basis for additional mapping / flattening of errors. As mentioned, TypeBox may introduce some convenience functions to assist with projecting errors in future, but as of now, they are kept fairly raw to cast the widest net in terms of possible mapping implementations.

Feel free to ping on this thread if you have any follow up questions.

Cheers S

ShlokDesai33 commented 3 weeks ago

Hey a @ShlokDesai33, would love to know what's the motivation behind moving to TypeBox from Valibot 👀

Because I might need to switch to fastify, which pushes typebox for validation. Haven't decided on anything yet, just comparing options.

ShlokDesai33 commented 3 weeks ago

@ShlokDesai33 Heya,

Will close off this issue for now as the mapping code above should serve as a basis for additional mapping / flattening of errors. As mentioned, TypeBox may introduce some convenience functions to assist with projecting errors in future, but as of now, they are kept fairly raw to cast the widest net in terms of possible mapping implementations.

Feel free to ping on this thread if you have any follow up questions.

Cheers S

Thanks a ton! The code is exactly what I was looking for.