I am unable of providing a solution creating the type that would infer the arguments of an ErrorMessage
value based on the code
argument.
./errorCodes.ts
export enum ErrorCodes {
ErrorCode1,
ErrorCode2,
ErrorCode3
}
./errorMessages.ts
export const ErrorMessages = {
[ErrorCodes.ErrorCode1]: (a1: string, a2: string) => `${a1} ${a2} message1...`,
[ErrorCodes.ErrorCode2]: (a1: boolean) => `${a1} message2...`,
[ErrorCodes.ErrorCode3]: `message3...`
}
./formatMessage.ts
import {ErrorCodes} from "./errorCodes"
import {ErrorMessages} from './errorMessages'
export const formatMessage = (
code: ErrorCodes,
...args: Parameters<typeof ErrorMessages[typeof ErrorCodes[typeof code]]>
// ^ Type '{ 0: (a1: string, a2: string) => string; 1: (a1: boolean) => string; 2: string; }' has no matching index signature for type 'string'.ts(2537)
) => {
const message = ErrorMessages[code];
const errorCode = `[${ErrorCodes[code]}]`;
switch (typeof message) {
case "string": {
return [errorCode, message].join(" ");
}
case "function": {
return [errorCode, message(...args)].join(" ");
/** ^
A spread argument must either have a tuple type or be passed to a rest parameter. ts(2556)
*/
}
}
I've tried the following line:
...args: Parameters<typeof ErrorMessages[typeof ErrorCodes[typeof code]]>
Even thought it semi-worked, there are some issues:
- There's no intellisense when invoking the function.
- as soon as I added
string
returns inside ofErrorMessages
, the type was obviously broken.
CodePudding user response:
Here is how I would define the function:
export const formatMessage = <E extends ErrorCodes>(
code: E,
...args: typeof ErrorMessages[E] extends infer Fn extends (...args: any) => any
? Parameters<Fn>
: [typeof ErrorMessages[E]]
) => {
const message = ErrorMessages[code];
const errorCode = `[${ErrorCodes[code]}]`;
switch (typeof message) {
case "string": {
return [errorCode, message].join(" ");
}
case "function": {
return [errorCode, (message as (...args: any) => any)(...args)].join(" ");
}
}
}
Let's go over the changes.
The function should be generic. Something like
typeof ErrorMessages[typeof ErrorCodes[typeof code]]
probably does not what you expect it to do.
typeof code
will not reference the type ofcode
with which the function was called. It only references its static type which isErrorCodes
. The whole statement just producess a union of all cases.We should instead give
code
the generic typeE
. The type ofE
is determined by the caller and can be referenced in the the second parameter type.The second parameter has to use a conditional type to check if
typeof ErrorMessages[E]
is a function type before giving to toParameters
. As you already noticed,string
can not be passed to this utility type because its not a function.The last error message
A spread argument must either have a tuple type or be passed to a rest parameter
can not be solved with some satisfying solution. The compiler does not have the means to fully understand the higher order implications of the type of
...args
and how it relates to the type ofmessage
. We have to help him out here by asserting thatmessage
is a function which can be called with whatever arguments.return [errorCode, (message as (...args: any) => any)(...args)].join(" ");
With all these things in place, the function can now be called like this:
formatMessage(ErrorCodes.ErrorCode1, "a", "b")
formatMessage(ErrorCodes.ErrorCode2, true)
formatMessage(ErrorCodes.ErrorCode3, "a")