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:33 UTC

(grails-core) 03/08: Restore null-safe getMergedField handling and cover ClosureDataFetcher.buildFetchOptions

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 8c6e067feee0b7b48ee845b50aa2d927976ca119
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sat Aug 22 10:47:04 2026 -0500

    Restore null-safe getMergedField handling and cover ClosureDataFetcher.buildFetchOptions
    
    A prior commit in this cleanup ("Parameterize raw
    Association/ToOne/Selection usages") accidentally dropped the
    null-guard around environment.getMergedField() in
    EntityFetchOptions.getJoinProperties(DataFetchingEnvironment, boolean)
    while editing nearby code, turning a graceful "no merged field ->
    empty fields list" fallback into a NullPointerException. This broke
    6+ existing specs whose DataFetchingEnvironment mocks don't stub
    getMergedField() (so it returns null, as real callers can also see).
    Restored the guard as a single-assignment if/else.
    
    Also adds test coverage for ClosureDataFetcher.buildFetchOptions(),
    which had none: null domain type, non-GORM domain type, a real GORM
    entity domain type, and that the built EntityFetchOptions is cached
    across calls. Converts ClosureDataFetcherSpec to HibernateSpec to
    back the GORM-entity case.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../gorm/graphql/entity/EntityFetchOptions.java    | 23 ++++++++-----
 .../fetcher/impl/ClosureDataFetcherSpec.groovy     | 40 ++++++++++++++++++++--
 2 files changed, 52 insertions(+), 11 deletions(-)

diff --git a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/EntityFetchOptions.java b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/EntityFetchOptions.java
index b13d44ba07..03eb53ebf9 100644
--- a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/EntityFetchOptions.java
+++ b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/entity/EntityFetchOptions.java
@@ -211,17 +211,22 @@ public class EntityFetchOptions {
      * @return The list of properties to eagerly fetch
      */
     public Set<String> getJoinProperties(DataFetchingEnvironment environment, boolean skipCollections) {
-
         MergedField environmentMergedField = environment.getMergedField();
 
-        List<Field> fields = environmentMergedField
-            .getFields()
-            .stream()
-            .filter(field -> field.getSelectionSet() != null)
-            .flatMap(field -> field.getSelectionSet().getSelections().stream())
-            .filter(Field.class::isInstance)
-            .map(Field.class::cast)
-            .collect(Collectors.toList());
+        List<Field> fields;
+        if (environmentMergedField == null) {
+            fields = new ArrayList<>();
+        }
+        else {
+            fields = environmentMergedField
+                    .getFields()
+                    .stream()
+                    .filter(field -> field.getSelectionSet() != null)
+                    .flatMap(field -> field.getSelectionSet().getSelections().stream())
+                    .filter(Field.class::isInstance)
+                    .map(Field.class::cast)
+                    .collect(Collectors.toList());
+        }
 
         return getJoinProperties(fields, skipCollections);
     }
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/impl/ClosureDataFetcherSpec.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/impl/ClosureDataFetcherSpec.groovy
index a5a721975c..cd8889d64a 100644
--- a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/impl/ClosureDataFetcherSpec.groovy
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/fetcher/impl/ClosureDataFetcherSpec.groovy
@@ -20,11 +20,15 @@
 package org.grails.gorm.graphql.fetcher.impl
 
 import graphql.schema.DataFetchingEnvironment
-import spock.lang.Specification
+import org.grails.gorm.graphql.HibernateSpec
+import org.grails.gorm.graphql.domain.general.toone.One
+import org.grails.gorm.graphql.entity.EntityFetchOptions
 import spock.lang.Subject
 
 @Subject(ClosureDataFetcher)
-class ClosureDataFetcherSpec extends Specification {
+class ClosureDataFetcherSpec extends HibernateSpec {
+
+    List<Class> getDomainClasses() { [One] }
 
     void "test closure is called with the source argument"() {
         given:
@@ -57,4 +61,36 @@ class ClosureDataFetcherSpec extends Specification {
         then:
         result == 'hello'
     }
+
+    void "test buildFetchOptions returns null when no domain type is supplied"() {
+        given:
+        ClosureDataFetcher fetcher = new ClosureDataFetcher({})
+
+        expect:
+        fetcher.buildFetchOptions() == null
+    }
+
+    void "test buildFetchOptions returns null when the domain type is not a GORM entity"() {
+        given:
+        ClosureDataFetcher fetcher = new ClosureDataFetcher({}, String)
+
+        expect:
+        fetcher.buildFetchOptions() == null
+    }
+
+    void "test buildFetchOptions returns fetch options for a GORM entity domain type"() {
+        given:
+        ClosureDataFetcher fetcher = new ClosureDataFetcher({}, One)
+
+        expect:
+        fetcher.buildFetchOptions() instanceof EntityFetchOptions
+    }
+
+    void "test buildFetchOptions caches the result"() {
+        given:
+        ClosureDataFetcher fetcher = new ClosureDataFetcher({}, One)
+
+        expect:
+        fetcher.buildFetchOptions().is(fetcher.buildFetchOptions())
+    }
 }