diff --git a/ts/environment.ts b/ts/environment.ts index 9aab86c70..92795de6a 100644 --- a/ts/environment.ts +++ b/ts/environment.ts @@ -1,6 +1,8 @@ // Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only +import { makeEnumParser } from './util/enum'; + // Many places rely on this enum being a string. export enum Environment { Development = 'development', @@ -33,17 +35,7 @@ export function setEnvironment(env: Environment): void { environment = env; } -const ENVIRONMENTS_BY_STRING = new Map([ - ['development', Environment.Development], - ['production', Environment.Production], - ['staging', Environment.Staging], - ['test', Environment.Test], - ['test-lib', Environment.TestLib], -]); -export function parseEnvironment(value: unknown): Environment { - if (typeof value !== 'string') { - return Environment.Production; - } - const result = ENVIRONMENTS_BY_STRING.get(value); - return result || Environment.Production; -} +export const parseEnvironment = makeEnumParser( + Environment, + Environment.Production +); diff --git a/ts/test-both/util/enum_test.ts b/ts/test-both/util/enum_test.ts new file mode 100644 index 000000000..4a3033865 --- /dev/null +++ b/ts/test-both/util/enum_test.ts @@ -0,0 +1,37 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import { makeEnumParser } from '../../util/enum'; + +describe('enum utils', () => { + describe('makeEnumParser', () => { + enum Color { + Red = 'red', + Green = 'green', + Blue = 'blue', + } + + const parse = makeEnumParser(Color, Color.Blue); + + it('returns a parser that returns the default value if passed a non-string', () => { + [undefined, null, 0, 1, 123].forEach(serializedValue => { + const result: Color = parse(serializedValue); + assert.strictEqual(result, Color.Blue); + }); + }); + + it('returns a parser that returns the default value if passed a string not in the enum', () => { + ['', 'garbage', 'RED'].forEach(serializedValue => { + const result: Color = parse(serializedValue); + assert.strictEqual(result, Color.Blue); + }); + }); + + it('returns a parser that parses enum values', () => { + const result: Color = parse('green'); + assert.strictEqual(result, Color.Green); + }); + }); +}); diff --git a/ts/util/enum.ts b/ts/util/enum.ts new file mode 100644 index 000000000..2e357dd45 --- /dev/null +++ b/ts/util/enum.ts @@ -0,0 +1,30 @@ +// Copyright 2021 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +/** + * Turn a string (like "green") into its enum type (like `Color.Green`). Useful when + * deserializing strings into enum types. + * + * It only supports enums with string values. It could theoretically support more, but: + * + * 1. It's easier to debug. A serialized value of "Green" is easier to associate with + * `Color.Green` than a serialized value of 2. + * 2. TypeScript's default uses numeric enum values. Because the stability of values is + * important and it's easy to mess up the stability of values (e.g., by reordering the + * enum), these are discouraged here. + * + * Again: no "hard" technical reason why this only supports strings; it's to encourage + * good behavior. + */ +export function makeEnumParser< + TEnumKey extends string, + TEnumValue extends string +>( + enumToParse: Record, + defaultValue: TEnumValue +): (value: unknown) => TEnumValue { + const enumValues = new Set(Object.values(enumToParse)); + const isEnumValue = (value: unknown): value is TEnumValue => + typeof value === 'string' && enumValues.has(value); + return value => (isEnumValue(value) ? value : defaultValue); +}