Skip to content

Commit

Permalink
Return partial result on hydration exception (#650)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnawf authored Dec 9, 2024
1 parent ef7b74a commit d60103a
Show file tree
Hide file tree
Showing 5 changed files with 669 additions and 7 deletions.
24 changes: 17 additions & 7 deletions lib/src/main/java/graphql/nadel/NextgenEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,23 @@ internal class NextgenEngine(
executionContext: NadelExecutionContext,
hydrationDetails: ServiceExecutionHydrationDetails,
): ServiceExecutionResult {
return executeTopLevelField(
topLevelField = topLevelField,
service = service,
executionContext = executionContext.copy(
hydrationDetails = hydrationDetails,
),
)
return try {
executeTopLevelField(
topLevelField = topLevelField,
service = service,
executionContext = executionContext.copy(
hydrationDetails = hydrationDetails,
),
)
} catch (e: Exception) {
when (e) {
is GraphQLError -> newServiceExecutionErrorResult(
field = topLevelField,
error = e,
)
else -> throw e
}
}
}

internal suspend fun executePartitionedCall(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package graphql.nadel.tests.next.fixtures.hydration

import graphql.nadel.Nadel
import graphql.nadel.ServiceExecutionHydrationDetails
import graphql.nadel.ServiceExecutionResult
import graphql.nadel.engine.NadelExecutionContext
import graphql.nadel.engine.NadelServiceExecutionContext
import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprint
import graphql.nadel.engine.transform.NadelTransform
import graphql.nadel.engine.transform.NadelTransformFieldResult
import graphql.nadel.engine.transform.query.NadelQueryTransformer
import graphql.nadel.engine.transform.result.NadelResultInstruction
import graphql.nadel.engine.transform.result.json.JsonNodes
import graphql.nadel.error.NadelGraphQLErrorException
import graphql.nadel.tests.next.NadelIntegrationTest
import graphql.normalized.ExecutableNormalizedField

class HydrationThrowsErrorTest : NadelIntegrationTest(
query = """
query {
issueById(id: "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1") {
id
key
assignee {
id
name
}
}
}
""".trimIndent(),
variables = mapOf(),
services = listOf(
Service(
name = "issues",
overallSchema = """
type Query {
issueById(id: ID!): Issue
}
type Issue {
id: ID!
key: String
assigneeId: ID @hidden
assignee: User
@hydrated(
service: "identity"
field: "userById"
arguments: [{name: "id", value: "$source.assigneeId"}]
)
}
""".trimIndent(),
runtimeWiring = { wiring ->
data class Issue(
val id: String,
val key: String,
val assigneeId: String? = null,
)

val issuesById = listOf(
Issue(
id = "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1",
key = "GQLGW-1",
assigneeId = "ari:cloud:identity::user/1",
)
).associateBy { it.id }

wiring
.type("Query") { type ->
type.dataFetcher("issueById") {
issuesById[it.getArgument("id")]
}
}
},
),
Service(
name = "identity",
overallSchema = """
type Query {
userById(id: ID!): User
}
type User {
id: ID!
name: String
}
""".trimIndent(),
runtimeWiring = { wiring ->
},
),
),
) {
override fun makeNadel(): Nadel.Builder {
return super.makeNadel()
.transforms(
listOf(
ThrowErrorTransform(),
),
)
}

private class ThrowErrorTransform : NadelTransform<Any> {
override suspend fun isApplicable(
executionContext: NadelExecutionContext,
serviceExecutionContext: NadelServiceExecutionContext,
executionBlueprint: NadelOverallExecutionBlueprint,
services: Map<String, graphql.nadel.Service>,
service: graphql.nadel.Service,
overallField: ExecutableNormalizedField,
hydrationDetails: ServiceExecutionHydrationDetails?,
): Any? {
if (overallField.name == "userById") {
throw Bye()
}

return null
}

override suspend fun getResultInstructions(
executionContext: NadelExecutionContext,
serviceExecutionContext: NadelServiceExecutionContext,
executionBlueprint: NadelOverallExecutionBlueprint,
service: graphql.nadel.Service,
overallField: ExecutableNormalizedField,
underlyingParentField: ExecutableNormalizedField?,
result: ServiceExecutionResult,
state: Any,
nodes: JsonNodes,
): List<NadelResultInstruction> {
throw UnsupportedOperationException()
}

override suspend fun transformField(
executionContext: NadelExecutionContext,
serviceExecutionContext: NadelServiceExecutionContext,
transformer: NadelQueryTransformer,
executionBlueprint: NadelOverallExecutionBlueprint,
service: graphql.nadel.Service,
field: ExecutableNormalizedField,
state: Any,
): NadelTransformFieldResult {
throw UnsupportedOperationException()
}
}

class Bye : NadelGraphQLErrorException(
message = "BYE",
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// @formatter:off
package graphql.nadel.tests.next.fixtures.hydration

import graphql.nadel.tests.next.ExpectedNadelResult
import graphql.nadel.tests.next.ExpectedServiceCall
import graphql.nadel.tests.next.TestSnapshot
import graphql.nadel.tests.next.listOfJsonStrings
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.listOf

private suspend fun main() {
graphql.nadel.tests.next.update<HydrationThrowsErrorTest>()
}

/**
* This class is generated. Do NOT modify.
*
* Refer to [graphql.nadel.tests.next.UpdateTestSnapshots
*/
@Suppress("unused")
public class HydrationThrowsErrorTestSnapshot : TestSnapshot() {
override val calls: List<ExpectedServiceCall> = listOf(
ExpectedServiceCall(
service = "issues",
query = """
| {
| issueById(id: "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1") {
| id
| key
| hydration__assignee__assigneeId: assigneeId
| __typename__hydration__assignee: __typename
| }
| }
""".trimMargin(),
variables = " {}",
result = """
| {
| "data": {
| "issueById": {
| "id": "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1",
| "key": "GQLGW-1",
| "hydration__assignee__assigneeId": "ari:cloud:identity::user/1",
| "__typename__hydration__assignee": "Issue"
| }
| }
| }
""".trimMargin(),
delayedResults = listOfJsonStrings(
),
),
)

/**
* ```json
* {
* "errors": [
* {
* "message": "BYE",
* "locations": [],
* "extensions": {
* "classification": "Bye"
* }
* }
* ],
* "data": {
* "issueById": {
* "id": "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1",
* "key": "GQLGW-1",
* "assignee": null
* }
* }
* }
* ```
*/
override val result: ExpectedNadelResult = ExpectedNadelResult(
result = """
| {
| "errors": [
| {
| "message": "BYE",
| "locations": [],
| "extensions": {
| "classification": "Bye"
| }
| }
| ],
| "data": {
| "issueById": {
| "id": "ari:cloud:jira:19b8272f-8d25-4706-adce-8db72305e615:issue/1",
| "key": "GQLGW-1",
| "assignee": null
| }
| }
| }
""".trimMargin(),
delayedResults = listOfJsonStrings(
),
)
}
Loading

0 comments on commit d60103a

Please sign in to comment.