busypeoples / spected

Validation library
MIT License
703 stars 32 forks source link

Validation on the root element #104

Open benneq opened 5 years ago

benneq commented 5 years ago

I want to validate a single (non-object) value, like this:

spected(
    [[ isNotEmpty, "must not be empty" ]],
    "foo"
);

I get the following error:

Uncaught TypeError: Invalid attempt to destructure non-iterable instance

Another use case would be something like this:

const obj = null; (or undefined, or anything else)
spected(
    [[ val => typeof val === 'object', "must be an object" ]],
    obj
)

Or maybe: Is object or array? (both are valid). And then apply validation either to the object, or to all elements of the array.

busypeoples commented 5 years ago

Thanks for the feedback, will try to see how we can approach this as soon as I have some time. Should find some time this week!

benneq commented 5 years ago

Thank you! :)

I guess root validation would basically just the code "within" the reduce of the validate method.

Something like this might work (pseudo code):

const validate = curry((successFn: Function, failFn: Function, spec: Object, input: Object): Object => {
  const inputFn = typeof input === 'function' ? input : (key?: string) => key ? input : input
  const input = inputFn();

  if(typeof input === 'object') {
    const keys = Object.keys(inputFn())
    return reduce((result, key) => {
      // ... just like it was before
    }
  } else {
    // here comes the code from within the `reduce` function
    // i just removed all the "key" and "...result" stuff
    // i've not tested this at all 
    const inputObj = input;
    const value = inputObj
    const predicates = spec
    if (Array.isArray(predicates)) {
      return transform(() => successFn(value), failFn, map(f => runPredicate(f, value, inputObj, key), predicates)
    } else if (typeof predicates === 'object') {
      return validate(successFn, failFn, predicates, value)
    } else if (typeof predicates === 'function') {
      const rules = predicates(value)
      return validate(successFn, failFn, rules, value)
    } else {
      return successFn([])
    }
  }
})

Btw. could you make the const inputFn line a bit better formatted? I still don't understand what it does 😆 Here's how I read it:

const inputFn = typeof input === 'function' // if input === 'function'
    ? input // then return input
    : (key?: string) => key // else if ???
    ? input // then return input
    : input // else return input

The "else if" part doesn't make sense to me. Or maybe (more likely) I'm just reading it wrong

Using this formatting, ternary operators (even nested) are really easy to read:

const res = a === null                  // if a === null
    ? 'null'                            // then return 'null'
    : typeof a === 'string'             // else if a is a string
    ? a === 'foo'                       // then check if a === 'foo'
        ? 'it is foo'                   // if that's true, return 'it is foo'
        : 'it's not foo, but a string'  // if that's not true, return this
    : 'not a string, and not null';     // else return this

// equivalent:
if(a === null) {
  return 'null'
} else if(typeof a === 'string') {
  if(a === 'foo') {
    return 'it is foo'
  } else {
    return 'it's not foo, but a string'
  }
} else {
  return 'not a string, and not null'
}