Skip to content

Commit

Permalink
💥 updates throws on validation errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jlarsson committed Dec 12, 2022
1 parent a65fd69 commit 2693cfc
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 42 deletions.
11 changes: 10 additions & 1 deletion src/about-me/person/updaters/__test/email-updater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('emailUpdater', () => {
},
})
})
it('ignores malformed emails', async () => {
it('throws on malformed emails', async () => {
const updater = createEmailUpdater()

const initialEmail: Email = {
Expand All @@ -52,6 +52,14 @@ describe('emailUpdater', () => {
verifiedDate: '2020-02-02',
}

await expect(() => updater.updatePerson(
makePerson({
email: initialEmail,
}),
{ email: 'badly formattted email' }))
.rejects
.toThrow('Validation error')
/*
const updated = await updater.updatePerson(
makePerson({
email: initialEmail,
Expand All @@ -61,5 +69,6 @@ describe('emailUpdater', () => {
expect(updated).toMatchObject({
email: initialEmail,
})
*/
})
})
25 changes: 10 additions & 15 deletions src/about-me/person/updaters/__test/phone-updater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,9 @@ describe('phoneUpdater', () => {
})

it.each([
[null],
['bad number'],
['123'],
])('ignores update on invalid number "%s"', async phoneNumber => {
])('throws on update with invalid number "%s"', async phoneNumber => {
const updater = createPhoneUpdater('SE')

const initialPhone: Phone = {
Expand All @@ -57,18 +56,16 @@ describe('phoneUpdater', () => {
verifiedDate: '2020-02-02',
}

const updated = await updater.updatePerson(
await expect(() => updater.updatePerson(
makePerson({
phone: initialPhone,
}),
{ phoneNumber })

expect(updated).toMatchObject({
phone: initialPhone,
})
{ phoneNumber }))
.rejects
.toThrow('Validation error')
})

it('ignores malformed phone numbers', async () => {
it('throws on malformed phone numbers', async () => {
const updater = createPhoneUpdater('SE')

const initialPhone: Phone = {
Expand All @@ -77,14 +74,12 @@ describe('phoneUpdater', () => {
verifiedDate: '2020-02-02',
}

const updated = await updater.updatePerson(
await expect(() => updater.updatePerson(
makePerson({
phone: initialPhone,
}),
{ phoneNumber: 'badly formattted phone number' })

expect(updated).toMatchObject({
phone: initialPhone,
})
{ phoneNumber: 'badly formattted phone number' }))
.rejects
.toThrow('Validation error')
})
})
35 changes: 23 additions & 12 deletions src/about-me/person/updaters/email-updater.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
import { randomUUID } from 'crypto'
import * as EmailValidator from 'email-validator'
import { Email, PersonUpdater } from '../types'
import { throwValidationErrorForField } from './validation-error'

const normalizeStringInput = (s?: string): string|null => (typeof s === 'string')
? s.trim()
: null

export const createEmailUpdater = (): PersonUpdater => ({
updatePerson: async (person, update) => ({
...person,
email: patchEmail(person?.email, update?.email?.trim()),
email: patchEmail(person?.email || null, getValidatedEmail(normalizeStringInput(update?.email))),
}),
})

const patchEmail = (email?: Email, update?: string): Email | null =>
update === ''
? null
: update && EmailValidator.validate(update) && (update !== email?.address)
? ({
address: update,
isVerified: false,
verifiedDate: null,
verificationCode: randomUUID(),
})
: email
const getValidatedEmail = (email: string): string|null|false => email && EmailValidator.validate(email) && email

const patchEmail = (email: Email, update: string|null|false): Email | null =>
update === null
? email
: update === ''
? null
: !update
? (throwValidationErrorForField('email'), email)
: update !== email?.address
? ({
address: update,
isVerified: false,
verifiedDate: null,
verificationCode: randomUUID(),
})
: email
37 changes: 23 additions & 14 deletions src/about-me/person/updaters/phone-updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,41 @@ import { getEnv } from '@helsingborg-stad/gdi-api-node'
import { parsePhoneNumber } from 'awesome-phonenumber'
import { randomUUID } from 'crypto'
import { PersonUpdater, Phone } from '../types'
import { throwValidationErrorForField } from './validation-error'

export const createPhoneUpdaterFromEnv = (regionCode = 'SE'): PersonUpdater => createPhoneUpdater(getEnv('PHONENUMBER_REGION', { fallback: regionCode }))

const normalizeStringInput = (s?: string): string|null => (typeof s === 'string')
? s.trim()
: null

export const createPhoneUpdater = (regionCode: string): PersonUpdater => ({
updatePerson: async (person, update) => ({
...person,
phone: patchPhone(person?.phone, getValidatedPhoneNumber(update?.phoneNumber?.trim(), regionCode)),
phone: patchPhone(person?.phone || null, getValidatedPhoneNumber(normalizeStringInput(update?.phoneNumber), regionCode)),
}),
})

const getValidatedPhoneNumber = (number: string, regionCode: string): string => {
if (number === '') {
return ''
if (!number) {
return number
}
const parsed = parsePhoneNumber(number || '', regionCode)
const parsed = parsePhoneNumber(number.trim(), regionCode)
return parsed?.isPossible() && parsed?.getNumber('e164')
}

const patchPhone = (phone?: Phone, update?: string): Phone | null =>
update === ''
? null
: update && (update !== phone?.number)
? ({
number: update,
isVerified: false,
verifiedDate: null,
verificationCode: randomUUID(),
})
: phone
update === null
? phone
: update === ''
? null
: !update
? (throwValidationErrorForField('phoneNumber'), phone)
: (update !== phone?.number)
? ({
number: update,
isVerified: false,
verifiedDate: null,
verificationCode: randomUUID(),
})
: phone
9 changes: 9 additions & 0 deletions src/about-me/person/updaters/validation-error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { GraphQLError } from 'graphql'

export const throwValidationErrorForField = (fieldName: string): void => {
throw new GraphQLError('Validation error', {
extensions: {
'validation-failed-for-field': fieldName,
},
})
}

0 comments on commit 2693cfc

Please sign in to comment.