apache / apache/grails-core

Field error count grows exponentially on GORM object with certain domains

Open
#11,128 5 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.