File size: 2,010 Bytes
bee0296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
import { CHOICE_TOKEN } from '../choice-token'
import { CHOICES_LIMIT } from '../choices-limit'
import { isRecord } from '../is-record'
import type { Method } from '../method'
import { refusal } from '../refusal'

/**
 * One property's value as it goes into the metadata: a finite number, a
 * boolean, or a Choice's value chosen from its token list.
 *
 * A bare string is refused: free text never reaches the row.
 *
 * @param key the property's key, named in a refusal
 * @param value what the caller passed under it
 * @param method the method the property was passed to, named in a refusal
 * @returns the value as stored: the boolean or finite number unchanged, or the
 *          chosen member when value is a Choice
 */
export function checkedValue(
  key: string,
  value: unknown,
  method: Method = 'log',
): string | number | boolean {
  if (typeof value === 'boolean') {
    return value
  }

  if (typeof value === 'number') {
    if (Number.isFinite(value)) {
      return value
    }

    throw refusal(`props.${key}: a number is finite`, method)
  }

  if (typeof value === 'string') {
    throw refusal(
      `props.${key}: free text is refused; a string is a Choice, ` +
        `{ value, of: [...] }`,
      method,
    )
  }

  if (!isRecord(value) || !Array.isArray(value.of)) {
    throw refusal(
      `props.${key}: a value is a finite number, a boolean, or a Choice, ` +
        `{ value, of: [...] }`,
      method,
    )
  }

  const members = value.of
  const chosen = value.value

  const isTokenList =
    members.length > 0 &&
    members.length <= CHOICES_LIMIT &&
    members.every(
      member => typeof member === 'string' && CHOICE_TOKEN.test(member),
    )

  if (!isTokenList) {
    throw refusal(
      `props.${key}.of: a list of 1 to ${CHOICES_LIMIT} ` + `lowercase tokens`,
      method,
    )
  }

  if (typeof chosen !== 'string' || !members.includes(chosen)) {
    throw refusal(`props.${key}.value: one of the members of \`of\``, method)
  }

  return chosen
}