Skip to content

Commit

Permalink
feat(utils): add exhaustive switch case typeguard
Browse files Browse the repository at this point in the history
  • Loading branch information
karrui committed Nov 25, 2020
1 parent a967986 commit 3860e34
Show file tree
Hide file tree
Showing 4 changed files with 25 additions and 13 deletions.
15 changes: 6 additions & 9 deletions src/app/modules/form/form.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { err, errAsync, ok, okAsync, Result, ResultAsync } from 'neverthrow'
import { createLoggerWithLabel } from '../../../config/logger'
import { IFormSchema, IPopulatedForm, Status } from '../../../types'
import getFormModel from '../../models/form.server.model'
import { assertUnreachable } from '../../utils/assert-unreachable'
import { ApplicationError, DatabaseError } from '../core/core.errors'

import {
Expand Down Expand Up @@ -67,6 +68,10 @@ export const retrieveFullFormById = (
export const isFormPublic = (
form: IPopulatedForm,
): Result<true, FormDeletedError | PrivateFormError | ApplicationError> => {
if (!form.status) {
return err(new ApplicationError())
}

switch (form.status) {
case Status.Public:
return ok(true)
Expand All @@ -75,14 +80,6 @@ export const isFormPublic = (
case Status.Private:
return err(new PrivateFormError(form.inactiveMessage))
default:
logger.error({
message: 'Encountered invalid form status',
meta: {
action: 'isFormPublic',
formStatus: form.status,
form,
},
})
return err(new ApplicationError())
return assertUnreachable(form.status)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,11 @@ describe('submission.utils', () => {
})

it('should throw error if called with invalid responseMode', async () => {
// Arrange
const invalidResponseMode = 'something' as ResponseMode
// Act + Assert
expect(() => getModeFilter(undefined!)).toThrowError(
'getResponsesForEachField: Invalid response mode parameter',
expect(() => getModeFilter(invalidResponseMode)).toThrowError(
`This should never be reached in TypeScript: "${invalidResponseMode}"`,
)
})
})
Expand Down
7 changes: 5 additions & 2 deletions src/app/modules/submission/submission.utils.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import { BasicField, ResponseMode } from '../../../types'
import { assertUnreachable } from '../../utils/assert-unreachable'
import { FIELDS_TO_REJECT } from '../../utils/field-validation/config'

type ModeFilterParam = {
fieldType: BasicField
}

export const getModeFilter = (responseMode: ResponseMode) => {
export const getModeFilter = (
responseMode: ResponseMode,
): (<T extends ModeFilterParam>(responses: T[]) => T[]) => {
switch (responseMode) {
case ResponseMode.Email:
return emailModeFilter
case ResponseMode.Encrypt:
return encryptModeFilter
default:
throw Error('getResponsesForEachField: Invalid response mode parameter')
return assertUnreachable(responseMode)
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/app/utils/assert-unreachable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Typescript type guard that asserts that all switch cases are exhaustive.
* Use to get compile-time safety for making sure all the cases are handled.
*
* See:
* https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript
*/
export const assertUnreachable = (switchCase: never): never => {
throw new Error(`This should never be reached in TypeScript: "${switchCase}"`)
}

0 comments on commit 3860e34

Please sign in to comment.