You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@grails.apache.org by bo...@apache.org on 2026/08/23 16:04:36 UTC

(grails-core) 06/08: Replace deprecated GraphQLArgument/GraphQLInputObjectField defaultValue calls

This is an automated email from the ASF dual-hosted git repository.

borinquenkid pushed a commit to branch refactor/graphql-entity
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 83cc487a5cc11da9637c7af447a0c83dbbc8e89a
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Fri Aug 21 23:21:12 2026 -0500

    Replace deprecated GraphQLArgument/GraphQLInputObjectField defaultValue calls
    
    graphql-java 25 deprecates Builder#defaultValue(Object) in favor of
    defaultValueLiteral(Value) or defaultValueProgrammatic(Object).
    CustomArgument and ComplexTyped supply a plain, uncoerced Java value
    from the DSL, so defaultValueProgrammatic is the correct replacement.
    
    Switching unconditionally surfaced a real bug: the old deprecated call
    always marked the default value as "set" (even when the DSL user never
    configured one), which the old INTERNAL_VALUE state silently exempted
    from schema validation. defaultValueProgrammatic's EXTERNAL_VALUE state
    is validated, and a null default on a non-null argument/field type
    failed schema build. Both call sites now only apply a default value
    when one was actually configured via the DSL, leaving it unset
    otherwise - matching the DSL's actual intent and passing validation.
    
    Also documents (without changing) the unqualified withDelegate() calls
    in Arguable and ComplexTyped: explicitly qualifying the inherited
    ExecutesClosures static trait method there does not resolve under
    @CompileStatic, so the plain call must stay despite IDE inspections
    suggesting otherwise.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../graphql/entity/arguments/CustomArgument.groovy |  9 ++-
 .../graphql/entity/dsl/helpers/Arguable.groovy     |  4 +-
 .../graphql/entity/dsl/helpers/ComplexTyped.groovy | 16 +++-
 .../entity/arguments/SimpleArgumentSpec.groovy     | 89 ++++++++++++++++++++++
 .../entity/dsl/helpers/ComplexTypedSpec.groovy     | 69 +++++++++++++++++
 5 files changed, 180 insertions(+), 7 deletions(-)

diff --git a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/arguments/CustomArgument.groovy b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/arguments/CustomArgument.groovy
index 8d3b52eaea..10ae751d12 100644
--- a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/arguments/CustomArgument.groovy
+++ b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/arguments/CustomArgument.groovy
@@ -49,11 +49,16 @@ abstract class CustomArgument<T> implements Named<T>, Describable<T>, Nullable<T
     GraphQLArgument.Builder getArgument(GraphQLTypeManager typeManager, MappingContext mappingContext) {
         GraphQLInputType type = getType(typeManager, mappingContext)
 
-        newArgument()
+        GraphQLArgument.Builder builder = newArgument()
             .name(name)
             .description(description)
-            .defaultValue(defaultValue)
             .type(type)
+
+        if (defaultValue != null) {
+            builder.defaultValueProgrammatic(defaultValue)
+        }
+
+        builder
     }
 
     void validate() {
diff --git a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/Arguable.groovy b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/Arguable.groovy
index 2a6409e3e8..826709900f 100644
--- a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/Arguable.groovy
+++ b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/Arguable.groovy
@@ -41,7 +41,9 @@ trait Arguable<T> extends ExecutesClosures {
 
     private void handleArgumentClosure(CustomArgument argument, @DelegatesTo(strategy = Closure.DELEGATE_ONLY)Closure closure) {
         // GROOVY-12106: STC could not resolve this inherited ExecutesClosures static from a sub-trait
-        // body; fixed in Groovy 5.0.7, so the plain inherited-static call compiles again.
+        // body; fixed in Groovy 5.0.7, so the plain inherited-static call compiles again. Qualifying
+        // this call (e.g. ExecutesClosures.withDelegate(...)) does NOT resolve under STC either - only
+        // the unqualified form works, so leave it as-is despite IDE inspections suggesting otherwise.
         withDelegate(closure, (Object) argument)
         argument.validate()
         arguments.add(argument)
diff --git a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTyped.groovy b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTyped.groovy
index 128b18eacf..7f537e3cb0 100644
--- a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTyped.groovy
+++ b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTyped.groovy
@@ -19,6 +19,7 @@
 
 package org.grails.gorm.graphql.entity.dsl.helpers
 
+import graphql.schema.GraphQLInputObjectField
 import graphql.schema.GraphQLInputObjectType
 import graphql.schema.GraphQLInputType
 import graphql.schema.GraphQLList
@@ -107,11 +108,16 @@ trait ComplexTyped<T> extends ExecutesClosures {
 
             for (Field field: fields) {
                 if (field.input) {
-                    builder.field(newInputObjectField()
+                    GraphQLInputObjectField.Builder fieldBuilder = newInputObjectField()
                             .name(field.name)
                             .description(field.description)
-                            .defaultValue(field.defaultValue)
-                            .type(field.getInputType(typeManager, mappingContext)))
+                            .type(field.getInputType(typeManager, mappingContext))
+
+                    if (field.defaultValue != null) {
+                        fieldBuilder.defaultValueProgrammatic(field.defaultValue)
+                    }
+
+                    builder.field(fieldBuilder)
                 }
             }
             GraphQLInputType type = builder.build()
@@ -132,7 +138,9 @@ trait ComplexTyped<T> extends ExecutesClosures {
     private void handleField(@DelegatesTo(strategy = Closure.DELEGATE_ONLY)Closure closure, Field field) {
         field.nullable(defaultNull)
         // GROOVY-12106: STC could not resolve this inherited ExecutesClosures static from a sub-trait
-        // body; fixed in Groovy 5.0.7, so the plain inherited-static call compiles again.
+        // body; fixed in Groovy 5.0.7, so the plain inherited-static call compiles again. Qualifying
+        // this call (e.g. ExecutesClosures.withDelegate(...)) does NOT resolve under STC either - only
+        // the unqualified form works, so leave it as-is despite IDE inspections suggesting otherwise.
         withDelegate(closure, (Object) field)
         handleField(field)
     }
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/arguments/SimpleArgumentSpec.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/arguments/SimpleArgumentSpec.groovy
new file mode 100644
index 0000000000..21f820b586
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/arguments/SimpleArgumentSpec.groovy
@@ -0,0 +1,89 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.grails.gorm.graphql.entity.arguments
+
+import graphql.Scalars
+import graphql.schema.GraphQLArgument
+import org.grails.datastore.mapping.model.MappingContext
+import org.grails.gorm.graphql.types.GraphQLTypeManager
+import spock.lang.Specification
+
+class SimpleArgumentSpec extends Specification {
+
+    GraphQLTypeManager typeManager = Stub(GraphQLTypeManager) {
+        hasType(String) >> true
+        getType(String, _ as boolean) >> Scalars.GraphQLString
+    }
+    MappingContext mappingContext = Stub(MappingContext)
+
+    void "test getArgument sets an externally provided default value"() {
+        given:
+        SimpleArgument argument = new SimpleArgument()
+                .name('foo')
+                .description('a foo argument')
+                .returns(String)
+                .defaultValue('bar')
+
+        when:
+        GraphQLArgument built = argument.getArgument(typeManager, mappingContext).build()
+
+        then:
+        built.name == 'foo'
+        built.description == 'a foo argument'
+        built.argumentDefaultValue.set
+        built.argumentDefaultValue.external
+        built.argumentDefaultValue.value == 'bar'
+    }
+
+    void "test getArgument leaves the default value not set when none is configured"() {
+        given:
+        SimpleArgument argument = new SimpleArgument()
+                .name('foo')
+                .returns(String)
+
+        when:
+        GraphQLArgument built = argument.getArgument(typeManager, mappingContext).build()
+
+        then:
+        !built.argumentDefaultValue.set
+    }
+
+    void "test validate requires a return type"() {
+        given:
+        SimpleArgument argument = new SimpleArgument().name('foo')
+
+        when:
+        argument.validate()
+
+        then:
+        thrown(IllegalArgumentException)
+    }
+
+    void "test validate passes when a name and return type are set"() {
+        given:
+        SimpleArgument argument = new SimpleArgument().name('foo').returns(String)
+
+        when:
+        argument.validate()
+
+        then:
+        noExceptionThrown()
+    }
+}
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTypedSpec.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTypedSpec.groovy
new file mode 100644
index 0000000000..c385b6e1c7
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/entity/dsl/helpers/ComplexTypedSpec.groovy
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.grails.gorm.graphql.entity.dsl.helpers
+
+import graphql.Scalars
+import graphql.schema.GraphQLInputObjectField
+import graphql.schema.GraphQLInputObjectType
+import org.grails.datastore.mapping.model.MappingContext
+import org.grails.gorm.graphql.entity.fields.SimpleField
+import org.grails.gorm.graphql.types.GraphQLTypeManager
+import spock.lang.Specification
+
+class ComplexTypedSpec extends Specification {
+
+    GraphQLTypeManager typeManager = Stub(GraphQLTypeManager) {
+        hasType(String) >> true
+        getType(String, _ as boolean) >> Scalars.GraphQLString
+    }
+    MappingContext mappingContext = Stub(MappingContext)
+
+    ComplexTyped buildComplexTyped() {
+        (ComplexTyped) new Object().withTraits(ComplexTyped)
+    }
+
+    void "test buildCustomInputType sets an externally provided default value on its fields"() {
+        given:
+        ComplexTyped complexTyped = buildComplexTyped()
+        complexTyped.fields.add(new SimpleField().name('foo').returns(String).defaultValue('bar'))
+
+        when:
+        GraphQLInputObjectType type = (GraphQLInputObjectType) complexTyped.buildCustomInputType('Foo', typeManager, mappingContext, true)
+        GraphQLInputObjectField field = type.getFieldDefinition('foo')
+
+        then:
+        field.inputFieldDefaultValue.set
+        field.inputFieldDefaultValue.external
+        field.inputFieldDefaultValue.value == 'bar'
+    }
+
+    void "test buildCustomInputType leaves the default value not set when none is configured"() {
+        given:
+        ComplexTyped complexTyped = buildComplexTyped()
+        complexTyped.fields.add(new SimpleField().name('foo').returns(String))
+
+        when:
+        GraphQLInputObjectType type = (GraphQLInputObjectType) complexTyped.buildCustomInputType('Foo', typeManager, mappingContext, true)
+        GraphQLInputObjectField field = type.getFieldDefinition('foo')
+
+        then:
+        !field.inputFieldDefaultValue.set
+    }
+}