Created
July 2, 2019 15:18
-
-
Save jamesdabbs/e675d511ec67c984f123be174a51bef7 to your computer and use it in GitHub Desktop.
Refactoring a `sendPasswordReset` to be a (monadic) chain
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import uuid from 'uuid/v4' | |
import { | |
Envelope, Token, User, | |
allUsers, deliverEmail, prompt, saveToken | |
} from '../util' | |
const promptFor = (message: string): string => { | |
console.log(message) | |
return prompt() | |
} | |
const findUser = (identity: string): User | undefined => { | |
for (let user of allUsers()) { | |
if (user.name === identity || user.email === identity) { | |
return user | |
} | |
} | |
return | |
} | |
const generateResetToken = (user: User): Token => { | |
const token = { | |
uuid: uuid(), | |
user | |
} | |
saveToken(token) | |
return token | |
} | |
const deliverResetToken = (token: Token): Envelope => { | |
const email = { | |
to: token.user.email, | |
body: `Your password reset token is ${token.uuid}` | |
} | |
return deliverEmail(email) | |
} | |
export const sendPasswordReset = (message: string): Envelope | undefined => { | |
const identity = promptFor(message) | |
const user = findUser(identity) | |
if (!user) { return } | |
const token = generateResetToken(user) | |
return deliverResetToken(token) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import uuid from 'uuid/v4' | |
import { | |
Envelope, Token, User, | |
allUsers, deliverEmail, prompt, saveToken | |
} from '../util' | |
type Either<A, B> = Left<A, B> | Right<A, B> | |
class Left<A, B> { | |
_value: A | |
constructor(a: A) { | |
this._value = a | |
} | |
chain<C>(_: (b: B) => Either<A, C>): Either<A, C> { | |
return left(this._value) | |
} | |
map<C>(f: (b: B) => C): Either<A, C> { | |
return left(this._value) | |
} | |
} | |
class Right<A, B> { | |
_value: B | |
constructor(b: B) { | |
this._value = b | |
} | |
chain<C>(f: (b: B) => Either<A, C>): Either<A, C> { | |
return f(this._value) | |
} | |
map<C>(f: (b: B) => C): Either<A, C> { | |
return right(f(this._value)) | |
} | |
} | |
const left = <A>(a: A) => new Left<A, never>(a) | |
const right = <B>(b: B) => new Right<never, B>(b) | |
const promptFor = (message: string): Either<string, string> => { | |
console.log(message) | |
const response = prompt() | |
if (response.length === 0) { | |
return left('No email given') | |
} else { | |
return right(response) | |
} | |
} | |
const findUser = (identity: string): Either<string, User> => { | |
for (let user of allUsers()) { | |
if (user.name === identity || user.email === identity) { | |
return right(user) | |
} | |
} | |
return left(`Could not find user ${identity}`) | |
} | |
const generateResetToken = (user: User): Token => { | |
const token = { | |
uuid: uuid(), | |
user | |
} | |
saveToken(token) | |
return token | |
} | |
const deliverResetToken = (token: Token): Either<string, Envelope> => { | |
const email = { | |
to: token.user.email, | |
body: `Your password reset token is ${token.uuid}` | |
} | |
try { | |
return right(deliverEmail(email)) | |
} catch (e) { | |
return left(e.message) | |
} | |
} | |
const pipeK = (...fns) => x => fns.reduceRight((v, f) => v.chain(f), x) | |
export const sendPasswordReset = (message: string): Either<string, Envelope> => { | |
return pipeK( | |
promptFor, | |
findUser, | |
x => right(generateResetToken(x)), | |
deliverResetToken | |
)(message) | |
return promptFor(message) | |
.chain(findUser) | |
// .map(generateResetToken) | |
.chain(x => right(generateResetToken(x))) | |
.chain(deliverResetToken) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as ps from 'prompt-sync' | |
export const compose = (...fns) => (x) => fns.reduceRight((v, f) => f(v), x) | |
export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x) | |
export type User = { | |
id: number, | |
name: string, | |
email: string | |
} | |
export type Token = { | |
user: User | |
uuid: string | |
} | |
export type Email = { | |
to: string | |
body: string | |
} | |
export type Envelope = { | |
email: Email, | |
deliveredAt: Date | |
} | |
const users: User[] = [ | |
{ id: 1, name: 'James', email: 'james.dabbs@gmail.com' }, | |
{ id: 2, name: 'Rachel', email: 'rachel@example.com' }, | |
{ id: 3, name: 'Tim', email: 'tim.doherty@procore.com' }, | |
] | |
const tokens: Token[] = [] | |
let emailDeliveries: Envelope[] = [] | |
export const allUsers = () => users.slice(0) | |
export const deliverEmail = (email: Email) => { | |
const envelope = { email, deliveredAt: new Date() } | |
emailDeliveries.push(envelope) | |
return envelope | |
} | |
export const prompt = (): string => ps().prompt('') | |
export const saveToken = (token: Token) => { tokens.push(token) } | |
export const getDeliveries = () => emailDeliveries.slice(0) | |
export const clearDeliveries = () => emailDeliveries = [] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment