Thanks to visit codestin.com
Credit goes to github.com

Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,13 @@ package viaduct.engine.api
* An EngineObjectData that is not fully resolved until [resolveData] is called.
*/
interface LazyEngineObjectData : EngineObjectData {
/**
* Resolves the data for this lazy object.
*
* @return true if the data was resolved by this call, false if it was already called previously
*/
suspend fun resolveData(
selections: RawSelectionSet,
context: EngineExecutionContext
)
): Boolean
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import viaduct.graphql.utils.DefaultSchemaProvider

typealias CheckerFn = suspend (arguments: Map<String, Any?>, objectDataMap: Map<String, EngineObjectData>) -> Unit
typealias NodeBatchResolverFn = suspend (selectors: List<NodeResolverExecutor.Selector>, context: EngineExecutionContext) -> Map<NodeResolverExecutor.Selector, Result<EngineObjectData>>
typealias NodeUnbatchedResolverFn = (id: String, selections: RawSelectionSet?, context: EngineExecutionContext) -> EngineObjectData
typealias NodeUnbatchedResolverFn = suspend (id: String, selections: RawSelectionSet?, context: EngineExecutionContext) -> EngineObjectData
typealias FieldUnbatchedResolverFn = suspend (
arguments: Map<String, Any?>,
objectValue: EngineObjectData,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package viaduct.engine.runtime

import graphql.schema.GraphQLObjectType
import java.util.concurrent.atomic.AtomicBoolean
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.async
import kotlinx.coroutines.supervisorScope
Expand All @@ -20,6 +21,7 @@ class NodeEngineObjectDataImpl(
) : NodeEngineObjectData, NodeReference {
private lateinit var resolvedEngineObjectData: EngineObjectData
private val resolving = CompletableDeferred<Unit>()
private val resolveDataCalled = AtomicBoolean(false)

override suspend fun fetch(selection: String): Any? = idOrWait(selection) ?: resolvedEngineObjectData.fetch(selection)

Expand All @@ -40,11 +42,17 @@ class NodeEngineObjectDataImpl(

/**
* To be called by the engine to resolve this node reference.
*
* @return true if the data was resolved by this call, false if it was already called previously
*/
override suspend fun resolveData(
selections: RawSelectionSet,
context: EngineExecutionContext
) {
): Boolean {
if (!resolveDataCalled.compareAndSet(false, true)) {
return false
}

try {
val nodeResolver = resolverRegistry.getNodeResolverDispatcher(graphQLObjectType.name)
?: throw IllegalStateException("No node resolver found for type ${graphQLObjectType.name}")
Expand Down Expand Up @@ -82,6 +90,7 @@ class NodeEngineObjectDataImpl(
resolvedEngineObjectData = nodeResolver.resolve(id, selections, context)
resolving.complete(Unit)
}
return true
} catch (e: Exception) {
resolving.completeExceptionally(e)
throw e
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,24 +152,33 @@ class ObjectEngineResultImpl private constructor(
private fun maybeInitializeKey(key: ObjectEngineResult.Key): Cell = storage.computeIfAbsent(key) { newCell() }

/**
* Resolves this OER exceptionally if it is in the pending state
* Resolves this OER exceptionally if it is in the pending state.
* If already resolved with the same exception, this is a no-op.
*
* @throws IllegalStateException if this OER is already resolved
* @throws IllegalStateException if this OER is already resolved normally or with a different exception
*/
internal fun resolveExceptionally(exception: Throwable) {
if (!oerState.completeExceptionally(exception)) {
throw IllegalStateException("Invariant: already resolved")
val completionException = oerState.getCompletionExceptionOrNull()
if (completionException == exception) {
return
}
// Otherwise it was resolved normally or with a different exception
throw IllegalStateException("Invariant: already resolved", completionException)
}
}

/**
* Resolves this OER if it is in the pending state
* Resolves this OER if it is in the pending state.
* If already resolved normally, this is a no-op.
*
* @throws IllegalStateException if this OER is already resolved
* @throws IllegalStateException if this OER is already resolved exceptionally
*/
internal fun resolve() {
if (!oerState.complete(Unit)) {
throw IllegalStateException("Invariant: already resolved")
oerState.getCompletionExceptionOrNull()?.let {
throw IllegalStateException("Invariant: already resolved exceptionally", it)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package viaduct.engine.runtime

import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import kotlinx.coroutines.ExperimentalCoroutinesApi
Expand Down Expand Up @@ -117,4 +118,22 @@ class NodeEngineObjectDataImplTest {
nodeReference.fetch("foo")
}
}

@Test
fun `resolveData called twice`(): Unit =
runBlocking {
every { dispatcherRegistry.getNodeResolverDispatcher("TestType") }.returns(nodeResolver)
coEvery { nodeResolver.resolve("testID", selections, context) }.returns(engineObjectData)
coEvery { engineObjectData.fetch("name") }.returns("testName")
every { dispatcherRegistry.getTypeCheckerDispatcher("TestType") }.returns(null)

val result1 = nodeReference.resolveData(selections, context)
assertEquals(true, result1)

val result2 = nodeReference.resolveData(selections, context)
assertEquals(false, result2)
coVerify(exactly = 1) { nodeResolver.resolve("testID", selections, context) }

assertEquals("testName", nodeReference.fetch("name"))
}
}
Loading