Developing a union type to guarantee every value in the `enum` is accounted for

My code includes an enum that lists operations (which cannot be altered):

enum OpType {
  OpA = 0,
  OpB = 1,
}

In addition, there are object types defined to hold data required for each operation:

type A = {
  readonly opType: OpType.OpA;
  readonly foo: number;
};

type B = {
  readonly opType: OpType.OpB;
  readonly bar: string;
};

Furthermore, there is a handler function in place to ensure all operations are dealt with effectively:

type Ops = A | B;

export const ensureExhaustive = (_param: never) => {};

export const handleOp = (op: Ops) => {
  switch (op.opType) {
    case OpType.OpA:
      if (op.foo < 80) { /* … */ }
      break;
    case OpType.OpB:
      if (op.bar === 'foo') { /* … */ }
      break;
    default:
      ensureExhaustive(op);
  }
}

Despite the functionality of the handleOp function, it only verifies handling of what is explicitly included in the Ops union – any new values added to the OpType enum, like OpC = 2, will not prompt detection that they are unhandled.

Is there a way to establish a "connection" between the enum values (potentially via an Ops type) and the switch statement within the handleOp function to guarantee every value is addressed?

Answer №1

You have the option to rewrite the ensureExhaustive function as a specialized type:

type EnsureExhaustive<T extends never> = T;

Next, create a placeholder type to ensure something is always never:

type OpsMatchesEnum = EnsureExhaustive<Exclude<OpType, Ops["opType"]>>;

Luckily, there is already a utility called Exclude that behaves in this way:

Exclude<1 | 2 | 3, 1 | 2>;     // 3
Exclude<1 | 2 | 3, 1 | 2 | 3>; // never

In essence, if the second argument includes all elements of the first, the result will be never. In our case, we want this when Ops["opType"] covers every member of OpType.

We can also make ensureExhaustive generic, giving it the signature:

export const ensureExhaustive = <T extends never>(_param: T) => {};

This allows us to utilize instantiation expressions introduced in version 4.7, eliminating the need for an EnsureExhaustive type:

type OpsMatchesEnum = typeof ensureExhaustive<Exclude<OpType, Ops["opType"]>>;

If you have noUnusedLocals enabled or use a linter, this dummy type may trigger an error or warning.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

In Javascript, what significance does the symbol ":" hold?

While exploring the Ionic framework, I came across the following code snippet: import { AlertController } from 'ionic-angular'; export class MyPage { constructor(public alertCtrl: AlertController) { } I'm curious about the significanc ...

The introduction of an underscore alters the accessibility of a variable

When working in Angular, I encountered a scenario where I have two files. In the first file, I declared: private _test: BehaviorSubject<any> = new BehaviorSubject({}); And in the second file, I have the following code: test$: Observable<Object& ...

Guide to incorporating @types/module with the corresponding npm module that has type definitions available

This is an example of a module I am currently utilizing in my project. I have come across TypeScript type definitions for the npm module polylabel, which can be found at https://github.com/mapbox/polylabel. When I run npm install --save @types/polylabel, ...

Issue: The configuration for the rule "no-empty-interface" in .eslintrc.js is not valid

Is it acceptable to include an empty interface like the one shown below in the eslintrc.js file? interface RoutesProps {} https://i.sstatic.net/HwroT.png https://i.sstatic.net/pUKhC.png ...

Issues with utilizing destructuring on props within React JS storybooks

I seem to be encountering an issue with destructuring my props in the context of writing a storybook for a story. It feels like there may be a mistake in my approach to destructuring. Below is the code snippet for my component: export function WrapTitle({ ...

Issue with Angular Material date picker: Date Parsing UTC causing dates to display as one day earlier

After exploring numerous threads related to this issue and spending several days trying to find a solution, I may have stumbled upon a potential fix. However, the workaround feels too messy for my liking. Similar to other users, I am encountering an issue ...

Attempting to map an object, however it is showing an error stating that the property 'title' does not exist on type 'never'

While attempting to retrieve data from the Bloomberg API, I encountered an issue when trying to extract the title from the response object. The error message received was: Property 'title' does not exist on type 'never'. Below is the co ...

When utilizing the catch function callback in Angular 2 with RxJs, the binding to 'this' can trigger the HTTP request to loop repeatedly

I have developed a method to handle errors resulting from http requests. Here is an example of how it functions: public handleError(err: any, caught: Observable<any>): Observable<any> { //irrelevant code omitted this.logger.debug(err);//e ...

Executing the command `subprocess.run("npx prettier --write test.ts", shell=True)` fails to work, however, the same command runs successfully when entered directly into the terminal

Here is the structure of my files: test.py test.ts I am currently trying to format the TypeScript file using a Python script, specifically running it in Command Prompt on Windows. When I execute my python script with subprocess.run("npx prettier --w ...

The attribute 'split' is not found on the never data type

I have a function that can update a variable called `result`. If `result` is not a string, the function will stop. However, if it is a string, I then apply the `split()` method to the `result` string. This function always runs successfully without crashin ...

What is the relationship between Typescript references, builds, and Docker?

I am facing a dilemma with my projectA which utilizes a common package that is also needed by my other Nodejs services. I am unsure of the best approach to package this in a Docker file. Ideally, running tsc build would compile both the project and the dep ...

Modifying an onClick handler function within a react element located in a node module, which points to a function in a prop declared in the main Component file

I have successfully implemented the coreui CDataTable to display a table. return ( <CDataTable items={data} fields={fields} ... /> ) Everything is working smoothly, but I wanted to add an extra button in the header of the C ...

Developing a personalized validation function using Typescript for the expressValidator class - parameter is assumed to have a type of 'any'

I'm seeking to develop a unique validation function for express-validator in typescript by extending the 'body' object. After reviewing the helpful resource page, I came across this code snippet: import { ExpressValidator } from 'expre ...

"Time" for creating a date with just the year or the month and year

I am trying to convert a date string from the format "YYYYMMDD" to a different format using moment.js. Here is the code snippet I am using: import moment from 'moment'; getDateStr(date: string, format){ return moment(date, 'YYYYMMDD&a ...

Ionic 2 - Dynamically Loading Segments

I am dealing with categories and dishes. Each dish is associated with a specific category. My goal is to send an http request to retrieve all the dishes belonging to a particular category. This will result in an array like: { Soup[{'Name',&ap ...

Sending a specific object and its corresponding key as parameters to a reusable component in Angular: T[K]

I am currently working on developing a generic component in Angular and Typescript version 4.4.4. The goal is to create a component where I can set both an object (obj) and specific keys (properties). Here's a simplified version of my text component: ...

Issue: Cannot assign type 'Promise<PostInfo>[]' to type 'PostInfo[]' while updating state

At the moment, I am facing an issue with my function that fetches an API and updates state. Specifically, I encounter an error when attempting to assign a Promise to the state. type DataState = { postList: Array<PostInfo>: }; const [state, setSt ...

Calculate the time difference between the stroke of midnight on a specific date and the present moment using JavaScript, node.js, and

Looking for a way to determine if the current moment is less than 3 minutes after midnight of the next date using a JavaScript Date object (e.g. 09.08.2020 15.45). This condition should evaluate to true for times ranging from 09.09.2020 00:00 up until 09.0 ...

The functionality of MaterializeCSS modals seems to be experiencing issues within an Angular2 (webpack) application

My goal is to display modals with a modal-trigger without it automatically popping up during application initialization. However, every time I start my application, the modal pops up instantly. Below is the code snippet from my component .ts file: import ...

Leverage the optional attribute of an interface as a data type within openapi-typescript

Is there a way to use an interface property as a variable type in TypeScript? I need to access the property: string type and use it as a variable type, but I'm having trouble accessing it. interface foo { bar?: { baz: { property: string; ...