From 321885f221038c388810130771372ff875e59728 Mon Sep 17 00:00:00 2001 From: Jarno Rantanen Date: Fri, 24 Apr 2020 11:45:30 +0300 Subject: [PATCH] Add stringLiteralUnionFields helper to model module. --- src/common/io.ts | 6 ++++++ src/common/model.ts | 26 +++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/common/io.ts b/src/common/io.ts index 92a3fccd..c5d7a1b5 100644 --- a/src/common/io.ts +++ b/src/common/io.ts @@ -1,4 +1,5 @@ import * as t from 'io-ts'; +import { LiteralType } from 'io-ts'; function defineRegexValidatedStringType(name: string, regex: RegExp) { const guard = (input: unknown): input is string => typeof input === 'string' && !!input.match(regex); @@ -41,3 +42,8 @@ export const generalWellbeing = t.union([t.literal('fine'), t.literal('impaired' // range of 0–99 days export const duration = defineRegexValidatedStringType('symptomsDuration', /^[0-9]{1,2}$/); + +// @example isStringLiteralType(fever) => false +// @example isStringLiteralType(fever.types[0]) => true +export const isStringLiteralType = (x: unknown): x is LiteralType => + x instanceof LiteralType && typeof x.value === 'string'; diff --git a/src/common/model.ts b/src/common/model.ts index f03bf78e..b83a7e3a 100644 --- a/src/common/model.ts +++ b/src/common/model.ts @@ -1,5 +1,6 @@ import { isRight } from 'fp-ts/lib/Either'; import * as t from 'io-ts'; +import { UnionType } from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { AbuseScore } from '../backend/abuseDetection'; import { @@ -10,16 +11,18 @@ import { gender, generalWellbeing, iso8601DateString, + isStringLiteralType, notAnswered, postalCode, uuidString, yesOrNo, } from './io'; +import { nonNullable } from './types'; // Because AWS Athena prefers lower-case column names (https://docs.aws.amazon.com/athena/latest/ug/tables-databases-columns-names.html), // we use snake case for some of these models, instead of camel case (https://en.wikipedia.org/wiki/Letter_case#Special_case_styles). -const responseFields = { +export const responseFields = { fever: fever, cough: cough, breathing_difficulties: yesOrNo, @@ -38,6 +41,7 @@ const responseFields = { gender: gender, postal_code: postalCode, }; +export const responseFieldKeys = (Object.keys(responseFields) as any) as Array; // this is theoretically unsafe (https://stackoverflow.com/a/55012175) but practically a lot safer than going with string[] ¯\_(ツ)_/¯ export const FrontendResponseModel = t.strict( { @@ -84,3 +88,23 @@ export function assertIs>(codec: C): (x: unknown) => t.T } }; } + +// Defines tuples describing all response fields that are simple unions of string literals. +// @example [ +// [ 'healthcare_contact', [ 'yes', 'no' ] ], +// [ 'general_wellbeing', [ 'fine', 'impaired', 'bad' ] ], +// ... +// ] +// What makes these fields special is that their values are easy to GROUP BY, SUM() etc in queries. +export const stringLiteralUnionFields: [string, string[]][] = responseFieldKeys + .map(key => + responseFields[key] instanceof UnionType + ? ([ + key, + (responseFields[key] as any).types // TODO: Assert that responseFields[key] is UnionType> instead (how?), to get rid of the awkward any + .map((t: unknown) => (isStringLiteralType(t) ? t.value : null)) + .filter(nonNullable), + ] as [string, string[]]) + : null, + ) + .filter(nonNullable);