io-ts icon indicating copy to clipboard operation
io-ts copied to clipboard

Better validation errors for tagged unions

Open anilanar opened this issue 6 years ago • 4 comments

🚀 Feature request

Current Behavior

const C = t.union([
  t.type({
    type: t.literal('A'),
    foo: t.number,
  }),
  t.type({
    type: t.literal('B'),
    bar: t.number,
  }),
]);

// Validation errors have errors for A too
// e.g. 
// 0.type expected 'A', actual 'B'
// 0.foo expected number, actual undefined
// 1.bar expected number, actual 'wrong type'
C.decode({ type: 'B', bar: 'wrong type' }).value

Desired Behavior

It would be great if there was a way to reduce validation errors if there's a tag match. I have a tagged union of 10 codecs with dozens of properties and it is very hard to understand what is actually wrong. So with above example codec, I'd expect the following validation error:

1.bar expected number, actual 'wrong type'

anilanar avatar Jul 04 '19 10:07 anilanar

I can't repro with [email protected] or [email protected]

import * as t from 'io-ts'
import { PathReporter } from 'io-ts/lib/PathReporter'

const C = t.union([
  t.type({
    type: t.literal('A'),
    foo: t.number
  }),
  t.type({
    type: t.literal('B'),
    bar: t.number
  })
])

console.log(PathReporter.report(C.decode({ type: 'B', bar: 'wrong type' })))
// [ 'Invalid value "wrong type" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/1: { type: "B", bar: number }/bar: number' ]

gcanti avatar Jul 04 '19 11:07 gcanti

Oh I see, my bad. I've checked the io-ts implementation now, union tag detection needs a TypeC or a StrictC and I was using some custom types.

anilanar avatar Jul 05 '19 09:07 anilanar

Alright, I'll reopen this so that this can be thought about. The only difference is mapOutput.

import * as t from 'io-ts'
import { mapOutput } from 'io-ts-types'
import { PathReporter } from 'io-ts/lib/PathReporter'

const C = t.union([
  mapOutput(t.type({
    type: t.literal('A'),
    foo: t.number
  }), x => x),
  t.type({
    type: t.literal('B'),
    bar: t.number
  })
])

console.log(PathReporter.report(C.decode({ type: 'B', bar: 'wrong type' })))

// 0: "Invalid value "B" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/0: { type: "A", foo: number }/type: "A""
// 1: "Invalid value undefined supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/0: { type: "A", foo: number }/foo: number"
// 2: "Invalid value "wrong type" supplied to : ({ type: "A", foo: number } | { type: "B", bar: number })/1: { type: "B", bar: number }/bar: number"

anilanar avatar Jul 05 '19 09:07 anilanar

In case someone would find it useful, I implemented a more aggressive union reduction reporter:

https://github.com/klesun/io-ts-better-union-error-reporter

The built-in PathReporter only seems to successfully eliminate unions on very basic types, but leaves the excessive options if there are sophisticated intersection types involved. The lib tries to solve that.

klesun avatar Jul 01 '21 08:07 klesun