Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - Objectizing and enriching validation responses #3

Open
sodiray opened this issue May 16, 2020 · 1 comment
Open

POC - Objectizing and enriching validation responses #3

sodiray opened this issue May 16, 2020 · 1 comment
Labels
duplicate This issue or pull request already exists enhancement New feature or request

Comments

@sodiray
Copy link

sodiray commented May 16, 2020

@antonioru I know your planning to tackle this this weekend. Couldn't get it off my mind and had some free time so heres a proof of concept script.

There's a lot of room for improvement and maybe some optimizations. Hope its helpful. Let me know what you think.

/**
* All around functional helpers
*/
const compose = (...funcs) => (...args) => funcs.slice(1).reduce((acc, fn) => fn(acc), funcs[0](...args))
const partial = (fn, ...args) => (...rest) => fn(...args, ...rest)



/**
* Function for converting validator function
* boolean results to an object
*/
const objectize = (doc, fn, value) => ({
  ...doc,
  valid: fn(value),
  value
})


/**
* When composing many validator function this will
* aggregate into passed and failed results
*/
const aggregateResults = (results) => ({
  passed: results.filter(r => r.valid), 
  failed: results.filter(r => !r.valid)
})


/**
* Some more helpers for composing and/or validations
*/
const applyDoc = (doc) => (result) => ({ ...result, ...doc })
const applyValue = (value) => (result) => ({ ...result, value })
const runValidators = (validators) => (value) => validators.map(validator => validator(value))


/**
* Determines if a result of composed functions
* is valid or not
*/
const orValid = (result) => ({ ...result, valid: result.passed.length > 0 })
const andValid = (result) => ({ ...result, valid: result.failed.length === 0 })


/**
* HOF used to take simple bool validation functions and
* convert to full object style validators
*/
const validator = (fn) => (doc) => partial(objectize, doc, fn)

// const orValidator = (...validators) => (doc) => value => orValid(applyDoc(doc)(aggregateResults(runValidators(validators)(value))))
const orValidator = (...validators) => (doc) => (value) => compose(
  runValidators(validators),
  aggregateResults,
  applyDoc(doc),
  applyValue(value),
  orValid)(value)

const andValidator = (...validators) => (doc) => (value) => compose(
  runValidators(validators), 
  aggregateResults,
  applyDoc(doc),
  applyValue(value),
  andValid)(value)


/**
* Same as before - bool validator functions. Some of these
* abbreviated for POC sake. Validators that were previously
* composed at this level would now be composed further
* below as validators
*/
const ofClass = (name, value) => Object.prototype.toString.call(value) === `[object ${name}]`
const isFalse = value => value === false
const isZero = value => value === 0
const isNumber = partial(ofClass, 'Number')
const biggerThan = number => value => value > number
const smallerThan = number => value => value < number


/**
* Object style validators composed with
* the core bool validators and descripting
* objects
*/
const isZeroValidator = validator(isZero)({
  name: 'isZero'
})

const isFalseValidator = validator(isFalse)({
  name: 'isFalse'
})

const isNumberValidator = validator(isNumber)({
  name: 'isNumber'
})

const biggerThanValidator = (min) => validator(biggerThan(min))({
  name: 'biggerThan',
  expected: `number bigger than ${min}`
})

const smallerThanValidator = (max) => validator(smallerThan(max))({
  name: 'smallerThan',
  expected: `number smaller than ${max}`
})

const isFalsyValidator = orValidator(isZeroValidator, isFalseValidator)({
  name: 'isFalsy'
})

const betweenValidator = (min) => (max) => andValidator(biggerThanValidator(min), smallerThanValidator(max))({
  name: 'betweenValidator'
})



/**
*
* Some POC Tests :smile:
*
*/

const assert = (condition) => {
  if (!condition) throw 'Assertion failed'
}


const isZeroResult = isZeroValidator(0)
console.log('isZeroResult ===>', isZeroResult)
assert(isZeroResult.name === 'isZero')
assert(isZeroResult.valid === true)
assert(isZeroResult.value === 0)

const isFalseResult = isFalseValidator(true)
console.log('isFalseResult ===>', isFalseResult)
assert(isFalseResult.name === 'isFalse')
assert(isFalseResult.valid === false)
assert(isFalseResult.value === true)

const biggerThanResult = biggerThanValidator(10)(50)
console.log('biggerThanResult ===>', biggerThanResult)
assert(biggerThanResult.name === 'biggerThan')
assert(biggerThanResult.valid === true)
assert(biggerThanResult.value === 50)
assert(biggerThanResult.expected === 'number bigger than 10')

const isFalsyResult = isFalsyValidator(0)
console.log('isFalsyResult ===>', isFalsyResult)
assert(isFalsyResult.name === 'isFalsy')
assert(isFalsyResult.valid === true)
assert(isFalsyResult.value === 0)
assert(isFalsyResult.passed[0].name === 'isZero')

const betweenResult = betweenValidator(10)(20)(9)
console.log('betweenResult ===>', betweenResult)
assert(betweenResult.name === 'betweenValidator')
assert(betweenResult.valid === false)
assert(betweenResult.value === 9)
assert(betweenResult.passed[0].name === 'smallerThan')

Also, wasn't sure how to share this with you... hope issue is ok 👍❓

@antonioru
Copy link
Owner

antonioru commented May 16, 2020

Hi @rayepps

first let me tell you, your code is absolutely brilliant, you must be a very talented developer!
I've developed something similar by using a monad to compose the responses, you can find my solution in the branch feature/monadic-response (of course it's way worse than this)

If you don't mind, I'll respond you properly in the #1 issue, so we'll keep the discussion there 😉


PS: in case you need to share anything with me in future, and I would be glad of it, my email address is:

my own name (antonio) AT my own company name (beautifulinteractions) .com ))

@antonioru antonioru added duplicate This issue or pull request already exists enhancement New feature or request labels May 16, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
duplicate This issue or pull request already exists enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants