'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 inresdetermines the type ofdata.
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:
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 |
