-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: improve top-level Query and Mutation type handling (#72)
* unit test * refactor * pass test * add mutation tests * fix integration test * refactor * reorganize * separate field definition functions * pull out boolean arg * pull out other boolean arg * refactor * refactor * bun version * set up docs workspace * docs
- Loading branch information
1 parent
ff1811a
commit 884884b
Showing
10 changed files
with
219 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
--- | ||
sidebar_position: 6 | ||
--- | ||
|
||
# Inheritance | ||
|
||
When dealing with GraphQL schema containing [field arguments](https://graphql.com/learn/arguments/), | ||
generating Kotlin code can be a bit tricky. This is because the generated code cannot in itself be the implementation | ||
in a resolver. | ||
|
||
Here's an example: | ||
|
||
```graphql | ||
type Query { | ||
resolveMyType: MyType! | ||
} | ||
|
||
type MyType { | ||
resolveMe(input: String!): String! | ||
} | ||
``` | ||
|
||
Generated Kotlin: | ||
|
||
```kotlin | ||
package com.types.generated | ||
|
||
open class Query { | ||
open fun resolveMyType(): MyType = throw NotImplementedError("Query.resolveMyType must be implemented.") | ||
} | ||
|
||
open class MyType { | ||
open fun resolveMe(input: String): String = throw NotImplementedError("MyType.resolveMe must be implemented.") | ||
} | ||
``` | ||
|
||
Source code: | ||
|
||
```kotlin | ||
import com.expediagroup.graphql.server.operations.Query | ||
import com.types.generated.MyType as MyTypeInterface | ||
import com.types.generated.Query as QueryInterface | ||
|
||
class MyQuery : Query, QueryInterface() { | ||
override fun resolveMyType(): MyTypeInterface = MyType() | ||
} | ||
|
||
class MyType : MyTypeInterface() { | ||
override fun resolveMe(input: String): String = "Hello world!" | ||
} | ||
``` | ||
|
||
As you can see, the generated code is not part of the implementation. Rather, it becomes an interface to inherit from in your implementation. | ||
This enforces a type contract between the schema and your resolver code. | ||
|
||
Note that GraphQL Kotlin will use the implementation classes to generate the schema, not the generated interfaces. | ||
This means that all `@GraphQLDescription` and `@Deprecated` annotations have to be added to implementation classes | ||
in order to be propagated to the resulting schema. | ||
|
||
## Top Level Types | ||
|
||
When dealing with top-level types, i.e. `Query` and `Mutation`, you can inherit from the corresponding generated class | ||
to enforce the type contract. This is fine as long as all of your resolvers are contained in the same Query or Mutation class. | ||
|
||
```graphql | ||
type Query { | ||
foo: String! | ||
bar: String! | ||
} | ||
``` | ||
|
||
```kotlin | ||
import com.expediagroup.graphql.server.operations.Query | ||
import com.types.generated.Query as QueryInterface | ||
|
||
class MyQuery : Query, QueryInterface() { | ||
override fun foo(): String = "Hello" | ||
override fun bar(): String = "World" | ||
} | ||
``` | ||
|
||
However, you might want to separate the implementation into multiple classes like so: | ||
|
||
```kotlin | ||
import com.expediagroup.graphql.server.operations.Query | ||
|
||
class FooQuery : Query { | ||
override fun foo(): String = "Hello" | ||
} | ||
|
||
class BarQuery : Query { | ||
override fun bar(): String = "World" | ||
} | ||
``` | ||
|
||
If you try to inherit from the generated `Query` class, you will get an error during schema generation. | ||
|
||
```kotlin | ||
import com.expediagroup.graphql.server.operations.Query | ||
import com.types.generated.Query as QueryInterface | ||
|
||
class FooQuery : Query, QueryInterface() { | ||
override fun foo(): String = "Hello" | ||
} | ||
|
||
class BarQuery : Query, QueryInterface() { | ||
override fun bar(): String = "World" | ||
} | ||
``` | ||
|
||
This is because the generated `Query` class contains both `foo` and `bar` fields, which creates a conflict when inherited by multiple implementation classes. | ||
|
||
Instead, you should inherit from the field-level generated `Query` classes like so: | ||
|
||
```kotlin | ||
import com.expediagroup.graphql.server.operations.Query | ||
import com.types.generated.FooQuery as FooQueryInterface | ||
import com.types.generated.BarQuery as BarQueryInterface | ||
|
||
class FooQuery : Query, FooQueryInterface() { | ||
override fun foo(): String = "Hello" | ||
} | ||
|
||
class BarQuery : Query, BarQueryInterface() { | ||
override fun bar(): String = "World" | ||
} | ||
``` | ||
|
||
This way, schema generation can complete without conflict, and you can separate your implementation into multiple classes! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
5 changes: 5 additions & 0 deletions
5
test/unit/should_handle_top_level_types_properly/codegen.config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
import { GraphQLKotlinCodegenConfig } from "../../../src/plugin"; | ||
|
||
export default { | ||
resolverInterfaces: [{ typeName: "Mutation", classMethods: "SUSPEND" }], | ||
} satisfies GraphQLKotlinCodegenConfig; |
35 changes: 35 additions & 0 deletions
35
test/unit/should_handle_top_level_types_properly/expected.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package com.kotlin.generated | ||
|
||
import com.expediagroup.graphql.generator.annotations.* | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class Query { | ||
open fun getStuff(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Query.getStuff must be implemented.") | ||
open fun getStuffWithInput(input: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Query.getStuffWithInput must be implemented.") | ||
} | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class GetStuffQuery { | ||
open fun getStuff(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Query.getStuff must be implemented.") | ||
} | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class GetStuffWithInputQuery { | ||
open fun getStuffWithInput(input: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Query.getStuffWithInput must be implemented.") | ||
} | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class Mutation { | ||
open suspend fun mutateStuff(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Mutation.mutateStuff must be implemented.") | ||
open suspend fun mutateStuffWithInput(input: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Mutation.mutateStuffWithInput must be implemented.") | ||
} | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class MutateStuffMutation { | ||
open suspend fun mutateStuff(dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Mutation.mutateStuff must be implemented.") | ||
} | ||
|
||
@GraphQLValidObjectLocations(locations = [GraphQLValidObjectLocations.Locations.OBJECT]) | ||
open class MutateStuffWithInputMutation { | ||
open suspend fun mutateStuffWithInput(input: String, dataFetchingEnvironment: graphql.schema.DataFetchingEnvironment): String = throw NotImplementedError("Mutation.mutateStuffWithInput must be implemented.") | ||
} |
9 changes: 9 additions & 0 deletions
9
test/unit/should_handle_top_level_types_properly/schema.graphql
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
type Query { | ||
getStuff: String! | ||
getStuffWithInput(input: String!): String! | ||
} | ||
|
||
type Mutation { | ||
mutateStuff: String! | ||
mutateStuffWithInput(input: String!): String! | ||
} |