Field error count grows exponentially on GORM object with certain domains
- Dominant language
- Groovy
- Stars
- 2.9k
- Forks
- 975
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 92
Description
### Task List
- [x] Steps to reproduce provided
- [x] Stacktrace (if present) provided
- [x] Example that reproduces the problem uploaded to Github
- [x] Full description of the issue provided (see below)
### Steps to Reproduce
1. Create an application with an Organization, User, and Channel domain classes.
a. An organization `hasMany = [users: User]`
b. A user `hasOne = [channel: Channel]` and `belongsTo = [organization: Organization]`
c. A channel `belongsTo = [organization: Organization, owner: User`
2. Set up a scenario where a single user is invalid in an organization
3. Attempt validation on the organization
### Expected Behaviour
There should be one field error, something like `users[0].fieldName`
### Actual Behaviour
There are a number of field errors equal to 2^(organization.users.size() - 1). So for an organization of 4 users there are 8 field errors for 1 invalid user:
```
organization.errors.errorCount != 8
| | | |
| | 8 false
| org.grails.datastore.mapping.validation.ValidationErrors: 8 errors
| Field error in object 'nest.validation.Organization' on field 'users[0].username': rejected value [null]; codes [nest.validation.User.username.nullable.error.nest.validation.Organization.users[0].username,nest.validation.User.username.nullable.error.nest.validation.Organization.users.username,nest.validation.User.username.nullable.error.users[0].username,nest.validation.User.username.nullable.error.users.username,nest.validation.User.username.nullable.error.username,nest.validation.User.username.nullable.error.java.lang.String,nest.validation.User.username.nullable.error,user.username.nullable.error.nest.validation.Organization.users[0].username,user.username.nullable.error.nest.validation.Organization.users.username,user.username.nullable.error.users[0].username,user.username.nullable.error.users.username,user.username.nullable.error.username,user.username.nullable.error.java.lang.String,user.username.nullable.error,nest.validation.User.username.nullable.nest.validation.Organization.users[0].username,nest.validation.User.username.nullable.nest.validation.Organization.users.username,nest.validation.User.username.nullable.users[0].username,nest.validation.User.username.nullable.users.username,nest.validation.User.username.nullable.username,nest.validation.User.username.nullable.java.lang.String,nest.validation.User.username.nullable,user.username.nullable.nest.validation.Organization.users[0].username,user.username.nullable.nest.validation.Organization.users.username,user.username.nullable.users[0].username,user.username.nullable.users.username,user.username.nullable.username,user.username.nullable.java.lang.String,user.username.nullable,nullable.nest.validation.Organization.users[0].username,nullable.nest.validation.Organization.users.username,nullable.users[0].username,nullable.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[1].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[1].channel.organization.users[0].username,nullable.nest.validation.Organization.users[1].channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.username,nullable.users[1].channel.organization.users[0].username,nullable.users[1].channel.organization.users.username,nullable.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[2].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[2].channel.organization.users[0].username,nullable.nest.validation.Organization.users[2].channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.username,nullable.users[2].channel.organization.users[0].username,nullable.users[2].channel.organization.users.username,nullable.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[2].channel.organization.users[1].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[2].channel.organization.users[1].channel.organization.users[0].username,nullable.nest.validation.Organization.users[2].channel.organization.users[1].channel.organization.users.username,nullable.nest.validation.Organization.users[2].channel.organization.users.channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.channel.organization.users.username,nullable.users[2].channel.organization.users[1].channel.organization.users[0].username,nullable.users[2].channel.organization.users[1].channel.organization.users.username,nullable.users[2].channel.organization.users.channel.organization.users.username,nullable.users.channel.organization.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[3].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[3].channel.organization.users[0].username,nullable.nest.validation.Organization.users[3].channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.username,nullable.users[3].channel.organization.users[0].username,nullable.users[3].channel.organization.users.username,nullable.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[3].channel.organization.users[1].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[3].channel.organization.users[1].channel.organization.users[0].username,nullable.nest.validation.Organization.users[3].channel.organization.users[1].channel.organization.users.username,nullable.nest.validation.Organization.users[3].channel.organization.users.channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.channel.organization.users.username,nullable.users[3].channel.organization.users[1].channel.organization.users[0].username,nullable.users[3].channel.organization.users[1].channel.organization.users.username,nullable.users[3].channel.organization.users.channel.organization.users.username,nullable.users.channel.organization.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[3].channel.organization.users[2].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[3].channel.organization.users[2].channel.organization.users[0].username,nullable.nest.validation.Organization.users[3].channel.organization.users[2].channel.organization.users.username,nullable.nest.validation.Organization.users[3].channel.organization.users.channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.channel.organization.users.username,nullable.users[3].channel.organization.users[2].channel.organization.users[0].username,nullable.users[3].channel.organization.users[2].channel.organization.users.username,nullable.users[3].channel.organization.users.channel.organization.users.username,nullable.users.channel.organization.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
| Field error in object 'nest.validation.Organization' on field 'users[3].channel.organization.users[2].channel.organization.users[1].channel.organization.users[0].username': rejected value [null]; codes [nullable.nest.validation.Organization.users[3].channel.organization.users[2].channel.organization.users[1].channel.organization.users[0].username,nullable.nest.validation.Organization.users[3].channel.organization.users[2].channel.organization.users[1].channel.organization.users.username,nullable.nest.validation.Organization.users[3].channel.organization.users[2].channel.organization.users.channel.organization.users.username,nullable.nest.validation.Organization.users[3].channel.organization.users.channel.organization.users.channel.organization.users.username,nullable.nest.validation.Organization.users.channel.organization.users.channel.organization.users.channel.organization.users.username,nullable.users[3].channel.organization.users[2].channel.organization.users[1].channel.organization.users[0].username,nullable.users[3].channel.organization.users[2].channel.organization.users[1].channel.organization.users.username,nullable.users[3].channel.organization.users[2].channel.organization.users.channel.organization.users.username,nullable.users[3].channel.organization.users.channel.organization.users.channel.organization.users.username,nullable.users.channel.organization.users.channel.organization.users.channel.organization.users.username,nullable.username,nullable.java.lang.String,nullable]; arguments [username,class nest.validation.User]; default message [Property [{0}] of class [{1}] cannot be null]
nest.validation.Organization : 1
```
### Environment Information
- **Operating System**: Fedora 28 Workstation
- **Grails Version:** 3.2.13
- **JDK Version:** openjdk version "1.8.0_181"
- **Container Version (If Applicable):** N/A
### Example Application
- https://github.com/joemccall86/test-constraint-validation/tree/v3213
### Notes
* The bug only happens in at least an integration test (and obviously a running system). The unit test passes just fine
* The bug does _not_ appear in Grails 3.3.8
* Sample application master branch is Grails 3.3.8, and the v3213 branch is Grails 3.2.13.
Contributor guide
Research direction
Start with the linked example application's v3213 branch and reproduce the organization validation scenario in an integration test; compare it with the passing unit test and the Grails 3.3.8 master branch. Done means one invalid user produces one field error rather than exponentially duplicated errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- groovy
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100