gcanti / io-ts-types

A collection of codecs and combinators for use with io-ts
https://gcanti.github.io/io-ts-types/
MIT License
311 stars 40 forks source link

Unexpected behaviour when using `withFallback` with `t.union` #169

Open OliverJAsh opened 2 years ago

OliverJAsh commented 2 years ago

🐛 Bug report

Current Behavior

import * as tt from "io-ts-types";
import * as t from "io-ts";
import { pipe } from "fp-ts/function";

const Premium = t.type({
    premium: tt.withFallback(t.literal(true), true),
});

const Free = t.type({
    premium: tt.withFallback(t.literal(false), false),
});

const Union = t.union([Free, Premium]);

const z = {};

pipe(z, Free.decode, console.log);
// Right ✅
pipe(z, Union.decode, console.log);
// Left ❌

Expected behavior

See code comments above.

If Free.decode(z) returns a Right then I believe Union.decode(z) should also return a Right?

Reproducible example

Suggested solution(s)

Additional context

Your environment

package.json:

        "fp-ts": "^2.12.2",
        "io-ts": "^2.2.16",
        "io-ts-types": "^0.5.16",
        "monocle-ts": "^2.3.13",
        "newtype-ts": "^0.3.5",
        "typescript": "^4.7.4"
gcanti commented 2 years ago

@OliverJAsh looks like io-ts detects Union as a tagged union (being "premium" the tag) and executes this path https://github.com/gcanti/io-ts/blob/63eea029773595b6a9cff9d70894935ae7a20e7e/src/index.ts#L1598 since {}['premium'] is undefined, so it returns early without giving withFallback a chance.

Not sure what's the possible fix, maybe execute a deoptimized validate instead of bailing out: https://github.com/gcanti/io-ts/compare/gcanti:63eea02...gcanti:11ecad3

WDYT?

OliverJAsh commented 2 years ago

That looks good to me but I'm not too familiar with the implementation. Maybe @mlegenhausen has some thoughts?

mlegenhausen commented 2 years ago

@gcanti this would be great. I think this should also cover possible other errors that could be introduced by custom types.