Reputation: 2496
I have a enum and I let each value of the enum as a key in my interface whose value is specific to the key.
Example:
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
// EDIT: These slicers can have completely different shapes.
interface AppleSlicer { foo: () => string }
interface BananaSlicer { bar: () => number }
interface FruitSlicers {
[Fruits.Apple]: AppleSlicer,
[Fruits.Banana]: BananSlicer,
}
This works well, but I want have similar code in several places and I want them to give me compile errors when there's a new entry in the enum. Currently, this doesn't do any exhaustive check, so it does not. Is it possible to achieve that with TypeScript?
Upvotes: 3
Views: 1472
Reputation: 33749
You can use an identity function to constrain the input parameter type so that that it must include all of the keys in the string enum, and also all of the entries in the interface:
enum Fruit {
Apple = 'Apple',
Banana = 'Banana',
Orange = 'Orange',
}
interface AppleSlicer { foo: () => string }
interface BananaSlicer { bar: () => number }
interface FruitSlicers {
[Fruit.Apple]: AppleSlicer;
[Fruit.Banana]: BananaSlicer;
}
function createSlicers <T extends Record<Fruit, unknown> & FruitSlicers>(value: T): T {
return value;
}
const slicers1 = createSlicers({
Apple: { foo: () => 'apple' },
Banana: { bar: () => 1 },
}); // Error (2345)
const slicers2 = createSlicers({
Apple: { foo: () => 'apple' },
Banana: { bar: () => 1 },
Orange: 'not in the interface, so can be any value',
}); // ok
Upvotes: 1
Reputation: 275947
For this you will need type compatibility checks. My recommended library for this is ts-expect
: https://github.com/TypeStrong/ts-expect
Here is a complete sample (I've inlined expectType
and TypeOf
from ts-expect):
export const expectType = <Type>(value: Type): void => void 0;
export type TypeOf<Target, Value> = Exclude<Value, Target> extends never
? true
: false;
type AppleSlicer = { apples: number }
type BananaSlicer = { bananas: number }
enum Fruits {
Apple = "Apple",
Banana = "Banana",
}
type FruitSlicersComplete = {
[Fruits.Apple]: AppleSlicer,
[Fruits.Banana]: BananaSlicer,
}
type FruitSlicersIncomplete = {
[Fruits.Apple]: AppleSlicer,
// [Fruits.Banana]: BananaSlicer,
}
type FruitSlicersExhaustive = {[key in Fruits]: any}
expectType<TypeOf<FruitSlicersExhaustive,FruitSlicersComplete>>(true); // Success ✅
expectType<TypeOf<FruitSlicersExhaustive,FruitSlicersIncomplete>>(true); // Error ❌
Upvotes: 3
Reputation: 10007
You probably want to make use of the Record
utility type, which works great with enums.
Something like:
enum Fruits {
Apple = 'Apple',
Banana = 'Banana',
}
type Slicer = Function; // Quick example case
type FruitSlicers = Record<Fruits, Slicer>; // <- Ensures every enum value is a key
const fruitSlicers: FruitSlicers = {
[Fruits.Apple]: () => {},
[Fruits.Banana]: () => {},
};
Now if I add another Fruit
:
enum Fruits {
Apple = 'Apple',
Banana = 'Banana',
Cherry = 'Cherry',
}
I get:
Property 'Cherry' is missing in type '{ Apple: () => void; Banana: () => void; }' but required in type 'FruitSlicers'
Upvotes: 5