Проверка правильности использования ключа-значения в возвращаемом объекте

#javascript #typescript #types

#javascript #typescript #типы

Вопрос:

У меня есть (сгенерированное) определение типа для возможных тел ответа:

 type ResponseMap = {
  '200': { 'ok': true },
  '404': { 'error': true, type: 'NotFound' },
  '500': null,
}
  

Я хочу написать функцию, которая возвращала бы объекты в форме { code, body } , проверенной на соответствие приведенному выше набору.

Лучшее, чего я смог добиться, это следующее:

 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 } }
}
  

Это проверка отсутствия неизвестных кодов состояния, возвращаемых функцией, и того, что все возвращаемые тела соответствуют одной из возможных форм тела.

Что он не проверяет, так это правильность сопряжения кода с телом, как продемонстрировано последним возвратом.

Как я могу ограничить тип точной формой тела для кода состояния здесь?

Мне нужно что-то вроде

 type ResponseCodeBodyPair<Bodies extends Record<string, unknown>> =
  Code extends keyof Bodies ?  { code: Code, body: Bodies[Code] } : never
  

… но это, конечно, не работает.

Я боролся с тем же самым много раз и всегда сдавался. Действительно ли это невозможно или я что-то упускаю?


PS Я знаю, что было бы нормально, если бы тип был { code: '200', body: … } | { code: '404', … } | … , но у меня его нет. Вот почему мне нужно каким-то образом преобразовать карту / запись в этот тип объединения.

Ответ №1:

Я нашел правильный способ сделать это.

Я могу сопоставить { [Code]: Response } объект с { [Code]: { code: Code, body: Response } } объектом и принимать значения этого объекта, T[keyof T] что приводит к точному типу, который мне нужен.

 type ResponseCodeBodyPairMap<M> = {
  [Code in keyof M]: { code: Code, body: M[Code] }
}

type Values<T> = T[keyof T]

type ResponseCodeBodyPair<M> = Values<ResponseCodeBodyPairMap<M>>
  

Комментарии:

1. Это было креативно!