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

fix(utils): omit computedDefault of empty objects #2849

Closed
wants to merge 1 commit into from

Conversation

ranihorev
Copy link
Contributor

@ranihorev ranihorev commented May 3, 2022

Reasons for making this change

Fixes #2150 and #2708.
See this playground

If there are no defaults, computedDefault will still create an empty object here. As a result, ajv assumes that the field should exist and is failing the validation if it has any required fields.

Copy link
Member

@epicfaace epicfaace left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests that explicitly test for #2150 (and maybe #2708)?

@ranihorev
Copy link
Contributor Author

ranihorev commented May 3, 2022

The test I added was explicitly for #2150, I just changed the field names from uuid to a,b,c :)

EDIT: before my changes the state used to be {value: {}}

@epicfaace
Copy link
Member

@ranihorev #2150 is about "if object field is not required and left empty then form can be submitted without error". The test is about default values. While the test may indeed reflect the issue, can we add a Form.js-level test that actually steps through the user-level process of this? i.e., it should leave the object field empty, the user should try to submit, there should be no error.

Can you also add a test for #2708 so we can see if it fixes that issue too?

@ranihorev
Copy link
Contributor Author

Sure, I misunderstood you earlier, sorry :)
I'll give it a try

@ranihorev
Copy link
Contributor Author

It was a bit more tricky than I expected, but I added the test (fixed my PR as well).
I also had to update ArrayField_test - I don't think that we should create an empty object array if nothing is selected.

if (includeUndefinedValues || computedDefault !== undefined) {
if (
includeUndefinedValues ||
(typeof computedDefault === "object"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is hard to parse. Can we split it into two if statements?

if (typeof computedDefault === "object") {// if statement here}
...
else {// other if statement here }

And also comment out what each branch means

Copy link
Contributor Author

@ranihorev ranihorev May 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the code. It's more verbose but indeed easier to read.

};
expect(getDefaultFormState(schema, {})).eql({});
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about a test for #2708?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot to update the description. I review #2708 and the two issues are identical as far as I can tell.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So -- that might be correct, but we should still add a test that explicitly tests for the scenario in #2708 -- i.e., tries to perform form validation and then ensures that the validation is working.

@ranihorev
Copy link
Contributor Author

@epicfaace when is the next release of package? trying to see if we can get this fix on time :)

@epicfaace
Copy link
Member

@epicfaace when is the next release of package? trying to see if we can get this fix on time :)

Not sure yet

@ranihorev
Copy link
Contributor Author

ranihorev commented May 5, 2022 via email

@ranihorev
Copy link
Contributor Author

I somehow stashed the Form level tests I wrote instead of pushing them. Sorry about that.
@epicfaace any ideas why includeUndefinedValues is set true in the validation? I'm seeing this message in the caller but it doesn't explain why
// Include form data with undefined values, which is required for validation.

@ranihorev
Copy link
Contributor Author

I think that includeUndefinedValues is not the right behavior for an object. It's causing us to create an empty object and the validation to fail. I think that it was added to allow setting extraErrors but it feels wrong. What do you think?

@ranihorev
Copy link
Contributor Author

ranihorev commented Jul 5, 2022

I think that includeUndefinedValues is not the right behavior for an object. It's causing us to create an empty object and the validation to fail. I think that it was added to allow setting extraErrors but it feels wrong. What do you think?

@epicfaace I'd love to get your help. I just noticed that my PR could cause a breaking change - if we stop initializing every object to an empty object, we won't be able to set custom errors on nested field (for example, see this). I don't think that it's a big concern for most users, but maybe I'm wrong.

We can make it work if we switch from using only the formData in customValidate to combining it with the schema

@heath-freenome
Copy link
Member

heath-freenome commented Jul 8, 2022

I think that includeUndefinedValues is not the right behavior for an object. It's causing us to create an empty object and the validation to fail. I think that it was added to allow setting extraErrors but it feels wrong. What do you think?

@ranihorev So we are actively working on v5 (see the rjsf-v5 branch) which has a bunch of breaking changes... including refactoring utils.js into a new @rjsf/utils package and the validator.js into @rjsf/validator-ajv6. So these changes will likely not get merged into v4. You are welcome to create a change list against that branch.

And yes, in order to properly build the schema for custom validation, the includeUndefinedValues is provided to build the FormValidation objects passed to the customValidator function. This functionality is essential... And yes we could defer creating empty objects in the formData only for calling custom validation. But we would need that behavior and it cannot be removed from computeDefaults() so if you do provide a fix to the rjsf-v5 branch, it would have to maintain that capability.

@ranihorev
Copy link
Contributor Author

@heath-freenome thanks for letting me know. I forked the repo so I'm not in a rush. I'll try moving this PR over to rjsf-v5.
btw, will the be any further work on v4? asking cause I have another PR

@heath-freenome
Copy link
Member

@heath-freenome thanks for letting me know. I forked the repo so I'm not in a rush. I'll try moving this PR over to rjsf-v5. btw, will the be any further work on v4? asking cause I have another PR

@ranihorev Sorry for the late reply to your question... no, V4 is locked. You can port your other PR to the v5 branch as well

@heath-freenome heath-freenome added the v5 refactor Needs refactor due to v5 breaking changes label Aug 29, 2022
@heath-freenome
Copy link
Member

@ranihorev Can you port this change, the tests to the @rjsf/utils package and also update the CHANGELOG.md?

@ranihorev
Copy link
Contributor Author

ranihorev commented Sep 8, 2022 via email

@heath-freenome
Copy link
Member

I can but I'm still not sure how we want to treat custom validations in this case. What is the expected behavior?

On Wed, Sep 7, 2022 at 19:36 Heath C @.> wrote: @ranihorev https://github.com/ranihorev Can you port this change, the tests to the @rjsf/utils package and also update the CHANGELOG.md? — Reply to this email directly, view it on GitHub <#2849 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEC2ORLCT73EP5RHMBOTTELV5DACPANCNFSM5U7KLQQA . You are receiving this because you were mentioned.Message ID: @.>

@epicfaace Do you have an opinion?

@heath-freenome
Copy link
Member

heath-freenome commented Sep 14, 2022

I can but I'm still not sure how we want to treat custom validations in this case. What is the expected behavior?

@ranihorev Here is what I propose... In both of the validators, let's try moving

   const newFormData = getDefaultFormState<T>(
      this,
      schema,
      formData,
      rootSchema,
      true
    ) as T;

to right before the customValidate(...) using the formData in the this.ajv.validate() call.

I've sanity checked the tests with that change and it seems to work. Then make your change in the utils so that it continues to respect the includeUndefinedValues flag.

@epicfaace
Copy link
Member

I can but I'm still not sure how we want to treat custom validations in this case. What is the expected behavior?

On Wed, Sep 7, 2022 at 19:36 Heath C @.> wrote: @ranihorev https://github.com/ranihorev Can you port this change, the tests to the @rjsf/utils package and also update the CHANGELOG.md? — Reply to this email directly, view it on GitHub <#2849 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEC2ORLCT73EP5RHMBOTTELV5DACPANCNFSM5U7KLQQA . You are receiving this because you were mentioned.Message ID: @.>

Sorry, I'm not clear what you're asking?

@heath-freenome
Copy link
Member

I can but I'm still not sure how we want to treat custom validations in this case. What is the expected behavior?

On Wed, Sep 7, 2022 at 19:36 Heath C @.> wrote: @ranihorev https://github.com/ranihorev Can you port this change, the tests to the @rjsf/utils package and also update the CHANGELOG.md? — Reply to this email directly, view it on GitHub <#2849 (comment)>, or unsubscribe https://github.com/notifications/unsubscribe-auth/AEC2ORLCT73EP5RHMBOTTELV5DACPANCNFSM5U7KLQQA . You are receiving this because you were mentioned.Message ID: _@**.**_>

Sorry, I'm not clear what you're asking?

@epicfaace Check out my proposal in comments above

@ranihorev
Copy link
Contributor Author

I can but I'm still not sure how we want to treat custom validations in this case. What is the expected behavior?

@ranihorev Here is what I propose... In both of the validators, let's try moving

   const newFormData = getDefaultFormState<T>(
      this,
      schema,
      formData,
      rootSchema,
      true
    ) as T;

to right before the customValidate(...) using the formData in the this.ajv.validate() call.

I've sanity checked the tests with that change and it seems to work. Then make your change in the utils so that it continues to respect the includeUndefinedValues flag.

@heath-freenome to make sure we're on the same page: you are suggesting to run this.ajv.validate(schema, formData); on formData instead of newFormData and only use the newFormData for the custom validation? Am I right?

I wonder if we should call getDefaultFormState twice - once with includeUndefinedValues = false for the regular validation and once with true for the custom validation

@heath-freenome
Copy link
Member

@heath-freenome to make sure we're on the same page: you are suggesting to run this.ajv.validate(schema, formData); on formData instead of newFormData and only use the newFormData for the custom validation? Am I right?

I wonder if we should call getDefaultFormState twice - once with includeUndefinedValues = false for the regular validation and once with true for the custom validation

There is some sense to that as well... Go with that.

@ranihorev
Copy link
Contributor Author

I started without computing it twice. It works great in the UI, but it breaks a lot of tests in utils as they all assume (incorrectly IMO) that those nested default objects will be generated. My plan is to fix them by manually setting includeUndefinedValues to true

@ranihorev
Copy link
Contributor Author

I found a bug in my PR :(
oneOf properties with default values are being discarded. For example:

{
          type: "array",
          items: {
            properties: {
              foo: {
                type: "object",
                properties: {
                  name: {
                    type: "string",
                  },
                },
                dependencies: {
                  name: {
                    oneOf: [
                      {
                        properties: {
                          name: {
                            enum: ["first"],
                          },
                          grade: {
                            type: "string",
                            default: "A",
                          },
                        },
                      },
                      {
                        properties: {
                          name: {
                            enum: ["second"],
                          },
                          grade: {
                            type: "string",
                            default: "B",
                          },
                        },
                      },
                    ],
                  },
                },
              },
            },
          },
        }

I'll look for a solution, but if anyone has an idea, please let me know :)

@heath-freenome
Copy link
Member

I found a bug in my PR :( oneOf properties with default values are being discarded. For example:

...

I'll look for a solution, but if anyone has an idea, please let me know :)

Nothing comes to mind at the moment :(

@zmagauina-fn
Copy link
Contributor

I used this PR as a starting point to make my own: #3287

@ranihorev
Copy link
Contributor Author

I used this PR as a starting point to make my own: #3287

Thanks for picking this up!

@heath-freenome
Copy link
Member

This was fixed in #3287

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
awaiting changes v5 refactor Needs refactor due to v5 breaking changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Non-required object field can't be left empty if at least one its property is required.
4 participants