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

Pass in payload relative to the data object when generating rules #264

Merged

Conversation

bentleyo
Copy link
Contributor

This PR attempts to solve the problem identified by @medvinator in #256 which is something our team has also run into.
If you want to dynamically build validation rules depending on the payload it's very difficult at the moment as the data object receives the full payload and doesn't know the path to its data.

E.g. a data object inside a collection won't know that the path to its data is collection.2.example and this makes rule generation very difficult to work with at the moment.

This PR approaches this problem by building the validation rules for each set of data. There's some pros and cons to this approach. The pros are that you can now build validation rules using the data relative to the object. However, collections with many many objects inside them will generate rules like:

collection.0.example, collection.1.example, collection.2.example etc.

In addition, if there's no data inside the collection no rule will be generated for the child field. E.g. no collection.*.example

I'm not sure of the best way to approach that problem and for our team the overheard in rule generation wouldn't be an issue and is worth the dynamic validation functionality.

This PR probably requires more tests and some code improvements, but I thought I'd create it to see what your thoughts are and start the discussion 😄

Thanks for creating an amazing package!

@bentleyo
Copy link
Contributor Author

If it's not possible to merge a change like this into the package would it be possible to do something like provide a choice of two different DataClassValidationRulesResolver classes and support both in the package?

The users could then bind the resolver of their choice. Not sure if that's a terrible idea or not!

@bentleyo bentleyo force-pushed the feature/relative-payload-validation branch from ffa8b01 to 3d10808 Compare November 23, 2022 02:04
@rubenvanassche
Copy link
Member

Hi @bentleyo,

First of all, thanks for the PR. I'm not going to merge it because creating so many duplicate rules feels a bit wrong. We use this package on large datasets and this PR would create an array of more than 400 rules which is a bit much.

That being said, I'm totally in with making the DataClassValidationRulesResolver configurable, so I would certainly merge a PR which adds such a functionality!

@bentleyo
Copy link
Contributor Author

@rubenvanassche thanks for your reply! No problem, I expected that this approach might be too far detached from how validation traditionally works in Laravel to be merged as-is into the package.

I still think this is something that might be really useful for situations when more dynamic validation is required, as the payload provided to rules isn't super useful at the moment and everything in a collection gets prefixed with .*. etc.

I'll have a think about it, but do you have any thoughts on how you would prefer the resolver to be configurable?
E.g. would you prefer the same resolver to be used and for this to be a setting in the config? Or do you think the better approach might be to have multiple resolvers and the choice of resolver be configured in the config?

Another approach I've thought of is potentially passing through the path to the DTO e.g. collection.0 along with the root payload to the rules method and providing some alternative way to add the rules avoiding the automatic prefix.

I'm open to suggestions, I haven't contributed much to open-source projects before.

@bentleyo
Copy link
Contributor Author

bentleyo commented Nov 24, 2022

I just realised that to provide the path to the DTO in the rules method it would be necessary to iterate over each item in collections, so it might not be preferable to have the overhead of that by default.

Would an option like 'relative_rules' => false, (by default) in the config which when gets set to true for this situation make sense? I'm trying to think of other names and scoped_roles is the other name that comes to mind.

Naming is hard!

@bentleyo bentleyo force-pushed the feature/relative-payload-validation branch from e40a449 to c5f7f07 Compare December 3, 2022 02:29
@bentleyo
Copy link
Contributor Author

bentleyo commented Dec 3, 2022

@rubenvanassche I have updated this PR so that relative_rule_generation is now configurable in the data.php config file. Let me know if you would consider this approach or require any changes.

@bentleyo bentleyo force-pushed the feature/relative-payload-validation branch from ec9f7e1 to ab1dd1d Compare December 4, 2022 23:56
@rubenvanassche
Copy link
Member

Hi @bentleyo,

Now going through the PR, my colleague told me about this gem: https://laravel.com/docs/9.x/validation#accessing-nested-array-data. Is this something we could use in this case?

@rubenvanassche
Copy link
Member

Looking good, one point if we can use the thing I've mentioned above and drop the 0.property, 1.property that would be great. Otherwise this can be merged!

@bentleyo
Copy link
Contributor Author

bentleyo commented Dec 7, 2022

@rubenvanassche I somehow managed to miss that feature, I'll have a look over the next day or so and see if there's a way I can utilise that functionality. Thanks for your reply!

@rubenvanassche
Copy link
Member

Thanks @bentleyo, let me know if something changes and I take another look!

@bentleyo
Copy link
Contributor Author

bentleyo commented Dec 8, 2022

@rubenvanassche I spent some time investigating this and have just pushed a potential solution.

Instead of making the functionality a global config setting I've managed to implement the functionality by creating a RelativeRuleData contract you can add to the data classes you want to have relative rule generation. Not sure of the best name for that contract, but I'm open to suggestions.

Some benefits of this new approach is that a huge array of rules is no longer generated and we're using the Rule::forEach functionality. To achieve this I've had to pass around a path to the data class in the payload, but I think that is a good feature / useful improvement.

Unfortunately I've had to extend the NestedRules class from Laravel. This is because Rule::fromEach can't be inside an array:

E.g. this would work:

'collection.*' => Rule::forEach(...),

However, this wouldn't:

'collection.*' => ['present', 'array', Rule::forEach(...)],

As I'm now using Rule::forEach to generate rules there are some failed github checks for Laravel 8 as the method didn't exist in that version. I'm not sure what we should do with that.

I've added some extra tests. Let me know what you think!

@bentleyo
Copy link
Contributor Author

@rubenvanassche I've added a check to see if the NestedRules class exists when relative rule generation is used. If it doesn't then an exception will be thrown that asks you to upgrade laravel.

This means that only this new feature requires laravel 9+ and the package can still be used on 8. The tests now pass 😄

@rubenvanassche
Copy link
Member

Thanks, @bentleyo this is great! I've restructured your code a bit but this is such a nice feature to have, thanks!

@rubenvanassche
Copy link
Member

Changed it even a little bit by removing the interface

@bentleyo
Copy link
Contributor Author

@rubenvanassche I just had a look at the changes you made. Super impressed! I really like the approach you took 😄

@rubenvanassche
Copy link
Member

Thanks, the original PR was also awesome, good work! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants