busypeoples / spected

Validation library
MIT License
703 stars 32 forks source link

Check if parent is undefined, then check children #106

Open benneq opened 5 years ago

benneq commented 5 years ago
const data = {
    foo: undefined   // because it is optional, but it could contain an object:
// foo: {
//      bar: 42
// }
}

Now I'd like to validate foo.bar (if present), which works fine as long as foo is not undefined. If it's undefined, there'll be an exception in var keys = Object.keys(inputFn()).

Here's a real world example I'd like to use:

Is there some way to get this working?


The result objects then might looks like this:

{
  foo: true // if foo === undefined
}

{
  foo: ["must not be null"] // if foo === null
}

{
  foo: ["must be an object"] // if typeof foo !== 'object'
}

{
  foo: {
    bar: "must be over 9000!" // if foo.bar <= 9000
  }
}

{
  foo: {
    bar: true // if foo.bar > 9000
  }
}
busypeoples commented 5 years ago

@benneq I will try to provide a detailed answer to your problem tomorrow!

busypeoples commented 5 years ago

That is a very good example. What it would need is a way to define validation functions that validate the parent data and depending on the result either stop or continue validating. That would also need a different way of structuring the validation data.

Maybe we can add a special function to spected, that enables to define a chain of functions, that stop validating as soon as one validation functions fails, and continues to validate deeper nested data as long as the higher level data is valid.

Not sure how this might look like, but maybe something like this:

const rules = {
   foo: runValidations([
      [a => a !== null, "no null"], 
      [a => a !== undefined, "no undefined"], 
      {
        bar: barRules
      }
  ])
}
benneq commented 5 years ago

Is this somehow possible using a validation function?

I still don't really know how this ramda stuff works, though I write some pseudo code)

const validationRules = {
  foo: (value) => {
    if(value === undefined) {
      return true;  // results in "foo: true"
    } else if(value === null) {
      return "errmsg";  // results in "foo: ["errmsg"]"
    } else {
      return {
        bar: [[(value) => value > 9000, "errmsg"]] // results in "foo: { bar: ["errmsg"] }"
      }
    }
  }
}

The question is: Is it already possible to stop after the first error using some fancy ramda stuff?

I think the whole process should stay functional. That's what this lib is all about.

Some more pseudo code:

const rules = {
  foo: stopAtFirstError([
    [...],
    [...],
    [...]
  ])
}
busypeoples commented 5 years ago

No, that isn't possible right now. spected runs all functions and collects all messages at the moment. Let me see how we can solve this.

benneq commented 5 years ago

But I'm sure, you could write a function, that does this internally :)

A simple function that returns an array of validation rules. But this function does the validation itself ... kinda...

Like the stopAtFirstError I posted above. It won't give spected all 3 elements, but instead it will run each element itself (maybe using spected? 😃 ), and then returns only a single validation rule.


Would be really nice if you would help with the TypeScript stuff. Then I could play around with that stuff myself!

I just need the function signature(s?) for this:

const rules = {
  foo: (values) => ?? => ??? => ????
}
busypeoples commented 5 years ago

I will take a look at the types!

Sure, we can implement it internally, it should be straight forward to use, I think this is the important part.

benneq commented 5 years ago

Okay, I now found a way, to make this possible... It's not very nice, and it's even not working for now, because spected has some issues with null / undefined values 😢

Here's the code that (in my opinion) should work:

const data = {
    foo: undefined
}

/* this works:
const data = {
    foo: {
        bar: 42
    }
}
*/

const rules = {
    foo: (value) => {
        if(value === undefined) {
            return [[() => true, '']] // some rule that always returns true
        } else if (value === null) {
            return [[() => false, 'cannot be null']] // some rule that always returns false
        } else {
            return { // the validation for the nested object
                bar: [[(val) => val > 9000, 'must be over 9000']]
            }
        }
    }
}

const res = spected(rules, data);

If you could make some changes to spected, to get this working, it should be quite easy to make a custom function for this, so you can then simply write:

const rules = {
    foo: validIfUndefinedAndNotValidIfNullElseValidateTheFollowing(
      {
        bar: [[(val) => val > 9000, 'must be over 9000']]
      }
    )
}

Then it should also be possible to write some kind of stopOnFirstError function I guess.

busypeoples commented 5 years ago

The problem is that spected currently always expects an array or object as input, so the problem is recursion. I will see how we can improve this.

benneq commented 5 years ago

Yeah, I saw that.

Maybe there should be some "catch" at the beginning of the validate function, that checks if input is a "simple type". Then you may also be able to solve this: https://github.com/25th-floor/spected/issues/104

busypeoples commented 5 years ago

Yes #104 and #106 are the same issue. Will have a look at it later on.