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

(grails-core) 02/03: Fix control flow and migrate deprecated typeResolver in AbstractObjectTypeBuilder

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

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

commit 9e3929a9acc6dd144ae4229dc63952f0832eab6a
Author: Walter Duque de Estrada <wb...@mac.com>
AuthorDate: Sun Aug 23 09:27:56 2026 -0500

    Fix control flow and migrate deprecated typeResolver in AbstractObjectTypeBuilder
    
    - build(entity) declared a blank GraphQLOutputType reassigned across
      if/else branches then passed to objectTypeCache.put(...) - Groovy's
      static type checker narrowed its inferred type to an intersection of
      GraphQLInterfaceType/GraphQLObjectType's common interfaces rather than
      the declared GraphQLOutputType, so put() no longer type-checked.
      Rewrote to return the cached value directly and assign objectType via
      a ternary instead of branch reassignment, the same treatment given to
      DefaultGormDataFetcher.resolveDatastore() earlier in this cleanup.
    - Fixed an unrelated latent bug found while touching build(): fields'
      ArrayList capacity was computed from `properties.size()` one line
      before `properties` was declared, which under CompileStatic actually
      resolved to the GroovyObject-inherited `getProperties()` bean-property
      map (an unrelated count), not the domain property list. Moved the
      properties declaration first.
    - buildInterfaceType(entity, name, description, fields) never used its
      entity parameter; dropped it (no overrides exist, undocumented).
    - GraphQLInterfaceType.Builder#typeResolver(TypeResolver) is deprecated
      since 2018-12-03 in favor of registering the resolver on
      GraphQLCodeRegistry.Builder by interface name, same migration already
      applied to CustomOperation/Schema's dataFetcher registration earlier
      in this cleanup.
    
    Added AbstractObjectTypeBuilderSpec, the first coverage of build()'s
    interface-type branch (a root entity with child entities) - the other
    object type builder specs construct their builder with a null
    GraphQLTypeManager and can't reach build() at all. Backs it with a
    minimal Animal/Dog GORM inheritance fixture and verifies the interface
    type, the child's implements-interface relationship, and that the
    migrated codeRegistry type resolver is actually registered.
    
    Co-Authored-By: Claude Sonnet 5 <no...@anthropic.com>
---
 .../types/output/AbstractObjectTypeBuilder.groovy  | 61 ++++++++----------
 .../gorm/graphql/domain/inheritance/Animal.groovy  | 29 +++++++++
 .../gorm/graphql/domain/inheritance/Dog.groovy     | 29 +++++++++
 .../domain/inheritance/InheritancePackage.groovy   | 23 +++++++
 .../output/AbstractObjectTypeBuilderSpec.groovy    | 72 ++++++++++++++++++++++
 5 files changed, 180 insertions(+), 34 deletions(-)

diff --git a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilder.groovy b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilder.groovy
index 1d8a4dd104..e527619f84 100644
--- a/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilder.groovy
+++ b/grails-data-graphql/core/src/main/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilder.groovy
@@ -101,44 +101,36 @@ abstract class AbstractObjectTypeBuilder implements ObjectTypeBuilder {
 
     @Override
     GraphQLOutputType build(PersistentEntity entity) {
-
-        GraphQLOutputType objectType
-
         if (objectTypeCache.containsKey(entity)) {
-            objectTypeCache.get(entity)
+            return objectTypeCache.get(entity)
         }
-        else {
-            final String description = GraphQLEntityHelper.getDescription(entity)
-            final String name = typeManager.namingConvention.getType(entity, type)
-
-            List<GraphQLFieldDefinition> fields = new ArrayList<>(properties.size() + 1)
-
-            List<GraphQLDomainProperty> properties = builder.getProperties(entity)
-            for (GraphQLDomainProperty prop: properties) {
-                if (prop.output) {
-                    GraphQLFieldDefinition.Builder field = buildField(prop, name)
-                    addFieldArgs(field, prop, entity.mappingContext)
-                    fields.add(field.build())
-                }
-            }
 
-            if (errorsResponseHandler != null) {
-                GraphQLFieldDefinition fieldDefinition = errorsResponseHandler.getFieldDefinition(typeManager, name)
-                fields.add(fieldDefinition)
-            }
-
-            boolean hasChildEntities = entity.root && !entity.mappingContext.getDirectChildEntities(entity).empty
+        final String description = GraphQLEntityHelper.getDescription(entity)
+        final String name = typeManager.namingConvention.getType(entity, type)
 
-            if (hasChildEntities && !type.embedded) {
-                objectType = buildInterfaceType(entity, name, description, fields)
-            }
-            else {
-                objectType = buildObjectType(entity, name, description, fields)
+        List<GraphQLDomainProperty> properties = builder.getProperties(entity)
+        List<GraphQLFieldDefinition> fields = new ArrayList<>(properties.size() + 1)
+        for (GraphQLDomainProperty prop: properties) {
+            if (prop.output) {
+                GraphQLFieldDefinition.Builder field = buildField(prop, name)
+                addFieldArgs(field, prop, entity.mappingContext)
+                fields.add(field.build())
             }
+        }
 
-            objectTypeCache.put(entity, objectType)
-            objectType
+        if (errorsResponseHandler != null) {
+            GraphQLFieldDefinition fieldDefinition = errorsResponseHandler.getFieldDefinition(typeManager, name)
+            fields.add(fieldDefinition)
         }
+
+        boolean hasChildEntities = entity.root && !entity.mappingContext.getDirectChildEntities(entity).empty
+
+        GraphQLOutputType objectType = hasChildEntities && !type.embedded ?
+                buildInterfaceType(name, description, fields) :
+                buildObjectType(entity, name, description, fields)
+
+        objectTypeCache.put(entity, objectType)
+        objectType
     }
 
     GraphQLObjectType buildObjectType(final PersistentEntity entity, final String name, final String description, final List<GraphQLFieldDefinition> fields) {
@@ -155,15 +147,16 @@ abstract class AbstractObjectTypeBuilder implements ObjectTypeBuilder {
         obj.build()
     }
 
-    GraphQLInterfaceType buildInterfaceType(final PersistentEntity entity, final String name, final String description, final List<GraphQLFieldDefinition> fields) {
+    GraphQLInterfaceType buildInterfaceType(final String name, final String description, final List<GraphQLFieldDefinition> fields) {
 
         GraphQLInterfaceType.Builder obj = newInterface()
                 .name(name)
                 .description(description)
                 .fields(fields)
-                .typeResolver(buildEntityTypeResolver())
 
-        obj.build()
+        GraphQLInterfaceType interfaceType = obj.build()
+        codeRegistry.typeResolver(name, buildEntityTypeResolver())
+        interfaceType
     }
 
     protected TypeResolver buildEntityTypeResolver() {
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Animal.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Animal.groovy
new file mode 100644
index 0000000000..7249cd9578
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Animal.groovy
@@ -0,0 +1,29 @@
+/*
+ *  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.domain.inheritance
+
+import grails.gorm.annotation.Entity
+
+@Entity
+class Animal {
+    String name
+
+    static graphql = true
+}
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Dog.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Dog.groovy
new file mode 100644
index 0000000000..226f3dfb38
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/Dog.groovy
@@ -0,0 +1,29 @@
+/*
+ *  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.domain.inheritance
+
+import grails.gorm.annotation.Entity
+
+@Entity
+class Dog extends Animal {
+    String breed
+
+    static graphql = true
+}
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/InheritancePackage.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/InheritancePackage.groovy
new file mode 100644
index 0000000000..5f178bede9
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/domain/inheritance/InheritancePackage.groovy
@@ -0,0 +1,23 @@
+/*
+ *  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.domain.inheritance
+
+class InheritancePackage {
+}
diff --git a/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilderSpec.groovy b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilderSpec.groovy
new file mode 100644
index 0000000000..543549b87c
--- /dev/null
+++ b/grails-data-graphql/core/src/test/groovy/org/grails/gorm/graphql/types/output/AbstractObjectTypeBuilderSpec.groovy
@@ -0,0 +1,72 @@
+/*
+ *  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.types.output
+
+import graphql.schema.GraphQLInterfaceType
+import graphql.schema.GraphQLObjectType
+import graphql.schema.GraphQLSchema
+import org.grails.datastore.mapping.config.Settings
+import org.grails.datastore.mapping.core.DatastoreUtils
+import org.grails.gorm.graphql.Schema
+import org.grails.gorm.graphql.domain.inheritance.InheritancePackage
+import org.grails.orm.hibernate.HibernateDatastore
+import spock.lang.AutoCleanup
+import spock.lang.Shared
+import spock.lang.Specification
+
+/**
+ * Exercises AbstractObjectTypeBuilder#build(PersistentEntity) through a real
+ * Schema, covering the interface-type branch (a root entity with child
+ * entities) which none of the other object type builder specs reach, since
+ * they construct their builders with a null GraphQLTypeManager.
+ */
+class AbstractObjectTypeBuilderSpec extends Specification {
+
+    @Shared @AutoCleanup HibernateDatastore hibernateDatastore
+    @Shared GraphQLSchema schema
+
+    void setupSpec() {
+        hibernateDatastore = new HibernateDatastore(
+                DatastoreUtils.createPropertyResolver(Collections.singletonMap(Settings.SETTING_DB_CREATE, 'create-drop')),
+                InheritancePackage.getPackage())
+        schema = new Schema(hibernateDatastore.mappingContext).generate()
+    }
+
+    void "test a root entity with child entities builds an interface type"() {
+        expect:
+        schema.getType('Animal') instanceof GraphQLInterfaceType
+    }
+
+    void "test a child entity builds an object type that implements the root interface"() {
+        given:
+        GraphQLInterfaceType animalType = (GraphQLInterfaceType) schema.getType('Animal')
+        GraphQLObjectType dogType = (GraphQLObjectType) schema.getType('Dog')
+
+        expect:
+        dogType.interfaces.contains(animalType)
+    }
+
+    void "test a type resolver is registered for the interface type"() {
+        given:
+        GraphQLInterfaceType animalType = (GraphQLInterfaceType) schema.getType('Animal')
+
+        expect:
+        schema.codeRegistry.getTypeResolver(animalType) != null
+    }
+}