Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

How to type a switch case with multiple payloads type as params?

I have the following scenario:

  public async handle(
    handler: WorkflowHandlerOption,
    payload: <how_to_type_it?>,
  ): Promise<StepResponseInterface> {
    switch (handler) {
      case WorkflowHandlerOption.JOB_APPLICATION_ACTIVITY: {
        const {
          entity_id: jobApplicationEntityId,
          status: jobApplicationStatus,
        } = await this.jobApplicationActivityHandler.execute(payload);
        // ...other staff
        break;
      }
      case WorkflowHandlerOption.BILLING_FEE: {
        const { entity_id: billingFeeId, status: billingFeeStatus } =
          await this.billingFeeHandler.execute(payload);
        // ...other staff
        break;
      }
      case WorkflowHandlerOption.DOCUMENT_GENERATION: {
        const { entity_id: documentId, status: documentStatus } =
          await this.documentGenerationHandler.execute(payload);
        // ...other staff
        break;
      }
      case WorkflowHandlerOption.UPDATE_JOB: {
        const { entity_id: jobId, status: jobStatus } =
          await this.jobHandler.execute(payload);
        // ...other staff
        break;
      }
      case WorkflowHandlerOption.DOCUMENT_GENERATION_ACTIVITY: {
        const {
          entity_id: documentActivityId,
          status: documentActivityStatus,
        } = await this.documentGenerationActivityHandler.execute(payload);
        // ...other staff
        break;
      }
      
      default:
        throw new BadRequestException('Handler not found');
    }

    // ...other staff
  }

I have many handlers which are getting completely different payloads, how should I type the payload param?

Imagine there will be like 50 cases..

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

The single option I have found it is to use Class Validator to validate the payload at every case.

I would want to use only typescript types and not third party libraries

>Solution :

Create a type that is a union of tuple types that represents the mapping of all the combinations of arguments your function accepts.

For example:

enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: { a: string }]
  | [handler: MyEnum.B, payload: { b: number }]
  | [handler: MyEnum.C, payload: { c: boolean }]

The HandlerArgs type binds specific payloads with specific enum values.

Now you can write an implementation like this:

class Foo {
  async handle([handler, payload]: HandlerArgs) {
    switch(handler) {
      case MyEnum.A: {
        console.log(payload.a) // fine
        break
      }
      case MyEnum.B: {
        console.log(payload.b) // fine
        break
      }
      case MyEnum.C: {
        console.log(payload.c) // fine
        break
      }
    }
  }
}

Here you can see that the type of payload is getting correctly narrowed to the payload type in the arguments tuple that was matched.

See Playground


You could even pull the payload types from the arguments of the functions you plan to pass those payloads to. You would do this by using the Parameters type to find the arguments of the functions and and derive that payload types from that.

For example:

enum MyEnum { A, B, C }

type HandlerArgs =
  | [handler: MyEnum.A, payload: Parameters<Foo['aHandler']['execute']>[0]]
  | [handler: MyEnum.B, payload: Parameters<Foo['bHandler']['execute']>[0]]
  | [handler: MyEnum.C, payload: Parameters<Foo['cHandler']['execute']>[0]]

class Foo {
  // mock some services this class uses
  declare aHandler: { execute: (payload: { a: string }) => void }
  declare bHandler: { execute: (payload: { b: number }) => void }
  declare cHandler: { execute: (payload: { c: boolean }) => void }

  async handle([handler, payload]: HandlerArgs) {
    switch(handler) {
      case MyEnum.A: {
        this.aHandler.execute(payload)
        break
      }
      case MyEnum.B: {
        this.bHandler.execute(payload)
        break
      }
      case MyEnum.C: {
        this.cHandler.execute(payload)
        break
      }
    }
  }
}

Here Parameters<Foo['aHandler']['execute']>[0] drills into the type of Foo.aHandler.execute and finds the parameters of that function, and then uses the first parameter as the payload type.

See playground

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading