'Using enum to determine property type

I am currently writing some type definitions regarding REST API. In this particular case, I want to send three main properties as response. Those are:

  • type: The type of response client should be getting.
  • res: Result code. This becomes 0 if successful, or non-zero if the request failed. In case of non-zero, each number indicates a cause of it.
  • data: The actual data the client would process. It may be the data client requested, or may show what part of the client's request caused the request to fail. As a result, value in res determines the type of data.

This is the code I wrote.

export type ResTemplate<T, S extends keyof U, U> = {
    type: T;
    res: S;
    data: U[S];
}

export enum ReqErrCode{
    MissingBody = 1,
    MissingProperty,
}

export type ReqErrData = {
    [ReqErrCode.MissingBody]: null;
    [ReqErrCode.MissingProperty]: string; //MissingProperty requires string, not null
}

export type ReqErr = ResTemplate<'ReqErr', ReqErrCode, ReqErrData>;

const t: ReqErr = {
    type: 'ReqErr',
    res: ReqErrCode.MissingProperty,
    data: null //But this doesn't get underlined
}

But t.data does not get underlined. My guess was that in ResTemplate<'ReqErr', ReqErrCode, ReqErrData>, S simply resolves into ReqErrCode.MissingBody | ReqErrCode.MissingProperty, which is why U became string | null.

I did find two solutions, but I would like to know if another way is possible. Those two are:

export type ReqErr<T extends ReqErrCode> = ResTemplate<'ReqErr', T, ReqErrData>;

const t: ReqErr<ReqErrCode.MissingProperty> = {
    type: 'ReqErr',
    res: ReqErrCode.MissingProperty,
    data: null // Underlined
}
export type ReqErr = {
    type: 'ReqErr'
} & (
    {
        res: ReqErrCode.MissingProperty;
        data: string;
    } | {
        res: ReqErrCode.MissingBody;
    }
);

const t: ReqErr = {
    type: 'ReqErr',
    res: ReqErrCode.MissingProperty,
    data: null // Underlined
}

Both works, but in the former, I have to repeat ReqErrCode.* in both generic type and res property every time I try to create an object. In the latter, ReqErr definition becomes messy. Additionally, I can't have types like ResTemplate above which all responses are based on, so I can't enforce all responses to have type, res and data properties.



Solution 1:[1]

You can use an overloaded function to create and constrain/validate your response data types:

TS Playground

type Values<T> = T[keyof T];
type Satisfies<Constraint, Type extends Constraint> = Type;

const ReqErrCodeMap = {
  MissingBody: 1,
  MissingProperty: 2,
} as const;

type ReqErrCode = Values<typeof ReqErrCodeMap>;

type ReqErrDataMap = Satisfies<Record<ReqErrCode, unknown>, {
  [ReqErrCodeMap.MissingBody]: null,
  [ReqErrCodeMap.MissingProperty]: string,
}>;

type ResponseData<Type, Code extends number, Data> = {
  type: Type;
  code: Code;
  data: Data;
};

function createResponse <Data>(
  type: string,
  code: 0,
  data: Data,
): ResponseData<string, 0, Data>;
function createResponse <Code extends ReqErrCode, Data extends ReqErrDataMap[Code]>(
  type: string,
  code: Code,
  data: Data,
): ResponseData<string, Code, Data>;
function createResponse <Data>(
  type: string,
  code: number,
  data: Data,
) {
  return {type, code, data};
}

const res = createResponse('ok', 0, {msg: 'You did it!'});

const resErrOkA = createResponse('error', ReqErrCodeMap.MissingBody, null);
const resErrNotOkA = createResponse('error', ReqErrCodeMap.MissingBody, 'Nope'); /*
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Argument of type '"Nope"' is not assignable to parameter of type 'null'.(2769) */

const resErrOkB = createResponse('error', ReqErrCodeMap.MissingProperty, 'Nope');
const resErrNotOkB = createResponse('error', ReqErrCodeMap.MissingProperty, {msg: 'Nope'}); /*
                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Argument of type '{ msg: string; }' is not assignable to parameter of type 'string'.(2769) */

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 jsejcksn