After generating a type definition for possible response bodies, I am looking to create a function that returns objects shaped as { code, body }
, which are validated against the typing provided.
My current solution looks like this:
type Codes<Bodies> = keyof Bodies
type ResponseCodeBodyPair<Bodies extends Record<string, unknown>> = {
code: Codes<Bodies>,
body: Bodies[Codes<Bodies>]
}
function endpoint(): ResponseCodeBodyPair<ResponseMap> {
if (Math.random()) {
return { code: '200', body: { 'ok': true } }
}
if (Math.random()) {
return { code: '404', body: { 'error': true, type: 'NotFound' } }
}
if (Math.random()) {
return { code: '500', body: null }
}
// This should fail since { ok: true } is not a valid body for 404.
return { code: '404', body: { 'ok': true } }
}
This implementation ensures that there are no unknown status codes returned by the function and that all returned bodies match one of the predefined shapes.
However, it does not validate the correctness of code-to-body pairing, as demonstrated by the last return statement.
I am searching for a way to restrict the type to the exact body shape for each status code. Something like:
type ResponseCodeBodyPair<Bodies extends Record<string, unknown>> =
Code extends keyof Bodies ? { code: Code, body: Bodies[Code] } : never
Unfortunately, my attempts have been unsuccessful so far. Is achieving this level of restriction even possible?
P.S. I acknowledge that having a type
{ code: '200', body: … } | { code: '404', … } | …
would simplify things, but that is not an option at the moment. Therefore, I need to find a way to transform the map/record into this union type.