Skip to content

Instantly share code, notes, and snippets.

@steenhansen
Last active June 20, 2022 20:04
Show Gist options
  • Save steenhansen/5a0dbad5388a79ebb900b257fc7a129c to your computer and use it in GitHub Desktop.
Save steenhansen/5a0dbad5388a79ebb900b257fc7a129c to your computer and use it in GitHub Desktop.
/**
* ES6 versions of Either monad used in FP in JS
* Author: Luis Atencio
* https://github.com/luijar/functional-programming-js/blob/master/src/model/monad/Either.js
* Validation from from Quildreen Motta's
* https://folktale.origamitower.com/api/v2.3.0/en/folktale.validation.html
*/
// Run the below examples in the console
const makeValidation = () => {
const the_failures = new Set();
function failure(the_object, an_error) {
the_failures.add(an_error.message);
return new Right(the_object);
}
function success(the_object){
return new Right(the_object);
}
class Either {
constructor(value) {
this._value = value;
}
getFailures() {
let the_errors = [...the_failures];
the_failures.clear();
return the_errors;
}
}
class Right extends Either {
map(f) {
return new Right(f(this._value));
}
verifyProp(f) {
return f(this._value);
}
tap(f) {
f(this._value);
return new Right(this._value);
}
};
return {failure, success};
}
const validation = makeValidation();
const BLANK_STR_OK = true;
const BLANK_STR_FAIL = false;
const STATES_REGEX = /^(AL|AK|AR|AZ|CA|CO|CT|DC|DE|FL|GA|HI|IA|ID|IL|IN|KS|KY|LA|MA|MD|ME|MI|MN|MO|MS|MT|NC|ND|NE|NH|NJ|NM|NV|NY|OH|OK|OR|PA|RI|SC|SD|TN|TX|UT|VA|VT|WA|WI|WV|WY)$/ig;
const THE_STATES = {'alabama': 'AL', 'alaska': 'AK', 'arizona': 'AZ', 'arkansas': 'AR', 'california': 'CA',
'colorado': 'CO', 'connecticut': 'CT', 'delaware': 'DE', 'district of columbia': 'DC',
'florida': 'FL', 'georgia': 'GA', 'hawaii': 'HI', 'idaho': 'ID', 'illinois': 'IL',
'indiana': 'IN', 'iowa': 'IA', 'kansas': 'KS', 'kentucky': 'KY', 'louisiana': 'LA',
'maine': 'ME', 'maryland': 'MD', 'massachusetts': 'MA', 'michigan': 'MI',
'minnesota': 'MN', 'mississippi': 'MS', 'missouri': 'MO', 'montana': 'MT',
'nebraska': 'NE', 'nevada': 'NV', 'new hampshire': 'NH', 'new jersey': 'NJ',
'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', 'north dakota': 'ND',
'ohio': 'OH', 'oklahoma': 'OK', 'oregon': 'OR', 'pennsylvania': 'PA',
'rhode island': 'RI', 'south carolina': 'SC', 'south dakota': 'SD', 'tennessee': 'TN',
'texas': 'TX', 'utah': 'UT', 'vermont': 'VT', 'virginia': 'VA', 'washington': 'WA',
'west virginia': 'WV', 'wisconsin': 'WI', 'wyoming': 'WY' };
const ZIP_REGEX = /^\d\d\d\d\d$/;
function decodeJson(some_json){
try {
const json_obj = JSON.parse(some_json);
return validation.success(json_obj);
} catch (json_error){
return validation.failure(json_error, json_error);
}
}
const checkState = key_name => address_object => {
const state_str = address_object[key_name];
const state_type = typeof state_str;
if (state_type === 'string'){
if (state_str.match(STATES_REGEX)){
return validation.success(address_object);
}
}
return validation.failure(address_object, Error (`State not valid '${state_str}'`));
}
const checkStr = (key_name, empty_str_ok) => address_object => {
const the_value = address_object[key_name];
if (the_value === null) {
return validation.failure(address_object, Error (`Key '${key_name}' is null`));
}
if (the_value === undefined) {
return validation.failure(address_object, Error (`Missing key of '${key_name}'`));
}
const str_type = typeof the_value;
if (str_type !== 'string'){
return validation.failure(address_object, Error (`Property '${key_name}' not a string, but '${str_type}'`));
}
if (!empty_str_ok && the_value.length === 0){
return validation.failure(address_object, Error (`Emtpy string for key '${key_name}'`));
}
return validation.success(address_object);
}
const nonEmptyStr = key_name => checkStr(key_name, BLANK_STR_FAIL);
const justStr = key_name => checkStr(key_name, BLANK_STR_OK);
const checkZip = key_name => address_object => {
const zip_str = address_object[key_name];
if (zip_str.match(ZIP_REGEX)){
return validation.success(address_object);
}
return validation.failure(address_object, Error (`Zip not valid '${zip_str}'`));
}
const toStrZip = key_name => address_object => {
const zip_val = address_object[key_name];
let new_zip;
if (typeof zip_val === 'number'){
const zip_str = zip_val.toString();
new_zip = zip_str.padStart(5, '0');
} else if (typeof zip_val === 'undefined'){
new_zip = '_undef_';
} else {
new_zip = zip_val.toString();
}
const obj_zip_str = Object.assign({}, address_object, {zip:new_zip});
return obj_zip_str;
}
const toShortState = key_name => address_object => {
const state_val = address_object[key_name];
if (typeof state_val === 'string'){
if (state_val in THE_STATES){
const short_state = THE_STATES[state_val];
const obj_2_state = Object.assign({}, address_object, {state:short_state});
return obj_2_state;
}
}
return address_object;
}
const validateAddress = addr_json_str => {
const the_failures = decodeJson(addr_json_str)
.verifyProp(nonEmptyStr('first'))
.verifyProp(nonEmptyStr('last'))
.verifyProp(justStr('apt'))
.verifyProp(nonEmptyStr('street'))
.tap(cur_object=>console.log(' tap:', cur_object))
.map(toShortState('state'))
.verifyProp(checkState('state'))
.map(toStrZip('zip'))
.verifyProp(checkZip('zip'))
.tap(cur_object=>console.log(' tap:', cur_object))
.getFailures();
return the_failures;
}
const showAddress = an_address => {
console.log('init:', an_address);
const address_errors = validateAddress(an_address);
console.log('errs:', address_errors);
console.log();
}
var good_addr = '{"first" : "Rodney", "last":"Dangerfield", "apt":"", "street":"Battlecreek", "state": "district of columbia", "zip":123 }';
var bad_first_state = '{"first" : "", "last":"Carlin", "apt":"", "street":"Battlecreek", "state": "New Utah", "zip":123 }';
var bad_all = '{"" : "George", "surname":"Carlin", "apt":"", "street":"", "state": "", "zip":"abc" }';
var bad_json = '_not_json_';
var the_addresses = [good_addr, bad_first_state, bad_all, bad_json];
var the_failures = the_addresses.map(showAddress);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment