ealush / vest

Vest ✅ Declarative validations framework
https://vestjs.dev/
MIT License
2.5k stars 85 forks source link

Validate dynamic keys #711

Closed danielleng closed 2 years ago

danielleng commented 2 years ago

Hi, is there a way I can validate data structures with dynamically generated names (keys) ?

For example, lets say I have dynamically generated input fields in my form, and the resulting data structure of the form's data before submission to the server will look like the following:

formData: {
  "users[0][incomeRange]": "13000",
  "users[0][residentialStatus][]": "foreigner",
  "users[0][twitter]": "test@test.com",
  "users[0][userContactNumber]": "12345678",
  "users[1][incomeRange]": "15000",
  "users[1][residentialStatus][]": "citizen",
  "users[1][twitter]": "test@u.com",
  "users[1][userContactNumber]": "87654321"
}

As per above, the name for each input is dynamic, but follow a fixed format: formName[index][fieldName]

The idea here is that the validation rules are the same for the same fieldName, e.g. "users[0][incomeRange]" and "users[1][incomeRange]" have the same test.

The test() function in the Vest library only supports a string for the field name parameter. Would anyone be able to recommend a way to validate this type of data structure ?

ealush commented 2 years ago

Hey @danielleng, thank you for this issue.

I believe that the feature you're after is test.each. It allows you to specify a list of items to be passed as your test parameters, then you can use these values both inside your field name, description and test body.

https://vestjs.dev/#/./test?id=testeach-for-dynamically-creating-tests-from-a-table

/*
const data = {
  products: [
    ['Game Boy Color', 25],
    ['Speak & Spell', 22.5],
    ['Tamagotchi', 15],
    ['Connect Four', 7.88],
  ]
}
*/

const suite = create('store_edit', data => {
  test.each(data.products)(
    name => name,
    'Price must be numeric and above zero.',
    (_, price) => {
      enforce(price).isNumeric().greaterThan(0);
    }
  );
});

In the above example, we're iterating over the dynamically generated products array. For each item, we use the product name as the field name property, and use the price of the product as the value to be validated.

Is that similar to what you are after?

danielleng commented 2 years ago

Hi Ealush,

Thanks for the reply! Really appreciate the help.

Ideally I didn't want to have to transform the form data any further than what it is now. I found another solution that's probably not as performant, as it creates a new suite each time a dynamic field is added to the form. But after some tests, it works fine.

    function validate(fieldNamePrefix, count) {
      return vest.create('DynamicForm', (formData) => {
        for (let i = 0; i < count; i++) {
          const email = `${fieldNamePrefix}[${i}][email]`;
          test(email, 'Must not be blank', () => {
            enforce(formData[email])
              .isNotEmpty();
          });
        }
      });
    }

This will create a test case for every email field in my form based on the field name template ${fieldNamePrefix}[${i}][email] So it will apply to: "users[0][email]": "test1@test.com", "users[1][email]": "test2@test.com", "users[2][email]": "test3@test.com", ... etc etc

Then finally:

const validationResult = validate('users', fieldGroups.length) (formData);
console.log(validationResult);

What do you think of this solution ? Is it a good idea to add a for-loop in the suite to dynamically create tests ?

ealush commented 2 years ago

Hey, sorry for replying late. I have been working hard on the new version of Vest, which introduces a new way of handling dynamic tests.

Version four of vest requires that you use its built-in field iteration function, each. The reason is that imperative iteration over tests (as you do with a for-loop) might remove the tests from existence in-between runs. This can confuse Vest, since it expects some tests to exists where other now reside. Using each in conjunction with key will make sure that Vest retains the knowledge of your tests and does not produce unpredictable results.

Since you're using version 3.x, you can keep using it your way, but before upgrading - you'll have to use each for iteration.

danielleng commented 2 years ago

Thanks for taking the time to respond! I will try out v4.