Skip to content

Instantly share code, notes, and snippets.

@ecthiender
Last active May 24, 2021 11:23
Show Gist options
  • Save ecthiender/fe91404d9350021d1a6d99df9a2d4554 to your computer and use it in GitHub Desktop.
Save ecthiender/fe91404d9350021d1a6d99df9a2d4554 to your computer and use it in GitHub Desktop.
/* The error handling library */
type ResultError<E> = {
errors: E,
trace?: any
}
type Success<T> = {
value: T
}
// this is basically THE type which is an instance of a Monad. This abstracts
// the notion of a computation which has failure
type Result<T,E> = ResultError<E> | Success<T>
/* Simple API for Monad abstraction. Just functions! */
// Because typescript doesn't have typeclasses I am implementing the 'bind'
// function a standalone function. This IS the function which implements the
// Monad (abstract) interface. This maybe later to changed to be a method of an
// (typescript) interface. But this works beautifully as it is now.
// bind :: Result a -> (a -> Result b) -> Result b
function bind<T,U,E>(res: Result<T,E>, nextFunc: (a: T) => Result<U,E>): Result<U,E> {
if ((res as ResultError<E>).errors) {
return (res as ResultError<E>)
}
else if((res as Success<T>).value) {
return nextFunc((res as Success<T>).value)
}
return (res as ResultError<E>)
}
// helper function: wraps a success value into a Result monad
function wrap<T,E>(val: T): Result<T,E> {
return {value: val}
}
// wraps an ApplicationError into a Result monad (the error branch)
function err<T,E>(e : E, trace?: any): Result<T,E> {
return {errors: e, trace}
}
/* end of Simple API */
/* Application related code */
type AppError = {
errorMessage: string,
errorCode: string
}
// convert functions that throw exceptions into functions that returns a
// Result<T>. It wraps the exception message into ApplicationError
function exceptionToResult<T,AppError>(fn: (...args: any[]) => T, code: string): (...args: any[]) => Result<T,AppError> {
return function(...args: any[]): Result<T,AppError> {
try {
const res = fn(args)
return wrap(res)
} catch (e) {
// don't know what am I doing here!
let except = ({errorCode: code, errorMessage: e.message} as unknown as AppError)
return err(except, e)
}
}
}
interface User {
userId?: UserId,
username: string,
email: string
}
type UserId = string
// monkey-patch (¯\_(ツ)_/¯) the in-built JSON.parse function into a safe function
// THIS IS NOT SAFE TO DO - other libraries depending on this function will break!
JSON.parse = exceptionToResult(JSON.parse, 'parse-error')
function parseJSON(jsonData: string): Result<Object,AppError> {
console.log('Parsing JSON', jsonData)
// can return an error
return JSON.parse(jsonData)
}
// validates the user fields, can throw ValidationError or return the User
function validateUser(user: Object): Result<User,AppError> {
console.log('validating user', user);
if (user.hasOwnProperty('username') && user.hasOwnProperty('email')) {
return wrap(user as User)
}
return err({errorCode: "validation-error", errorMessage: "username and email not found in payload"})
}
function saveUser(user: User): Result<UserId,AppError> {
console.log('saving user in the db', user)
// lets make this function fail randomly
const choices = ['success', 'failure']
var choice = choices[Math.floor(Math.random() * choices.length)];
// success return some user id value
if (choice === 'success') {
return wrap("0a1b2c3d4e5f")
}
// error
return err({errorCode: 'database-error', errorMessage: 'saving user failed'})
}
// in this function you can keep chaining function calls using bind. If any of
// them fails this function will immediately return from the computation
function createUserService(jsonData: string): Result<UserId,AppError> {
console.log(jsonData)
let user = parseJSON(jsonData)
let validatedUser = bind(user, validateUser)
let userId = bind(validatedUser, saveUser)
return userId
}
// TODO: an ideal API might be something like:
// new MonadService(jsonData)
// .bind(parseJSON)
// .bind(validateUser)
// .bind(saveUser)
// this will fail at the parse json
let res = createUserService('invalid-json')
console.log('Result 1: ', res)
// this will faily randomly in saveUser
res = createUserService('{"username": "bond", "email": "[email protected]"}')
console.log('Result 2: ', res)
// this will fail in validation
res = createUserService('{"name": "bond", "email": "[email protected]"}')
console.log('Result 3: ', res)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment