Skip to content

Exhaustive AND have a default? #24

@mikecann

Description

@mikecann

I am still loving Variant and using it on a daily basis so MASSIVE thanks for this awesome lib :)

Up to now Variant has done just about everything I wanted. I have however started to run into a scenario that is causing problems. Its best described with an example.

e.g. I have the following

interface Attack {
  kind: `attack`;
  playerId: string;
}

interface Timeout {
  kind: `timeout`;
  playerId: string;
}

interface Surrender {
  kind: `surrender`;
  playerId: string;
}

export type BattleFinishedReason = Attack | Timeout | Surrender;

export const calculateIfBattleWasAbandoned = (reason: BattleFinishedReason ): boolean => 
  matchKind(reason, {
    timeout: () => true,
    surrender: () => true,
    attack: () => false,
  });

This is great, I release this code as v1. The server and client are working great.

I now do a v2 of the game and add a new type to the union..

...

interface Cancel {
  kind: `cancel`;
  playerId: string;
}

export type BattleFinishedReason = Attack | Timeout | Surrender | Cancel;

The compiler will now yell at me that there is a missing option in the calculateIfBattleWasAbandoned match which is great, so I add that:

export const calculateIfBattleWasAbandoned = (reason: BattleFinishedReason ): boolean => 
  matchKind(reason, {
    timeout: () => true,
    surrender: () => true,
    attack: () => false,
    cancel: () => true,
  });

I then push the code as a v2 release. The server is happy because it can handle the new case but the problem is there are clients still on v1 of the game and so their calculateIfBattleWasAbandoned matcher doesnt know how to handle the Cancel BattleFinishedReason ..

I know this can be solved with a default option in the matcher:

export const calculateIfBattleWasAbandoned = (reason: BattleFinishedReason ): boolean => 
  matchKind(reason, {
    timeout: () => true,
    surrender: () => true,
    attack: () => false,
    cancel: () => true,

    // Saving future me some headaches
    default: () => false
  });

... but this breaks the exhustive checking which I rely on so much.

So is it possible for the matcher to be both exhaustive AND have a default?

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions