Skip to content

Commit

Permalink
refactor: Reduce coupling with Type (#119)
Browse files Browse the repository at this point in the history
The current implementation is heavily coupled to `Type` which itself is
heavily coupled to actual Kotlin classes. This tries to move away from
`Type` when it is not needed to simplify working with non-Kotlin schemas
(like schemas created from SDL or introspection queries).
  • Loading branch information
stuebingerb authored Dec 20, 2024
2 parents c57f57f + b46b0c3 commit 1b68d58
Show file tree
Hide file tree
Showing 18 changed files with 147 additions and 132 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,6 @@ class DefaultSchema(
override fun typeByKClass(kClass: KClass<*>): Type? = model.queryTypes[kClass]

override fun inputTypeByKClass(kClass: KClass<*>): Type? = model.inputTypes[kClass]

override fun findTypeByName(name: String): Type? = model.allTypesByName[name]
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.apurebase.kgraphql.schema.introspection.__Directive
import com.apurebase.kgraphql.schema.introspection.__InputValue
import com.apurebase.kgraphql.schema.introspection.__Schema
import com.apurebase.kgraphql.schema.introspection.__Type
import com.apurebase.kgraphql.schema.introspection.typeReference
import com.apurebase.kgraphql.schema.model.Depreciable

data class SchemaPrinterConfig(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.apurebase.kgraphql.InvalidInputValueException
import com.apurebase.kgraphql.request.Variables
import com.apurebase.kgraphql.schema.introspection.TypeKind
import com.apurebase.kgraphql.schema.introspection.__Schema
import com.apurebase.kgraphql.schema.introspection.typeReference
import com.apurebase.kgraphql.schema.model.ast.ArgumentNodes
import com.apurebase.kgraphql.schema.model.ast.ValueNode
import com.apurebase.kgraphql.schema.model.ast.ValueNode.ObjectValueNode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import com.apurebase.kgraphql.request.Variables
import com.apurebase.kgraphql.request.VariablesJson
import com.apurebase.kgraphql.schema.DefaultSchema
import com.apurebase.kgraphql.schema.introspection.TypeKind
import com.apurebase.kgraphql.schema.introspection.__Field
import com.apurebase.kgraphql.schema.introspection.__Type
import com.apurebase.kgraphql.schema.model.FunctionWrapper
import com.apurebase.kgraphql.schema.model.ast.ArgumentNodes
import com.apurebase.kgraphql.schema.scalar.serializeScalar
Expand Down Expand Up @@ -76,7 +78,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
applyKeyToElement(ctx, result, node, node.field.returnType, 1)
}

private fun Any?.toPrimitive(node: Execution.Node, returnType: Type): JsonElement = when {
private fun Any?.toPrimitive(node: Execution.Node, returnType: __Type): JsonElement = when {
this == null -> createNullNode(node, returnType.unwrapList())
this is Collection<*> || this is Array<*> -> when (this) {
is Array<*> -> toList()
Expand All @@ -97,7 +99,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
ctx: ExecutionContext,
value: T?,
node: Execution.Node,
returnType: Type,
returnType: __Type,
parentCount: Long
) {
return when {
Expand Down Expand Up @@ -162,22 +164,22 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
}
}

private fun <T> createSimpleValueNode(returnType: Type, value: T, node: Execution.Node): JsonElement {
private fun <T> createSimpleValueNode(returnType: __Type, value: T, node: Execution.Node): JsonElement {
return when (val unwrapped = returnType.unwrapped()) {
is Type.Scalar<*> -> {
serializeScalar(unwrapped, value, node)
}

is Type.Enum<*> -> JsonPrimitive(value.toString())
else -> throw ExecutionException("Invalid Type: ${returnType.name}", node)
else -> throw ExecutionException("Invalid Type: ${unwrapped.name}", node)
}
}

private suspend fun <T> DeferredJsonMap.applyObjectProperties(
ctx: ExecutionContext,
value: T,
node: Execution.Node,
type: Type,
type: __Type,
parentCount: Long
) {
node.children.map { child ->
Expand All @@ -197,7 +199,9 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
return
}

val expectedType = container.condition.type
val expectedType = checkNotNull(schema.findTypeByName(container.condition.onType)) {
"Unable to find type ${container.condition.onType}"
}

if (expectedType.kind == TypeKind.OBJECT || expectedType.kind == TypeKind.INTERFACE) {
if (expectedType.isInstance(value)) {
Expand All @@ -223,7 +227,8 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
private suspend fun <T> DeferredJsonMap.applyProperty(
ctx: ExecutionContext,
value: T,
child: Execution, type: Type,
child: Execution,
type: __Type,
parentCount: Long
) {
when (child) {
Expand Down Expand Up @@ -286,7 +291,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
ctx: ExecutionContext,
parentValue: T,
node: Execution.Node,
field: Field,
field: __Field,
parentCount: Long
) {
node.field.checkAccess(parentValue, ctx.requestContext)
Expand All @@ -296,11 +301,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec

when (field) {
is Field.Kotlin<*, *> -> {
val rawValue = try {
(field.kProperty as KProperty1<T, *>).get(parentValue)
} catch (e: NullPointerException) {
throw e
}
val rawValue = (field.kProperty as KProperty1<T, *>).get(parentValue)
val value: Any? = field.transformation?.invoke(
funName = field.name,
receiver = rawValue,
Expand All @@ -322,7 +323,7 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
handleDataPropertyAsync(ctx, parentValue, node, field, parentCount)
}

else -> error("Only Kotlin Fields are supported!")
else -> error("Unexpected field type: $field, should be Field.Kotlin, Field.Function or Field.DataLoader")
}
}

Expand Down Expand Up @@ -395,11 +396,12 @@ class DataLoaderPreparedRequestExecutor(val schema: DefaultSchema) : RequestExec
result.await().toString()
}

private fun createNullNode(node: Execution.Node, returnType: Type): JsonNull = if (returnType !is Type.NonNull) {
JsonNull
} else {
throw ExecutionException("null result for non-nullable operation ${node.field}", node)
}
private fun createNullNode(node: Execution.Node, returnType: __Type): JsonNull =
if (returnType.kind != TypeKind.NON_NULL) {
JsonNull
} else {
throw ExecutionException("null result for non-nullable operation ${node.field}", node)
}

private suspend fun shouldInclude(ctx: ExecutionContext, executionNode: Execution): Boolean {
if (executionNode.directives?.isEmpty() == true) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.apurebase.kgraphql.schema.execution

import com.apurebase.kgraphql.schema.directive.Directive
import com.apurebase.kgraphql.schema.introspection.NotIntrospected
import com.apurebase.kgraphql.schema.introspection.__Type
import com.apurebase.kgraphql.schema.structure.Field
import com.apurebase.kgraphql.schema.structure.Type
import com.apurebase.kgraphql.schema.model.ast.ArgumentNodes
import com.apurebase.kgraphql.schema.model.ast.SelectionNode
import com.apurebase.kgraphql.schema.model.ast.VariableDefinitionNode
Expand Down Expand Up @@ -37,7 +37,7 @@ sealed class Execution {
class Union(
node: SelectionNode,
val unionField: Field.Union<*>,
val memberChildren: Map<Type, Collection<Execution>>,
val memberChildren: Map<__Type, Collection<Execution>>,
key: String,
alias: String? = null,
condition: TypeCondition? = null,
Expand All @@ -51,7 +51,7 @@ sealed class Execution {
typeCondition = condition,
directives = directives
) {
fun memberExecution(type: Type): Node {
fun memberExecution(type: __Type): Node {
return Node(
selectionNode = selectionNode,
field = field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.apurebase.kgraphql.request.Variables
import com.apurebase.kgraphql.request.VariablesJson
import com.apurebase.kgraphql.schema.DefaultSchema
import com.apurebase.kgraphql.schema.introspection.TypeKind
import com.apurebase.kgraphql.schema.introspection.__Field
import com.apurebase.kgraphql.schema.introspection.__Type
import com.apurebase.kgraphql.schema.model.FunctionWrapper
import com.apurebase.kgraphql.schema.model.ast.ArgumentNodes
import com.apurebase.kgraphql.schema.scalar.serializeScalar
Expand Down Expand Up @@ -129,7 +131,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
ctx: ExecutionContext,
value: T?,
node: Execution.Node,
returnType: Type
returnType: __Type
): JsonNode {
if (value == null) {
return createNullNode(node, returnType)
Expand All @@ -140,7 +142,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
}

return when {
//check value, not returnType, because this method can be invoked with element value
// Check value, not returnType, because this method can be invoked with element value
value is Collection<*> || value is Array<*> -> {
val values: Collection<*> = when (value) {
is Array<*> -> value.toList()
Expand Down Expand Up @@ -178,7 +180,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
}
}

private fun <T> createSimpleValueNode(returnType: Type, value: T, node: Execution.Node): JsonNode =
private fun <T> createSimpleValueNode(returnType: __Type, value: T, node: Execution.Node): JsonNode =
when (val unwrapped = returnType.unwrapped()) {
is Type.Scalar<*> -> {
serializeScalar(jsonNodeFactory, unwrapped, value, node)
Expand All @@ -188,11 +190,11 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
jsonNodeFactory.textNode(value.toString())
}

else -> throw ExecutionException("Invalid Type: ${returnType.name}", node)
else -> throw ExecutionException("Invalid Type: ${unwrapped.name}", node)
}

private fun createNullNode(node: Execution.Node, returnType: Type): NullNode {
if (returnType !is Type.NonNull) {
private fun createNullNode(node: Execution.Node, returnType: __Type): NullNode {
if (returnType.kind != TypeKind.NON_NULL) {
return jsonNodeFactory.nullNode()
} else {
throw ExecutionException("null result for non-nullable operation ${node.field}", node)
Expand All @@ -203,7 +205,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
ctx: ExecutionContext,
value: T,
node: Execution.Node,
type: Type
type: __Type
): ObjectNode {
val objectNode = jsonNodeFactory.objectNode()
for (child in node.children) {
Expand All @@ -222,7 +224,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
ctx: ExecutionContext,
value: T,
child: Execution,
type: Type
type: __Type
): Pair<String, JsonNode?>? {
when (child) {
// Union is subclass of Node so check it first
Expand Down Expand Up @@ -255,7 +257,9 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
value: T,
container: Execution.Fragment
): Map<String, JsonNode?> {
val expectedType = container.condition.type
val expectedType = checkNotNull(schema.findTypeByName(container.condition.onType)) {
"Unable to find type ${container.condition.onType}"
}
val include = shouldInclude(ctx, container)

if (include) {
Expand Down Expand Up @@ -286,7 +290,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
ctx: ExecutionContext,
parentValue: T,
node: Execution.Node,
field: Field
field: __Field
): JsonNode? {
val include = shouldInclude(ctx, node)
node.field.checkAccess(parentValue, ctx.requestContext)
Expand Down Expand Up @@ -322,9 +326,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
return handleDataProperty(ctx, parentValue, node, field)
}

else -> {
throw Exception("Unexpected field type: $field, should be Field.Kotlin or Field.Function")
}
else -> error("Unexpected field type: $field, should be Field.Kotlin, Field.Function or Field.DataLoader")
}
} else {
return null
Expand Down Expand Up @@ -406,6 +408,7 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor {
executionNode,
ctx.requestContext
)

// exceptions are not caught on purpose to pass up business logic errors
return try {
when {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.apurebase.kgraphql.schema.execution

import com.apurebase.kgraphql.schema.structure.Type

/**
* type conditions can be declared on fragments
*/
class TypeCondition(val type: Type)
class TypeCondition(val onType: String)
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,4 @@ class SchemaProxy(

override val directives: List<__Directive>
get() = getProxied().directives

override fun findTypeByName(name: String): __Type? = getProxied().findTypeByName(name)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.apurebase.kgraphql.schema.introspection

interface __Schema {

val types: List<__Type>

val queryType: __Type
Expand All @@ -11,6 +10,4 @@ interface __Schema {
val subscriptionType: __Type?

val directives: List<__Directive>

fun findTypeByName(name: String): __Type?
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,28 @@ interface __Type {

// NON_NULL and LIST only
val ofType: __Type?
}

fun __Type.typeReference(): String = when (kind) {
TypeKind.NON_NULL -> "${ofType?.typeReference()}!"
TypeKind.LIST -> "[${ofType?.typeReference()}]"
else -> name ?: ""
operator fun get(name: String): __Field? = null

fun isList(): Boolean = when {
kind == TypeKind.LIST -> true
ofType == null -> false
else -> (ofType as __Type).isList()
}

fun typeReference(): String = when (kind) {
TypeKind.NON_NULL -> "${ofType?.typeReference()}!"
TypeKind.LIST -> "[${ofType?.typeReference()}]"
else -> name ?: ""
}

fun unwrapped(): __Type = when (kind) {
TypeKind.NON_NULL, TypeKind.LIST -> ofType!!.unwrapped()
else -> this
}

fun unwrapList(): __Type = when (kind) {
TypeKind.LIST -> ofType as __Type
else -> ofType?.unwrapList() ?: throw NoSuchElementException("this type does not wrap list element")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ interface TypeDef {
fun toScalarType(): Type.Scalar<T> = Type.Scalar(this)
}

//To avoid circular dependencies etc. union type members are resolved in runtime
// To avoid circular dependencies etc. union type members are resolved in runtime
class Union(
name: String,
val members: Set<KClass<*>>,
Expand Down
Loading

0 comments on commit 1b68d58

Please sign in to comment.