Function wrapper statically checking parameters for HTTP response

Viewed 123

i am trying to implement a type assertion wrapper for function calling Express'es chain res.status(..).json(..). I think i am "already there", but i stuck. The goal is to statically check whether:

  • passed HTTP status code conforms certain response payload
  • response payload has certain value

TypeScript playground demo (full implementation attempt): click

One of errors from the list i receive in above linked TS demo code [x]:

Property '200' does not exist on type 'THTTPStatusCodeToData'

Wrapper implementation is:

function responseWrapper<
  DataKey extends keyof IEmbracedResponse
>(
  res: Res,
  status: keyof typeof mappedHTTPStatusCode,
  data: Record<
    DataKey, 
    THTTPStatusCodeToData[typeof status] // <-- so far, problem is here [x]
  >
) {
  return res.status(status).json(data); // just a chain call, but TS asserts correct `data` type for certain `status`
}

Example usage:

router.get('/', (req: Request, res: Response) => {
  if (!req.body.login) {
     return responseWrapper(res, 400, { error: 'Request payload lacks of "login"!' });
  }

  return responseWrapper(res, 200, { payload: { isAdmin: true }});
})

Example inputs and TS expected type checking results:

responseWrapper(res, 200, { exception: Error('ups') }); // <-- fail, because 'exception' key cannot be associated with 200 status
responseWrapper(res, 500, { exception: 'something crashed'}); // <-- fail, because 'exception' cannot be a string, but Error object
responseWrapper(res, 200, { something: null}); // <-- fail, because status 200 cannot be associated with 'something' key
responseWrapper(res, 500, { error: 'ups' }); // <-- fail, because status 500 must be associated with 'exception' key, not the 'error' key

responseWrapper(res, 200, { payload: { isAdmin: true }}); // <-- ok, because status 200 can be associated with 'payload' key and the payload has object value
responseWrapper(res, 500, { exception: Error('ups') }); // <-- ok, because status 500 can be associated with 'exception' key and the value is Error object
responseWrapper(res, 400, { error: 'ups' }); // <-- ok, because status 400 can be associated with 'error' key and it's a string

So far i've used less advanced wrapper, just to check if passed object value is correctly associated with the key, but now i also want to have HTTP status regarding value association check:

const embraceResponse = <
  Key extends keyof IEmbracedResponse
>(response: Record<Key, IEmbracedResponse[Key]>) =>
  response;

// usage examples:
res.status(200).json(embraceResponse({ payload: { prop: true } }));
res.status(400).json(embraceResponse({ error: 'ups' }));
1 Answers

Thanks for fully working playground :), I made some changes in your code, the key change is one solid type converting status to data. responseWrapper definition becomes simpler with it. And the checks, you provided, seem to be working now.

type StatusToData<status> = 
  status extends keyof TypeOfHTTPStatusCodes['SUCCESSFUL'] ? Pick<IEmbracedResponse,'payload'> | Pick<IEmbracedResponse,'message'>
  : status extends keyof TypeOfHTTPStatusCodes['CLIENT_ERROR'] ? Pick<IEmbracedResponse, 'error'> 
  : status extends keyof TypeOfHTTPStatusCodes['SERVER_ERROR'] ? Pick<IEmbracedResponse, 'exception'>
  : never;

function responseWrapper<
   Status extends keyof typeof mappedHTTPStatusCode
>(
  res: Res,
  status: Status ,
  data: StatusToData<Status>,
) {
  return res.status(status).json(data); 
}

Full Code in playground

Related