diff --git a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsDataLoader.java b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsDataLoader.java
index 166c95369..3f9762c67 100644
--- a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsDataLoader.java
+++ b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsDataLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Netflix, Inc.
+ * Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
package com.netflix.graphql.dgs;
+import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
@@ -32,7 +33,16 @@
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DgsDataLoader {
- String name();
+
+ /**
+ * Used internally by {@link DataLoaderNameUtil#getDataLoaderName(Class, DgsDataLoader)}.
+ *
+ * The value of this constant may change in future versions,
+ * and should therefore not be relied upon.
+ */
+ String GENERATE_DATA_LOADER_NAME = "NETFLIX_DGS_GENERATE_DATALOADER_NAME";
+
+ String name() default GENERATE_DATA_LOADER_NAME;
boolean caching() default true;
diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsDataFetchingEnvironment.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsDataFetchingEnvironment.kt
index 7a05b46e4..85f500070 100644
--- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsDataFetchingEnvironment.kt
+++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsDataFetchingEnvironment.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Netflix, Inc.
+ * Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ package com.netflix.graphql.dgs
import com.netflix.graphql.dgs.context.DgsContext
import com.netflix.graphql.dgs.exceptions.MultipleDataLoadersDefinedException
import com.netflix.graphql.dgs.exceptions.NoDataLoaderFoundException
+import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil
import graphql.GraphQLContext
import graphql.cachecontrol.CacheControl
import graphql.execution.ExecutionId
@@ -29,7 +30,12 @@ import graphql.language.Document
import graphql.language.Field
import graphql.language.FragmentDefinition
import graphql.language.OperationDefinition
-import graphql.schema.*
+import graphql.schema.DataFetchingEnvironment
+import graphql.schema.DataFetchingFieldSelectionSet
+import graphql.schema.GraphQLFieldDefinition
+import graphql.schema.GraphQLOutputType
+import graphql.schema.GraphQLSchema
+import graphql.schema.GraphQLType
import org.dataloader.DataLoader
import org.dataloader.DataLoaderRegistry
import java.util.*
@@ -48,13 +54,14 @@ class DgsDataFetchingEnvironment(private val dfe: DataFetchingEnvironment) : Dat
fun getDataLoader(loaderClass: Class<*>): DataLoader {
val annotation = loaderClass.getAnnotation(DgsDataLoader::class.java)
return if (annotation != null) {
- dfe.getDataLoader(annotation.name)
+ dfe.getDataLoader(DataLoaderNameUtil.getDataLoaderName(loaderClass, annotation))
} else {
val loaders = loaderClass.fields.filter { it.isAnnotationPresent(DgsDataLoader::class.java) }
if (loaders.size > 1) throw MultipleDataLoadersDefinedException(loaderClass)
- val loaderName = loaders
- .firstOrNull()?.getAnnotation(DgsDataLoader::class.java)?.name
- ?: throw NoDataLoaderFoundException(loaderClass)
+ val loaderField: java.lang.reflect.Field = loaders
+ .firstOrNull() ?: throw NoDataLoaderFoundException(loaderClass)
+ val theAnnotation = loaderField.getAnnotation(DgsDataLoader::class.java)
+ val loaderName = theAnnotation.name
dfe.getDataLoader(loaderName)
}
}
diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/exceptions/DgsUnnamedDataLoaderOnFieldException.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/exceptions/DgsUnnamedDataLoaderOnFieldException.kt
new file mode 100644
index 000000000..084d754d9
--- /dev/null
+++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/exceptions/DgsUnnamedDataLoaderOnFieldException.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 Netflix, Inc.
+ *
+ * Licensed 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
+ *
+ * http://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 com.netflix.graphql.dgs.exceptions
+
+import java.lang.reflect.Field
+
+class DgsUnnamedDataLoaderOnFieldException(field: Field) :
+ RuntimeException("Field `${field.name}` in class `${field.declaringClass.name}` was annotated with @DgsDataLoader, but the data loader was not given a proper name")
diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsDataLoaderProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsDataLoaderProvider.kt
index 4f02968bb..85e828acb 100644
--- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsDataLoaderProvider.kt
+++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsDataLoaderProvider.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Netflix, Inc.
+ * Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,9 +20,17 @@ import com.netflix.graphql.dgs.DataLoaderInstrumentationExtensionProvider
import com.netflix.graphql.dgs.DgsComponent
import com.netflix.graphql.dgs.DgsDataLoader
import com.netflix.graphql.dgs.DgsDataLoaderRegistryConsumer
+import com.netflix.graphql.dgs.exceptions.DgsUnnamedDataLoaderOnFieldException
import com.netflix.graphql.dgs.exceptions.InvalidDataLoaderTypeException
import com.netflix.graphql.dgs.exceptions.UnsupportedSecuredDataLoaderException
-import org.dataloader.*
+import com.netflix.graphql.dgs.internal.utils.DataLoaderNameUtil
+import org.dataloader.BatchLoader
+import org.dataloader.BatchLoaderWithContext
+import org.dataloader.DataLoader
+import org.dataloader.DataLoaderOptions
+import org.dataloader.DataLoaderRegistry
+import org.dataloader.MappedBatchLoader
+import org.dataloader.MappedBatchLoaderWithContext
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.aop.support.AopUtils
@@ -37,10 +45,12 @@ import javax.annotation.PostConstruct
*/
class DgsDataLoaderProvider(private val applicationContext: ApplicationContext) {
- private val batchLoaders = mutableListOf, DgsDataLoader>>()
- private val batchLoadersWithContext = mutableListOf, DgsDataLoader>>()
- private val mappedBatchLoaders = mutableListOf, DgsDataLoader>>()
- private val mappedBatchLoadersWithContext = mutableListOf, DgsDataLoader>>()
+ private data class LoaderHolder(val theLoader: T, val annotation: DgsDataLoader, val name: String)
+
+ private val batchLoaders = mutableListOf>>()
+ private val batchLoadersWithContext = mutableListOf>>()
+ private val mappedBatchLoaders = mutableListOf>>()
+ private val mappedBatchLoadersWithContext = mutableListOf>>()
fun buildRegistry(): DataLoaderRegistry {
return buildRegistryWithContextSupplier { null }
@@ -50,23 +60,22 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
val startTime = System.currentTimeMillis()
val dataLoaderRegistry = DataLoaderRegistry()
- batchLoaders.forEach { dataLoaderRegistry.register(it.second.name, createDataLoader(it.first, it.second, dataLoaderRegistry)) }
+ batchLoaders.forEach {
+ dataLoaderRegistry.register(it.name, createDataLoader(it.theLoader, it.annotation, dataLoaderRegistry))
+ }
mappedBatchLoaders.forEach {
- dataLoaderRegistry.register(
- it.second.name,
- createDataLoader(it.first, it.second, dataLoaderRegistry)
- )
+ dataLoaderRegistry.register(it.name, createDataLoader(it.theLoader, it.annotation, dataLoaderRegistry))
}
batchLoadersWithContext.forEach {
dataLoaderRegistry.register(
- it.second.name,
- createDataLoader(it.first, it.second, contextSupplier, dataLoaderRegistry)
+ it.name,
+ createDataLoader(it.theLoader, it.annotation, contextSupplier, dataLoaderRegistry)
)
}
mappedBatchLoadersWithContext.forEach {
dataLoaderRegistry.register(
- it.second.name,
- createDataLoader(it.first, it.second, contextSupplier, dataLoaderRegistry)
+ it.name,
+ createDataLoader(it.theLoader, it.annotation, contextSupplier, dataLoaderRegistry)
)
}
@@ -96,15 +105,16 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
val annotation = field.getAnnotation(DgsDataLoader::class.java)
ReflectionUtils.makeAccessible(field)
+ if (annotation.name == DgsDataLoader.GENERATE_DATA_LOADER_NAME) {
+ throw DgsUnnamedDataLoaderOnFieldException(field)
+ }
+
+ fun createHolder(t: T): LoaderHolder = LoaderHolder(t, annotation, annotation.name)
when (val get = field.get(dgsComponent)) {
- is BatchLoader<*, *> ->
- batchLoaders.add(get to annotation)
- is BatchLoaderWithContext<*, *> ->
- batchLoadersWithContext.add(get to annotation)
- is MappedBatchLoader<*, *> ->
- mappedBatchLoaders.add(get to annotation)
- is MappedBatchLoaderWithContext<*, *> ->
- mappedBatchLoadersWithContext.add(get to annotation)
+ is BatchLoader<*, *> -> batchLoaders.add(createHolder(get))
+ is BatchLoaderWithContext<*, *> -> batchLoadersWithContext.add(createHolder(get))
+ is MappedBatchLoader<*, *> -> mappedBatchLoaders.add(createHolder(get))
+ is MappedBatchLoaderWithContext<*, *> -> mappedBatchLoadersWithContext.add(createHolder(get))
else -> throw InvalidDataLoaderTypeException(dgsComponent::class.java)
}
}
@@ -116,15 +126,13 @@ class DgsDataLoaderProvider(private val applicationContext: ApplicationContext)
dataLoaders.values.forEach { dgsComponent ->
val javaClass = AopUtils.getTargetClass(dgsComponent)
val annotation = javaClass.getAnnotation(DgsDataLoader::class.java)
+ fun createHolder(t: T): LoaderHolder =
+ LoaderHolder(t, annotation, DataLoaderNameUtil.getDataLoaderName(javaClass, annotation))
when (dgsComponent) {
- is BatchLoader<*, *> ->
- batchLoaders.add(Pair(dgsComponent, annotation))
- is BatchLoaderWithContext<*, *> ->
- batchLoadersWithContext.add(Pair(dgsComponent, annotation))
- is MappedBatchLoader<*, *> ->
- mappedBatchLoaders.add(Pair(dgsComponent, annotation))
- is MappedBatchLoaderWithContext<*, *> ->
- mappedBatchLoadersWithContext.add(Pair(dgsComponent, annotation))
+ is BatchLoader<*, *> -> batchLoaders.add(createHolder(dgsComponent))
+ is BatchLoaderWithContext<*, *> -> batchLoadersWithContext.add(createHolder(dgsComponent))
+ is MappedBatchLoader<*, *> -> mappedBatchLoaders.add(createHolder(dgsComponent))
+ is MappedBatchLoaderWithContext<*, *> -> mappedBatchLoadersWithContext.add(createHolder(dgsComponent))
else -> throw InvalidDataLoaderTypeException(dgsComponent::class.java)
}
}
diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/utils/DataLoaderNameUtil.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/utils/DataLoaderNameUtil.kt
new file mode 100644
index 000000000..4fc6a494b
--- /dev/null
+++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/utils/DataLoaderNameUtil.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 Netflix, Inc.
+ *
+ * Licensed 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
+ *
+ * http://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 com.netflix.graphql.dgs.internal.utils
+
+import com.netflix.graphql.dgs.DgsDataLoader
+import com.netflix.graphql.dgs.Internal
+
+@Internal
+object DataLoaderNameUtil {
+
+ /**
+ * When the [annotation]'s [DgsDataLoader.name] is equal to [DgsDataLoader.GENERATE_DATA_LOADER_NAME],
+ * the [clazz]'s [Class.getSimpleName] will be used.
+ * In all other cases the [DgsDataLoader.name] method will be called on [annotation].
+ *
+ * This method does not verify that [annotation] belongs to [clazz] for performance reasons.
+ */
+ fun getDataLoaderName(clazz: Class<*>, annotation: DgsDataLoader): String {
+ return if (annotation.name == DgsDataLoader.GENERATE_DATA_LOADER_NAME) clazz.simpleName else annotation.name
+ }
+}
diff --git a/graphql-dgs/src/test/java/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutNameFromField.java b/graphql-dgs/src/test/java/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutNameFromField.java
new file mode 100644
index 000000000..c19268eeb
--- /dev/null
+++ b/graphql-dgs/src/test/java/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutNameFromField.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2022 Netflix, Inc.
+ *
+ * Licensed 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
+ *
+ * http://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 com.netflix.graphql.dgs;
+
+import org.dataloader.BatchLoader;
+
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+
+@DgsComponent
+public class ExampleBatchLoaderWithoutNameFromField {
+ @DgsDataLoader
+ public BatchLoader batchLoader = keys -> CompletableFuture.supplyAsync(() -> {
+ List values = new ArrayList<>();
+ values.add("a");
+ values.add("b");
+ values.add("c");
+ return values;
+ });
+
+ @DgsDataLoader
+ BatchLoader privateBatchLoader = keys -> CompletableFuture.supplyAsync(() -> {
+ List values = new ArrayList<>();
+ values.add("a");
+ values.add("b");
+ values.add("c");
+ return values;
+ });
+}
diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsDataLoaderProviderTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsDataLoaderProviderTest.kt
index 1178aa290..4341c2fe1 100644
--- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsDataLoaderProviderTest.kt
+++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsDataLoaderProviderTest.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 Netflix, Inc.
+ * Copyright 2022 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,20 @@
package com.netflix.graphql.dgs
+import com.netflix.graphql.dgs.exceptions.DgsUnnamedDataLoaderOnFieldException
import com.netflix.graphql.dgs.exceptions.InvalidDataLoaderTypeException
import com.netflix.graphql.dgs.internal.DgsDataLoaderProvider
+import graphql.schema.DataFetchingEnvironmentImpl
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import org.assertj.core.api.Assertions.assertThat
+import org.assertj.core.api.Assertions.assertThatThrownBy
import org.dataloader.BatchLoader
import org.dataloader.DataLoaderRegistry
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.extension.ExtendWith
@@ -49,7 +53,12 @@ class DgsDataLoaderProviderTest {
@Test
fun findDataLoaders() {
every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns emptyMap()
- every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(Pair("helloFetcher", ExampleBatchLoader()))
+ every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleBatchLoader()
+ )
+ )
val provider = DgsDataLoaderProvider(applicationContextMock)
provider.findDataLoaders()
@@ -61,7 +70,12 @@ class DgsDataLoaderProviderTest {
@Test
fun dataLoaderInvalidType() {
- every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(Pair("helloFetcher", object {}))
+ every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ object {}
+ )
+ )
val provider = DgsDataLoaderProvider(applicationContextMock)
assertThrows { provider.findDataLoaders() }
}
@@ -69,7 +83,12 @@ class DgsDataLoaderProviderTest {
@Test
fun findDataLoadersFromFields() {
every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns emptyMap()
- every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(Pair("helloFetcher", ExampleBatchLoaderFromField()))
+ every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleBatchLoaderFromField()
+ )
+ )
val provider = DgsDataLoaderProvider(applicationContextMock)
provider.findDataLoaders()
@@ -85,7 +104,12 @@ class DgsDataLoaderProviderTest {
@Test
fun findMappedDataLoaders() {
every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns emptyMap()
- every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(Pair("helloFetcher", ExampleMappedBatchLoader()))
+ every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleMappedBatchLoader()
+ )
+ )
val provider = DgsDataLoaderProvider(applicationContextMock)
provider.findDataLoaders()
@@ -98,7 +122,12 @@ class DgsDataLoaderProviderTest {
@Test
fun findMappedDataLoadersFromFields() {
every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns emptyMap()
- every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(Pair("helloFetcher", ExampleMappedBatchLoaderFromField()))
+ every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleMappedBatchLoaderFromField()
+ )
+ )
val provider = DgsDataLoaderProvider(applicationContextMock)
provider.findDataLoaders()
@@ -128,6 +157,71 @@ class DgsDataLoaderProviderTest {
assertThat(loaderKeys).isEqualTo(registry.keys.toMutableList()[0])
}
+ @Nested
+ inner class UnnamedBatchLoaderTests {
+ @Test
+ fun findDataLoadersWithoutName() {
+ every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns emptyMap()
+ every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleBatchLoaderWithoutName()
+ )
+ )
+
+ val provider = DgsDataLoaderProvider(applicationContextMock)
+ provider.findDataLoaders()
+ val dataLoaderRegistry = provider.buildRegistry()
+ Assertions.assertEquals(1, dataLoaderRegistry.dataLoaders.size)
+ val dataLoader =
+ dataLoaderRegistry.getDataLoader("ExampleBatchLoaderWithoutName")
+ Assertions.assertNotNull(dataLoader)
+ }
+
+ @Test
+ fun findDataLoadersWithoutNameByClass() {
+ every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns emptyMap()
+ val theLoader = ExampleBatchLoaderWithoutName()
+ Assertions.assertEquals(
+ DgsDataLoader.GENERATE_DATA_LOADER_NAME,
+ theLoader.javaClass.getAnnotation(DgsDataLoader::class.java).name
+ )
+ every {
+ applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java)
+ } returns mapOf(Pair("helloFetcher", theLoader))
+
+ val provider = DgsDataLoaderProvider(applicationContextMock)
+ provider.findDataLoaders()
+ val dataLoaderRegistry = provider.buildRegistry()
+ Assertions.assertEquals(1, dataLoaderRegistry.dataLoaders.size)
+ val dataLoader = DgsDataFetchingEnvironment(
+ DataFetchingEnvironmentImpl.newDataFetchingEnvironment()
+ .dataLoaderRegistry(dataLoaderRegistry).build()
+ )
+ .getDataLoader(ExampleBatchLoaderWithoutName::class.java)
+ Assertions.assertNotNull(dataLoader)
+ }
+
+ @Test
+ fun findDataLoadersFromFieldsWithoutName() {
+ every { applicationContextMock.getBeansWithAnnotation(DgsDataLoader::class.java) } returns emptyMap()
+ every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(
+ Pair(
+ "helloFetcher",
+ ExampleBatchLoaderWithoutNameFromField()
+ )
+ )
+
+ val provider = DgsDataLoaderProvider(applicationContextMock)
+ assertThatThrownBy { provider.findDataLoaders() }
+ .hasNoCause()
+ .isExactlyInstanceOf(DgsUnnamedDataLoaderOnFieldException::class.java)
+ .hasMessage(
+ "Field `batchLoader` in class `com.netflix.graphql.dgs.ExampleBatchLoaderWithoutNameFromField` was annotated with @DgsDataLoader, but the data loader was not given a proper name"
+ )
+ }
+ }
+
@DgsDataLoader(name = "withRegistry")
class ExampleDataLoaderWithRegistry : BatchLoader, DgsDataLoaderRegistryConsumer {
diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutName.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutName.kt
new file mode 100644
index 000000000..7c2b9b5d2
--- /dev/null
+++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/ExampleBatchLoaderWithoutName.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2022 Netflix, Inc.
+ *
+ * Licensed 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
+ *
+ * http://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 com.netflix.graphql.dgs
+
+import org.dataloader.BatchLoader
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.CompletionStage
+
+@DgsDataLoader
+class ExampleBatchLoaderWithoutName : BatchLoader {
+ override fun load(keys: MutableList?): CompletionStage> {
+ return CompletableFuture.supplyAsync { mutableListOf("a", "b", "c") }
+ }
+}