Graphql on a @GrailsCompileStatic domain class cause nullable constraint to be ignored
If you have a domain class that is marked with @GrailsCompileSatic and this class has a property with a constraint nullable: true, and if this class is enabled for GraphQL with a custom operation/query, then the nullable: true constraint behaves like nullable: false.
Steps to reproduce:
-
Create a new project (Tested with Grails 4.0.4 / 4.0.5 and their default gorm version) Add
compile "org.grails.plugins:gorm-graphql-plugin:2.0.0"dependency -
Create a domain class, say
MyDomainmarked with@GrailsCompileStaticand anullableproperty, like:
@GrailsCompileStatic
class MyDomain {
String name
static constraints = {
name nullable: true
}
}
- Add a custom GraphQL mapping which defines a simple query:
@GrailsCompileStatic
class MyDomain {
String name
static constraints = {
name nullable: true
}
static graphql = GraphQLMapping.build {
query("myQuery", Integer) {
dataFetcher(new DataFetcher() {
@Override
Object get(DataFetchingEnvironment environment) throws Exception {
return new Integer(0)
}
})
}
}
}
If you just put static graphql=true or static graphql = GraphQLMapping.build {} there's no issue.
- Now in BootStrap, you just create and save a new instance of that class leaving the nullable field empty, like:
class BootStrap {
def init = { servletContext ->
new MyDomain().save(failOnError: true)
}
def destroy = {
}
}
- Now you run with
gradlew bootRun. It is expected the instance to be persisted with no errors, because it has only one field, which isnull, and that field allowsnull. Instead, you see a validation exception:
grails.validation.ValidationException: Validation Error(s) occurred during save():
- Field error in object 'test.MyDomain' on field 'name': rejected value [null]; codes [test.MyDomain.name.nullable.error.test.MyDomain.name,test.MyDomain.name.null
able.error.name,test.MyDomain.name.nullable.error.java.lang.String,test.MyDomain.name.nullable.error,myDomain.name.nullable.error.test.MyDomain.name,myDomain.name.
nullable.error.name,myDomain.name.nullable.error.java.lang.String,myDomain.name.nullable.error,test.MyDomain.name.nullable.test.MyDomain.name,test.MyDomain.name.nu
llable.name,test.MyDomain.name.nullable.java.lang.String,test.MyDomain.name.nullable,myDomain.name.nullable.test.MyDomain.name,myDomain.name.nullable.name,myDomain
.name.nullable.java.lang.String,myDomain.name.nullable,nullable.test.MyDomain.name,nullable.name,nullable.java.lang.String,nullable]; arguments [name,class test.My
Domain]; default message [O campo [{0}] da classe [{1}] nÒo pode ser vazio]
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:80)
at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:74)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1732)
at groovy.lang.MetaClassImpl.invokeConstructor(MetaClassImpl.java:1556)
at org.codehaus.groovy.runtime.InvokerHelper.invokeConstructorOf(InvokerHelper.java:1042)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.newInstance(DefaultGroovyMethods.java:17159)
at org.grails.orm.hibernate.AbstractHibernateGormInstanceApi.save(AbstractHibernateGormInstanceApi.groovy:134)
at org.grails.datastore.gorm.GormEntity$Trait$Helper.save(GormEntity.groovy:153)
at test.MyDomain.save(MyDomain.groovy)
at test.MyDomain.save(MyDomain.groovy)
at org.grails.datastore.gorm.GormEntity$save.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:115)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:127)
at testconstraintnotnull.BootStrap$_closure1.doCall(BootStrap.groovy:10)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:101)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:323)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:263)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1099)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at groovy.lang.Closure.call(Closure.java:405)
at groovy.lang.Closure.call(Closure.java:399)
at grails.util.Environment.evaluateEnvironmentSpecificBlock(Environment.java:594)
at grails.util.Environment.executeForEnvironment(Environment.java:587)
at grails.util.Environment.executeForCurrentEnvironment(Environment.java:563)
at org.grails.web.servlet.boostrap.DefaultGrailsBootstrapClass.callInit(DefaultGrailsBootstrapClass.java:74)
at org.grails.web.servlet.context.GrailsConfigUtils.executeGrailsBootstraps(GrailsConfigUtils.java:83)
at org.grails.plugins.web.servlet.context.BootStrapClassRunner.onStartup(BootStrapClassRunner.groovy:56)
at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy:269)
at grails.boot.config.GrailsApplicationPostProcessor.onApplicationEvent(GrailsApplicationPostProcessor.groovy)
at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:403)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:360)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:897)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:141)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:744)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:391)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312)
at grails.boot.GrailsApp.run(GrailsApp.groovy:99)
at grails.boot.GrailsApp.run(GrailsApp.groovy:485)
at grails.boot.GrailsApp.run(GrailsApp.groovy:472)
at testconstraintnotnull.Application.main(Application.groovy:11)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49)
Now if you either remove @GrailsCompileStatic or remove the custom query or remove graphql at all, the app just runs
I uploaded a semple here
This appears to be the same issue as #48