Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DefaultDgsFederationResolver now handles failed completion stages ret… #730

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Expand Up @@ -23,6 +23,8 @@ build
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
.idea/**/runConfigurations.xml
.idea/**/aws.xml

# Gradle
.idea/**/gradle.xml
Expand Down
Expand Up @@ -24,11 +24,14 @@ import com.netflix.graphql.dgs.exceptions.InvalidDgsEntityFetcher
import com.netflix.graphql.dgs.exceptions.MissingDgsEntityFetcherException
import com.netflix.graphql.dgs.internal.DgsSchemaProvider
import com.netflix.graphql.types.errors.TypedGraphQLError
import graphql.GraphQLError
import graphql.execution.*
import graphql.execution.DataFetcherExceptionHandler
import graphql.execution.DataFetcherExceptionHandlerParameters
import graphql.execution.DataFetcherResult
import graphql.execution.ResultPath
import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment
import graphql.schema.TypeResolver
import org.dataloader.Try
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
Expand All @@ -46,7 +49,10 @@ open class DefaultDgsFederationResolver() :
* This is the most common use case.
* The default constructor is used to extend the DefaultDgsFederationResolver. In that case injection is used to provide the schemaProvider.
*/
constructor(providedDgsSchemaProvider: DgsSchemaProvider, dataFetcherExceptionHandler: Optional<DataFetcherExceptionHandler>) : this() {
constructor(
providedDgsSchemaProvider: DgsSchemaProvider,
dataFetcherExceptionHandler: Optional<DataFetcherExceptionHandler>
) : this() {
dgsSchemaProvider = providedDgsSchemaProvider
dgsExceptionHandler = dataFetcherExceptionHandler
}
Expand All @@ -68,10 +74,9 @@ open class DefaultDgsFederationResolver() :
}

private fun dgsEntityFetchers(env: DataFetchingEnvironment): CompletableFuture<DataFetcherResult<List<Any?>>> {
val errorsList = mutableListOf<GraphQLError>()
val resultList = env.getArgument<List<Map<String, Any>>>(_Entity.argumentName)
.map { values ->
try {
Try.tryCall {
val typename = values["__typename"]
?: throw RuntimeException("__typename missing from arguments in federated query")
val fetcher = dgsSchemaProvider.entityFetchers[typename]
Expand All @@ -96,39 +101,50 @@ open class DefaultDgsFederationResolver() :
} else {
CompletableFuture.completedFuture(result)
}
} catch (e: Exception) {
if (e is InvocationTargetException && e.targetException != null) {
if (dgsExceptionHandler.isPresent) {
val res = dgsExceptionHandler.get().onException(
DataFetcherExceptionHandlerParameters.newExceptionParameters()
.dataFetchingEnvironment(env).exception(e.targetException).build()
)
res.errors.forEach { errorsList.add(it) }
} else {
errorsList.add(
TypedGraphQLError.newInternalErrorBuilder()
.message("%s: %s", e.targetException::class.java.name, e.targetException.message)
.path(ResultPath.parse("/_entities")).build()
)
}
} else {
errorsList.add(
TypedGraphQLError.newInternalErrorBuilder().message("%s: %s", e::class.java.name, e.message)
.path(ResultPath.parse("/_entities")).build()
)
}
CompletableFuture.completedFuture(null)
}
.map { tryFuture -> Try.tryFuture(tryFuture) }
.recover { exception -> CompletableFuture.completedFuture(Try.failed(exception)) }
.get()
}

return CompletableFuture.allOf(*resultList.toTypedArray()).thenApply {
DataFetcherResult.newResult<List<Any?>>().data(
resultList.asSequence()
.map { cf -> cf.join() }
.flatMap { r -> if (r is Collection<*>) r.asSequence() else sequenceOf(r) }
.toList()
)
.errors(errorsList)
DataFetcherResult.newResult<List<Any?>>()
.data(
resultList.asSequence()
.map { cf -> cf.join() }
.map { tryResult -> tryResult.orElse(null) }
.flatMap { r -> if (r is Collection<*>) r.asSequence() else sequenceOf(r) }
.toList()
)
.errors(
resultList.asSequence()
.map { cf -> cf.join() }
.filter { tryResult -> tryResult.isFailure }
.map { tryResult -> tryResult.throwable }
.flatMap { e ->
if (e is InvocationTargetException && e.targetException != null) {
if (dgsExceptionHandler.isPresent) {
val res = dgsExceptionHandler.get().onException(
DataFetcherExceptionHandlerParameters.newExceptionParameters()
.dataFetchingEnvironment(env).exception(e.targetException).build()
)
res.errors.asSequence()
} else {
sequenceOf(
TypedGraphQLError.newInternalErrorBuilder()
.message("%s: %s", e.targetException::class.java.name, e.targetException.message)
.path(ResultPath.parse("/_entities")).build()
)
}
} else {
sequenceOf(
TypedGraphQLError.newInternalErrorBuilder().message("%s: %s", e::class.java.name, e.message)
.path(ResultPath.parse("/_entities")).build()
)
}
}
.toList()
)
.build()
}
}
Expand Down
Expand Up @@ -237,6 +237,36 @@ class DgsFederationResolverTest {
assertThat(result.get().errors[0].message).contains("DgsInvalidInputArgumentException")
}

@Test
fun dgsEntityFetcherWithFailedCompletableFuture() {

val movieEntityFetcher = object {
@DgsEntityFetcher(name = "Movie")
fun movieEntityFetcher(values: Map<String, Any>): CompletableFuture<Movie> {
return CompletableFuture.supplyAsync {
if (values["movieId"] == "invalid") {
throw DgsInvalidInputArgumentException("Invalid input argument exception")
}

Movie(values["movieId"].toString())
}
}
}
every { applicationContextMock.getBeansWithAnnotation(DgsScalar::class.java) } returns emptyMap()
every { applicationContextMock.getBeansWithAnnotation(DgsDirective::class.java) } returns emptyMap()
every { applicationContextMock.getBeansWithAnnotation(DgsComponent::class.java) } returns mapOf(Pair("MovieEntityFetcher", movieEntityFetcher))
dgsSchemaProvider.schema("""type Query {}""")

val arguments = mapOf<String, Any>(Pair(_Entity.argumentName, listOf(mapOf(Pair("__typename", "Movie"), Pair("movieId", "invalid")))))
val executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().path(ResultPath.parse("/_entities")).type(GraphQLList.list(GraphQLUnionType.newUnionType().name("Entity").possibleTypes(GraphQLObjectType.newObject().name("Movie").build()).build())).build()
val dataFetchingEnvironment = DgsDataFetchingEnvironment(DataFetchingEnvironmentImpl.newDataFetchingEnvironment().arguments(arguments).executionStepInfo(executionStepInfo).build())
val result = (DefaultDgsFederationResolver(dgsSchemaProvider, Optional.of(dgsExceptionHandler)).entitiesFetcher().get(dataFetchingEnvironment) as CompletableFuture<DataFetcherResult<List<*>>>)
assertThat(result).isNotNull
assertThat(result.get().data.size).isEqualTo(1)
assertThat(result.get().errors.size).isEqualTo(1)
assertThat(result.get().errors[0].message).contains("DgsInvalidInputArgumentException")
}

@Test
fun dgsEntityFetcherWithIllegalArgumentException() {

Expand Down