spected icon indicating copy to clipboard operation
spected copied to clipboard

Check if parent is undefined, then check children

Open benneq opened this issue 5 years ago • 10 comments

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:

  • If foo === undefined, it is VALID.
  • If foo === null, it is INVALID: "must not be null"
  • If typeof foo !== 'object', it is INVALID: "must be an object"
  • Now that we're sure that it's an object, we can proceed to validate: If foo.bar <= 9000, then bar is INVALID: "must be over 9000!"

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
  }
}

benneq avatar Jan 15 '19 20:01 benneq

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

busypeoples avatar Jan 15 '19 22:01 busypeoples

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
      }
  ])
}

busypeoples avatar Jan 17 '19 12:01 busypeoples

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([
    [...],
    [...],
    [...]
  ])
}

benneq avatar Jan 17 '19 12:01 benneq

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.

busypeoples avatar Jan 17 '19 12:01 busypeoples

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) => ?? => ??? => ????
}

benneq avatar Jan 17 '19 12:01 benneq

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.

busypeoples avatar Jan 17 '19 12:01 busypeoples

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.

benneq avatar Jan 17 '19 14:01 benneq

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.

busypeoples avatar Jan 17 '19 14:01 busypeoples

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

benneq avatar Jan 17 '19 14:01 benneq

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

busypeoples avatar Jan 17 '19 14:01 busypeoples