Last active
May 24, 2021 11:23
-
-
Save ecthiender/fe91404d9350021d1a6d99df9a2d4554 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
/* 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