Skip to content
Open
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
30 changes: 0 additions & 30 deletions kgraphql/api/kgraphql.api
Original file line number Diff line number Diff line change
Expand Up @@ -826,15 +826,6 @@ public final class de/stuebingerb/kgraphql/schema/execution/Execution$Remote : d
public synthetic fun withParent$de_stuebingerb_kgraphql (Lde/stuebingerb/kgraphql/schema/execution/Execution;)Lde/stuebingerb/kgraphql/schema/execution/Execution;
}

public final class de/stuebingerb/kgraphql/schema/execution/Execution$Union : de/stuebingerb/kgraphql/schema/execution/Execution$Node {
public fun <init> (Lde/stuebingerb/kgraphql/schema/model/ast/SelectionNode$FieldNode;Lde/stuebingerb/kgraphql/schema/structure/Field$Union;Ljava/util/Map;Ljava/util/Map;Lde/stuebingerb/kgraphql/schema/execution/Execution;)V
public final fun getMemberChildren ()Ljava/util/Map;
public final fun getUnionField ()Lde/stuebingerb/kgraphql/schema/structure/Field$Union;
public final fun memberExecution (Lde/stuebingerb/kgraphql/schema/structure/Type;)Lde/stuebingerb/kgraphql/schema/execution/Execution$Node;
public synthetic fun withParent$de_stuebingerb_kgraphql (Lde/stuebingerb/kgraphql/schema/execution/Execution;)Lde/stuebingerb/kgraphql/schema/execution/Execution$Node;
public synthetic fun withParent$de_stuebingerb_kgraphql (Lde/stuebingerb/kgraphql/schema/execution/Execution;)Lde/stuebingerb/kgraphql/schema/execution/Execution;
}

public final class de/stuebingerb/kgraphql/schema/execution/ExecutionMode : java/lang/Enum {
public static final field Normal Lde/stuebingerb/kgraphql/schema/execution/ExecutionMode;
public static final field Serial Lde/stuebingerb/kgraphql/schema/execution/ExecutionMode;
Expand Down Expand Up @@ -2089,27 +2080,6 @@ public final class de/stuebingerb/kgraphql/schema/structure/Field$RemoteOperatio
public fun isDeprecated ()Z
}

public final class de/stuebingerb/kgraphql/schema/structure/Field$Union : de/stuebingerb/kgraphql/schema/structure/Field, de/stuebingerb/kgraphql/schema/model/FunctionWrapper {
public fun <init> (Lde/stuebingerb/kgraphql/schema/model/PropertyDef$Union;Lde/stuebingerb/kgraphql/schema/structure/Type;Ljava/util/List;)V
public fun arity ()I
public fun checkAccess (Ljava/lang/Object;Lde/stuebingerb/kgraphql/Context;)V
public fun getArguments ()Ljava/util/List;
public fun getArgumentsDescriptor ()Ljava/util/Map;
public fun getDeprecationReason ()Ljava/lang/String;
public fun getDescription ()Ljava/lang/String;
public fun getHasReceiver ()Z
public fun getKFunction ()Lkotlin/reflect/KFunction;
public fun getName ()Ljava/lang/String;
public fun getReturnType ()Lde/stuebingerb/kgraphql/schema/structure/Type;
public fun hasReturnType ()Z
public fun invoke (Ljava/util/List;Ljava/util/List;Lcom/fasterxml/jackson/databind/ObjectWriter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun invoke ([Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun isDeprecated ()Z
public fun subscribe (Ljava/lang/String;Lde/stuebingerb/kgraphql/schema/Subscriber;)V
public fun unsubscribe (Ljava/lang/String;)V
public fun valueParameters ()Ljava/util/List;
}

public final class de/stuebingerb/kgraphql/schema/structure/InputValue : de/stuebingerb/kgraphql/schema/introspection/__InputValue {
public fun <init> (Lde/stuebingerb/kgraphql/schema/model/InputValueDef;Lde/stuebingerb/kgraphql/schema/structure/Type;Ljava/lang/String;)V
public synthetic fun <init> (Lde/stuebingerb/kgraphql/schema/model/InputValueDef;Lde/stuebingerb/kgraphql/schema/structure/Type;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import de.stuebingerb.kgraphql.schema.model.ast.OperationTypeNode
import de.stuebingerb.kgraphql.schema.model.ast.SelectionNode
import de.stuebingerb.kgraphql.schema.model.ast.VariableDefinitionNode
import de.stuebingerb.kgraphql.schema.structure.Field
import de.stuebingerb.kgraphql.schema.structure.Type

sealed class Execution {
abstract val selectionNode: SelectionNode
Expand Down Expand Up @@ -74,44 +73,6 @@ sealed class Execution {
)
}

class Union(
node: SelectionNode.FieldNode,
val unionField: Field.Union<*>,
val memberChildren: Map<Type, Collection<Execution>>,
directives: Map<Directive, ArgumentNodes?>?,
parent: Execution?
) : Node(
selectionNode = node,
field = unionField,
children = emptyList(),
arguments = null,
directives = directives,
variables = null,
arrayIndex = null,
parent = parent
) {
fun memberExecution(type: Type): Node = Node(
selectionNode = selectionNode,
field = field,
children = requireNotNull(memberChildren[type]) {
"Union ${unionField.name} has no member $type"
},
arguments = arguments,
directives = directives,
variables = variables,
arrayIndex = arrayIndex,
parent = parent
)

override fun withParent(parent: Execution): Union = Union(
selectionNode,
unionField,
memberChildren,
directives,
parent
)
}

class Remote(
selectionNode: SelectionNode.FieldNode,
field: Field,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ internal class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecu
forEach { execution ->
when (execution) {
is Execution.Fragment -> execution.elements.inspect()
is Execution.Union -> execution.memberChildren.values.flatten().inspect()
is Execution.Node -> {
execution.children.inspect()
if (execution.field is Field.DataLoader<*, *, *>) {
Expand Down Expand Up @@ -126,40 +125,6 @@ internal class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecu
jsonNodeFactory.nullNode()
}

private suspend fun <T> createUnionOperationNode(
ctx: ExecutionContext,
parent: T,
node: Execution.Union,
unionProperty: Field.Union<T>
): Deferred<JsonNode> {
try {
node.field.checkAccess(parent, ctx.requestContext)
} catch (e: Throwable) {
return handleException(ctx, node, node.field.returnType, e)
}
val operationResult: Any? = unionProperty.invoke(
funName = unionProperty.name,
receiver = parent,
inputValues = node.field.arguments,
args = node.arguments,
executionNode = node,
ctx = ctx
).await()

val possibleTypes = (unionProperty.returnType.unwrapped() as Type.Union).possibleTypes
val returnType = possibleTypes.find { it.isInstance(operationResult) }

if (returnType == null && unionProperty.returnType.isNotNullable()) {
val expectedOneOf = possibleTypes.joinToString { it.name.toString() }
throw ExecutionException(
"Unexpected type of union property value, expected one of [$expectedOneOf] but was '$operationResult'",
node
)
}

return createNode(ctx, operationResult, node, returnType ?: unionProperty.returnType)
}

private suspend fun <T> createNode(
ctx: ExecutionContext,
value: T?,
Expand Down Expand Up @@ -224,13 +189,6 @@ internal class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecu

node.children.isNotEmpty() -> createObjectNode(ctx, value, node, returnType)

node is Execution.Union -> createObjectNode(
ctx,
value,
node.memberExecution(returnType),
returnType
)

// TODO: do we have to consider more? more validation e.g.?
value is JsonNode -> CompletableDeferred(value)

Expand Down Expand Up @@ -291,21 +249,6 @@ internal class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecu
type: Type
): Pair<String, Deferred<JsonNode>>? {
when (child) {
// Union is subclass of Node so check it first
is Execution.Union -> {
val field = checkNotNull(type.unwrapped()[child.key]) {
"Execution unit '${child.key}' is not contained by operation return type '${type.unwrapped().name}'"
}
if (field is Field.Union<*>) {
return child.aliasOrKey to createUnionOperationNode(ctx, value, child, field as Field.Union<T>)
} else {
throw ExecutionException(
"Unexpected non-union field for union execution node '${child.aliasOrKey}'",
child
)
}
}

is Execution.Remote -> {
return child.aliasOrKey to handleFunctionProperty(
ctx,
Expand Down Expand Up @@ -407,7 +350,7 @@ internal class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecu
return handleDataProperty(ctx, parentValue, node, field)
}

else -> error("Unexpected field type '$field', should be Field.Kotlin, Field.Function or Field.DataLoader")
else -> error("Unexpected field type '$field', should be Field.Kotlin, Field.Function, or Field.DataLoader")
}
} else {
return null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,25 +84,6 @@ sealed class Field : __Field {
}
}

class Union<T>(
private val kql: PropertyDef.Union<T>,
override val returnType: Type,
override val arguments: List<InputValue<*>>
) : Field(), FunctionWrapper<Any?> by kql {

override val name: String = kql.name

override val description: String? = kql.description

override val isDeprecated: Boolean = kql.isDeprecated

override val deprecationReason: String? = kql.deprecationReason

override fun checkAccess(parent: Any?, ctx: Context) {
kql.accessRule?.invoke(parent as T?, ctx)?.let { throw it }
}
}

class RemoteOperation<T, R>(
private val kql: BaseOperationDef<T, R>,
val field: Delegated,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,6 @@ internal class RequestInterpreter(private val schemaModel: SchemaModel) {
// prevent stack overflow
private val fragmentsStack = Stack<String>()

fun getFragment(name: String) =
checkNotNull(fragments[name]) { "Fragment '$name' not found" }.also { usedFragments.add(name) }

fun get(node: FragmentSpreadNode): Execution.Fragment? {
if (fragmentsStack.contains(node.name.value)) {
throw ValidationException("Fragment spread circular references are not allowed", node)
Expand Down Expand Up @@ -176,8 +173,6 @@ internal class RequestInterpreter(private val schemaModel: SchemaModel) {
node
)

is Field.Union<*> -> handleUnion(field, node, ctx)

is Field.RemoteOperation<*, *> -> handleRemoteOperation(field, node, ctx)

else -> {
Expand Down Expand Up @@ -251,52 +246,6 @@ internal class RequestInterpreter(private val schemaModel: SchemaModel) {
)
}

private fun <T> handleUnion(
field: Field.Union<T>,
selectionNode: FieldNode,
ctx: InterpreterContext
): Execution.Union {
validateUnionRequest(field, selectionNode)

// https://spec.graphql.org/October2021/#sec-Unions
// "With interfaces and objects, only those fields defined on the type can be queried directly; to query
// other fields on an interface, typed fragments must be used. This is the same as for unions, but unions
// do not define any fields, so *no* fields may be queried on this type without the use of type refining
// fragments or inline fragments (with the exception of the meta-field `__typename`)."
val unionMembersChildren: Map<Type, List<Execution>> =
(field.returnType.unwrapped() as Type.Union).possibleTypes.associateWith { possibleType ->
val mergedSelectionsForType = selectionNode.selectionSet?.selections?.flatMap {
when {
// Only __typename is allowed as field selection
it is FieldNode && it.name.value == "__typename"
-> listOf(it)

it is FragmentSpreadNode && ctx.getFragment(it.name.value).first.name == possibleType.name
-> ctx.getFragment(it.name.value).second.selections

it is InlineFragmentNode && possibleType.name == it.typeCondition?.name?.value
-> it.selectionSet.selections

else -> emptyList()
}
}

if (!mergedSelectionsForType.isNullOrEmpty()) {
handleReturnType(ctx, possibleType, SelectionSetNode(null, mergedSelectionsForType))
} else {
emptyList()
}
}

return Execution.Union(
node = selectionNode,
unionField = field,
memberChildren = unionMembersChildren,
directives = selectionNode.directives?.lookup(),
parent = null
)
}

private fun Type?.validateForFragment(fragment: ASTNode, enclosingType: Type? = null): Type {
val (typeName, fragmentName) = when (fragment) {
is InlineFragmentNode -> fragment.typeCondition?.name?.value to "inline fragment"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ open class SchemaCompilation(
private suspend fun handleUnionProperty(unionProperty: PropertyDef.Union<*>): Field {
val inputValues = handleInputValues(unionProperty, unionProperty.inputValues)
val type = applyNullability(unionProperty.nullable, handleUnionType(unionProperty.union))
return Field.Union(unionProperty, type, inputValues)
return Field.Function(unionProperty, type, inputValues)
}

private suspend fun handlePossiblyWrappedType(kType: KType, typeCategory: TypeCategory): Type = when {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,25 +57,6 @@ private fun Field.validateArguments(requestNode: FieldNode, parentTypeName: Stri
return exceptions
}

/**
* validate that only typed fragments or __typename are present
*/
internal fun validateUnionRequest(field: Field.Union<*>, selectionNode: FieldNode) {
val illegalChildren =
selectionNode.selectionSet?.selections?.filterIsInstance<FieldNode>()?.filter { it.name.value != "__typename" }

if (!illegalChildren.isNullOrEmpty()) {
throw ValidationException(
message = "Invalid selection set with properties: ${
illegalChildren.joinToString(prefix = "[", postfix = "]") {
it.aliasOrName.value
}
} on union type property ${field.name} : ${(field.returnType.unwrapped() as Type.Union).possibleTypes.map { it.name }}",
node = selectionNode
)
}
}

fun validateName(name: String) {
if (name.startsWith("__")) {
throw SchemaException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ class SchemaBuilderTest {

val unionField = scenarioType["pdf"]
unionField shouldNotBe null
unionField shouldBeInstanceOf Field.Union::class
unionField shouldBeInstanceOf Field.Function::class
}

@Test
Expand Down
Loading
Loading