gorm-graphql icon indicating copy to clipboard operation
gorm-graphql copied to clipboard

Graphql on a @GrailsCompileStatic domain class cause nullable constraint to be ignored

Open magnomp opened this issue 5 years ago • 1 comments

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:

  1. 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

  2. Create a domain class, say MyDomain marked with @GrailsCompileStatic and a nullable property, like:

@GrailsCompileStatic
class MyDomain {
    String name

    static constraints = {
        name nullable: true
    }
}
  1. 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.

  1. 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 = {
    }
}
  1. Now you run with gradlew bootRun. It is expected the instance to be persisted with no errors, because it has only one field, which is null, and that field allows null. 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

magnomp avatar Nov 05 '20 20:11 magnomp

This appears to be the same issue as #48

jamesdh avatar Nov 10 '20 16:11 jamesdh