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
28 changes: 14 additions & 14 deletions docs/docs/recommended-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ sidebar_position: 4

# Recommended Usage

In general, the `resolverClasses` config should be used to generate more performant code. This is especially important
In general, the `resolverInterfaces` config should be used to generate more performant code. This is especially important
when dealing with expensive operations, such as database queries or network requests. When at least one field has
arguments in a type, we generate an interface with function signatures to be inherited in source code.
arguments in a type, we generate an open class with function signatures to be inherited in source code.
However, when fields have no arguments, we generate data classes by default.

## Example
Expand All @@ -33,8 +33,8 @@ Generated Kotlin:
```kotlin
package com.types.generated

interface Query {
fun resolveMyType(input: String): MyType
open class Query {
open fun resolveMyType(input: String): MyType = throw NotImplementedError("Query.resolveMyType must be implemented.")
}

data class MyType(
Expand All @@ -50,7 +50,7 @@ import com.expediagroup.graphql.server.operations.Query
import com.types.generated.MyType
import com.types.generated.Query as QueryInterface

class MyQuery : Query, QueryInterface {
class MyQuery : Query, QueryInterface() {
override fun resolveMyType(input: String): MyType =
MyType(
field1 = myExpensiveCall1(),
Expand All @@ -65,15 +65,15 @@ that the `field1` and `field2` properties are both initialized when the `MyType`
`myExpensiveCall1()` and `myExpensiveCall2()` will both be called in sequence! Even if I only query for `field1`, not
only will `myExpensiveCall2()` still run, but it will also wait until `myExpensiveCall1()` is totally finished.

### Instead, use the `resolverClasses` config!
### Instead, use the `resolverInterfaces` config!

Codegen config:

```ts
import { GraphQLKotlinCodegenConfig } from "@expediagroup/graphql-kotlin-codegen";

export default {
resolverClasses: [
resolverInterfaces: [
{
typeName: "MyType",
},
Expand All @@ -86,13 +86,13 @@ Generated Kotlin:
```kotlin
package com.types.generated

interface Query {
fun resolveMyType(input: String): MyType
open class Query {
open fun resolveMyType(input: String): MyType = throw NotImplementedError("Query.resolveMyType must be implemented.")
}

interface MyType {
fun field1(): String
fun field2(): String?
open class MyType {
open fun field1(): String = throw NotImplementedError("MyType.field1 must be implemented.")
open fun field2(): String? = throw NotImplementedError("MyType.field2 must be implemented.")
}
```

Expand All @@ -102,12 +102,12 @@ Source code:
import com.types.generated.MyType as MyTypeInterface
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore

class MyQuery : Query, QueryInterface {
class MyQuery : Query, QueryInterface() {
override fun resolveMyType(input: String): MyType = MyType()
}

@GraphQLIgnore
class MyType : MyTypeInterface {
class MyType : MyTypeInterface() {
override fun field1(): String = myExpensiveCall1()
override fun field2(): String? = myExpensiveCall2()
}
Expand Down
9 changes: 2 additions & 7 deletions src/definitions/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,8 @@ export function buildObjectTypeDefinition(
const fieldNodes = typeInResolverInterfacesConfig
? node.fields
: fieldsWithArguments;
const abstractModifier = constructor ? "abstract " : "";
const keyWord = constructor ? "abstract class" : "interface";
return `${annotations}${outputRestrictionAnnotation}${keyWord} ${name}${constructor}${interfaceInheritance} {
${getDataClassMembers({ node, fieldNodes, schema, config, shouldGenerateFunctions, abstractModifier })}
return `${annotations}${outputRestrictionAnnotation}open class ${name}${constructor}${interfaceInheritance} {
${getDataClassMembers({ node, fieldNodes, schema, config, shouldGenerateFunctions })}
}`;
}

Expand All @@ -115,14 +113,12 @@ function getDataClassMembers({
fieldNodes,
schema,
config,
abstractModifier,
shouldGenerateFunctions,
}: {
node: ObjectTypeDefinitionNode;
fieldNodes?: readonly FieldDefinitionNode[];
schema: GraphQLSchema;
config: CodegenConfigWithDefaults;
abstractModifier?: string;
shouldGenerateFunctions?: boolean;
}) {
return (fieldNodes ?? node.fields)
Expand All @@ -135,7 +131,6 @@ function getDataClassMembers({
config,
typeMetadata,
shouldGenerateFunctions,
abstractModifier,
);
})
.join(`${shouldGenerateFunctions ? "" : ","}\n`);
Expand Down
26 changes: 11 additions & 15 deletions src/helpers/build-field-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,8 @@ export function buildFieldDefinition(
config: CodegenConfigWithDefaults,
typeMetadata: TypeMetadata,
shouldGenerateFunctions?: boolean,
abstractModifier: string = "",
) {
const modifier = buildFieldModifier(
node,
fieldNode,
schema,
config,
abstractModifier,
);
const modifier = buildFieldModifier(node, fieldNode, schema, config);
const fieldArguments = buildFieldArguments(node, fieldNode, schema, config);
const fieldDefinition = `${modifier} ${fieldNode.name.value}${fieldArguments}`;
const annotations = buildAnnotations({
Expand All @@ -56,7 +49,8 @@ export function buildFieldDefinition(
);
}

const defaultFunctionValue = `${typeMetadata.isNullable ? "?" : ""}`;
const notImplementedError = ` = throw NotImplementedError("${node.name.value}.${fieldNode.name.value} must be implemented.")`;
const defaultFunctionValue = `${typeMetadata.isNullable ? "?" : ""}${notImplementedError}`;
const defaultValue = shouldGenerateFunctions
? defaultFunctionValue
: typeMetadata.defaultValue;
Expand All @@ -67,7 +61,7 @@ export function buildFieldDefinition(
);
const isCompletableFuture =
typeInResolverInterfacesConfig?.classMethods === "COMPLETABLE_FUTURE";
const completableFutureDefinition = `java.util.concurrent.CompletableFuture<${typeMetadata.typeName}${typeMetadata.isNullable ? "?" : ""}>`;
const completableFutureDefinition = `java.util.concurrent.CompletableFuture<${typeMetadata.typeName}${typeMetadata.isNullable ? "?" : ""}>${notImplementedError}`;
const field = indent(
`${fieldDefinition}: ${isCompletableFuture ? completableFutureDefinition : defaultDefinition}`,
2,
Expand All @@ -80,7 +74,6 @@ function buildFieldModifier(
fieldNode: FieldDefinitionNode,
schema: GraphQLSchema,
config: CodegenConfigWithDefaults,
abstractModifier: string,
) {
const typeInResolverInterfacesConfig = findTypeInResolverInterfacesConfig(
node,
Expand All @@ -91,9 +84,8 @@ function buildFieldModifier(
fieldNode,
schema,
);
const overrideModifier = shouldOverrideField ? "override " : "";
if (!typeInResolverInterfacesConfig && !fieldNode.arguments?.length) {
return `${overrideModifier}val`;
return shouldOverrideField ? "override val" : "val";
}
const functionModifier =
typeInResolverInterfacesConfig?.classMethods === "SUSPEND"
Expand All @@ -102,8 +94,12 @@ function buildFieldModifier(
if (node.kind === Kind.INTERFACE_TYPE_DEFINITION) {
return `${functionModifier}fun`;
}

return `${abstractModifier}${overrideModifier}${functionModifier}fun`;
const isCompletableFuture =
typeInResolverInterfacesConfig?.classMethods === "COMPLETABLE_FUTURE";
if (shouldOverrideField && !isCompletableFuture) {
return "override fun";
}
return `open ${functionModifier}fun`;
}

function buildFieldArguments(
Expand Down
2 changes: 1 addition & 1 deletion test/integration/Query.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import com.expediagroup.graphql.server.operations.Query
import graphql.schema.DataFetchingEnvironment
import test.integration.Query as QueryInterface

class IntegrationTestQuery : Query, QueryInterface {
class IntegrationTestQuery() : Query, QueryInterface() {
override fun testQuery(dataFetchingEnvironment: DataFetchingEnvironment): SomeType = SomeType()
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ data class MyTypeToConsolidateInputParent(
)

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface MyTypeToConsolidateParent2 {
fun field(input: MyTypeToConsolidate, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
open class MyTypeToConsolidateParent2 {
open fun field(input: MyTypeToConsolidate, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyTypeToConsolidateParent2.field must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ package com.kotlin.generated
import com.expediagroup.graphql.generator.annotations.*

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface TypeWithOnlyFieldArgs {
fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
open class TypeWithOnlyFieldArgs {
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("TypeWithOnlyFieldArgs.nullableResolver must be implemented.")
open fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("TypeWithOnlyFieldArgs.nonNullableResolver must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
abstract class HybridType(
open class HybridType(
val nullableField: String? = null,
val nonNullableField: String
) {
abstract fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
abstract fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("HybridType.nullableResolver must be implemented.")
open fun nonNullableResolver(arg: InputTypeForResolver, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("HybridType.nonNullableResolver must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.INPUT_OBJECT])
Expand All @@ -30,14 +30,14 @@ interface HybridInterface {
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
abstract class TypeImplementingInterface(
open class TypeImplementingInterface(
override val field1: String? = null,
override val field2: String,
val booleanField1: Boolean? = null,
val booleanField2: Boolean = false,
val integerField1: Int? = null,
val integerField2: Int
) : HybridInterface {
abstract override fun nullableListResolver(arg1: Int?, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>?
abstract override fun nonNullableListResolver(arg1: Int, arg2: Int?, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String>
override fun nullableListResolver(arg1: Int?, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = throw NotImplementedError("TypeImplementingInterface.nullableListResolver must be implemented.")
override fun nonNullableListResolver(arg1: Int, arg2: Int?, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = throw NotImplementedError("TypeImplementingInterface.nonNullableListResolver must be implemented.")
}
48 changes: 24 additions & 24 deletions test/unit/should_honor_resolverInterfaces_config/expected.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,39 @@ package com.kotlin.generated
import com.expediagroup.graphql.generator.annotations.*

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface MyIncludedResolverType {
fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>?
fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String>
open class MyIncludedResolverType {
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverType.nullableField must be implemented.")
open fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MyIncludedResolverType.nonNullableField must be implemented.")
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverType.nullableResolver must be implemented.")
open fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MyIncludedResolverType.nonNullableResolver must be implemented.")
open fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = throw NotImplementedError("MyIncludedResolverType.nullableListResolver must be implemented.")
open fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = throw NotImplementedError("MyIncludedResolverType.nonNullableListResolver must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface MyIncludedResolverTypeWithNoFieldArgs {
fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
open class MyIncludedResolverTypeWithNoFieldArgs {
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MyIncludedResolverTypeWithNoFieldArgs.nullableField must be implemented.")
open fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MyIncludedResolverTypeWithNoFieldArgs.nonNullableField must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface MySuspendResolverType {
suspend fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
suspend fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
suspend fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String?
suspend fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String
suspend fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>?
suspend fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String>
open class MySuspendResolverType {
open suspend fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MySuspendResolverType.nullableField must be implemented.")
open suspend fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MySuspendResolverType.nonNullableField must be implemented.")
open suspend fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String? = throw NotImplementedError("MySuspendResolverType.nullableResolver must be implemented.")
open suspend fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("MySuspendResolverType.nonNullableResolver must be implemented.")
open suspend fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String?>? = throw NotImplementedError("MySuspendResolverType.nullableListResolver must be implemented.")
open suspend fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): List<String> = throw NotImplementedError("MySuspendResolverType.nonNullableListResolver must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
interface MyCompletableFutureResolverType {
fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String?>
fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String>
fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String?>
fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String>
fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<List<String?>?>
fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<List<String>>
open class MyCompletableFutureResolverType {
open fun nullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String?> = throw NotImplementedError("MyCompletableFutureResolverType.nullableField must be implemented.")
open fun nonNullableField(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String> = throw NotImplementedError("MyCompletableFutureResolverType.nonNullableField must be implemented.")
open fun nullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String?> = throw NotImplementedError("MyCompletableFutureResolverType.nullableResolver must be implemented.")
open fun nonNullableResolver(arg: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<String> = throw NotImplementedError("MyCompletableFutureResolverType.nonNullableResolver must be implemented.")
open fun nullableListResolver(arg1: Int? = null, arg2: Int, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<List<String?>?> = throw NotImplementedError("MyCompletableFutureResolverType.nullableListResolver must be implemented.")
open fun nonNullableListResolver(arg1: Int, arg2: Int? = null, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): java.util.concurrent.CompletableFuture<List<String>> = throw NotImplementedError("MyCompletableFutureResolverType.nonNullableListResolver must be implemented.")
}

@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT])
Expand Down
Loading