From 764cc43c5a5ebc15080bdd08a5656dfed7217fa8 Mon Sep 17 00:00:00 2001 From: Dariusz Kuc Date: Mon, 21 Oct 2019 10:24:39 -0500 Subject: [PATCH] [docs] updating docs to be more discoverable --- .../directives.md | 0 .../customizing-schemas/documenting-fields.md | 43 ++ docs/customizing-schemas/evolving-schema.md | 42 ++ docs/customizing-schemas/excluding-fields.md | 34 ++ .../generator-config.md | 134 +++--- docs/customizing-schemas/renaming-fields.md | 19 + docs/execution/contextual-data.md | 48 +++ ...{directives.md => federated-directives.md} | 380 +++++++++--------- docs/federated/federated-schemas.md | 330 +++++++-------- docs/writing-schemas/annotations.md | 82 +--- docs/writing-schemas/basics.md | 126 +++--- docs/writing-schemas/fields.md | 134 ------ docs/writing-schemas/scalars.md | 166 ++++---- website/sidebars.json | 20 +- 14 files changed, 784 insertions(+), 774 deletions(-) rename docs/{writing-schemas => customizing-schemas}/directives.md (100%) create mode 100644 docs/customizing-schemas/documenting-fields.md create mode 100644 docs/customizing-schemas/evolving-schema.md create mode 100644 docs/customizing-schemas/excluding-fields.md rename docs/{writing-schemas => customizing-schemas}/generator-config.md (96%) create mode 100644 docs/customizing-schemas/renaming-fields.md create mode 100644 docs/execution/contextual-data.md rename docs/federated/{directives.md => federated-directives.md} (96%) delete mode 100644 docs/writing-schemas/fields.md diff --git a/docs/writing-schemas/directives.md b/docs/customizing-schemas/directives.md similarity index 100% rename from docs/writing-schemas/directives.md rename to docs/customizing-schemas/directives.md diff --git a/docs/customizing-schemas/documenting-fields.md b/docs/customizing-schemas/documenting-fields.md new file mode 100644 index 0000000000..43f0ee96ba --- /dev/null +++ b/docs/customizing-schemas/documenting-fields.md @@ -0,0 +1,43 @@ +--- +id: documenting-fields +title: Documenting Schema +--- + +Since Javadocs are not available at runtime for introspection, `graphql-kotlin-schema-generator` includes an annotation +class `@GraphQLDescription` that can be used to add schema descriptions to *any* GraphQL schema element: + +```kotlin +@GraphQLDescription("A useful widget") +data class Widget( + @GraphQLDescription("The widget's value that can be null") + val value: Int? +) + +class WidgetQuery: Query { + + @GraphQLDescription("creates new widget for given ID") + fun widgetById(@GraphQLDescription("The special ingredient") id: Int): Widget? = Widget(id) +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +schema { + query: Query +} + +type Query { + """creates new widget for given ID""" + widgetById( + """The special ingredient""" + id: Int! + ): Widget +} + +"""A useful widget""" +type Widget { + """The widget's value that can be null""" + value: Int +} +``` diff --git a/docs/customizing-schemas/evolving-schema.md b/docs/customizing-schemas/evolving-schema.md new file mode 100644 index 0000000000..66b14b6a10 --- /dev/null +++ b/docs/customizing-schemas/evolving-schema.md @@ -0,0 +1,42 @@ +--- +id: evolving-schema +title: Evolving Schema +--- + +### Deprecating Fields + +GraphQL schemas can have fields marked as deprecated. Instead of creating a custom annotation, +`graphql-kotlin-schema-generator` just looks for the `kotlin.Deprecated` annotation and will use that annotation message +for the deprecated reason. + +```kotlin +class SimpleQuery { + @Deprecated(message = "this query is deprecated", replaceWith = ReplaceWith("shinyNewQuery")) + @GraphQLDescription("old query that should not be used always returns false") + fun simpleDeprecatedQuery(): Boolean = false + + @GraphQLDescription("new query that always returns true") + fun shinyNewQuery(): Boolean = true +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +schema { + query: Query +} + +type Query { + + """old query that should not be used always returns false""" + simpleDeprecatedQuery: Boolean! @deprecated(reason: "this query is deprecated, replace with shinyNewQuery") + + """new query that always returns true""" + shinyNewQuery: Boolean! +} +``` + +While you can deprecate any fields/functions/classes in your Kotlin code, GraphQL only supports deprecation directive on +the fields (which correspond to Kotlin fields and functions) and enum values. + diff --git a/docs/customizing-schemas/excluding-fields.md b/docs/customizing-schemas/excluding-fields.md new file mode 100644 index 0000000000..0e292cce43 --- /dev/null +++ b/docs/customizing-schemas/excluding-fields.md @@ -0,0 +1,34 @@ +--- +id: excluding-fields +title: Excluding Fields +--- + +There are two ways to ensure the GraphQL schema generation omits fields when using Kotlin reflection: + +* The first is by marking the field as non-`public` scope (`private`, `protected`, `internal`) +* The second method is by annotating the field with `@GraphQLIgnore`. + +```kotlin +class SimpleQuery { + @GraphQLIgnore + fun notPartOfSchema() = "ignore me!" + + private fun privateFunctionsAreNotVisible() = "ignored private function" + + fun doSomething(value: Int): Boolean = true +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +schema { + query: Query +} + +type Query { + doSomething(value: Int!): Boolean! +} +``` + +Note that the public method `notPartOfSchema` is not included in the schema. diff --git a/docs/writing-schemas/generator-config.md b/docs/customizing-schemas/generator-config.md similarity index 96% rename from docs/writing-schemas/generator-config.md rename to docs/customizing-schemas/generator-config.md index bcc3357efa..3f7bb73b18 100644 --- a/docs/writing-schemas/generator-config.md +++ b/docs/customizing-schemas/generator-config.md @@ -1,67 +1,67 @@ ---- -id: generator-config -title: Schema Generator Configuration ---- - -`graphql-kotlin-schema-generator` provides a single function, `toSchema,` to generate a schema from Kotlin objects. This -function accepts four arguments: config, queries, mutations and subscriptions. The queries, mutations and subscriptions -are a list of -[TopLevelObjects](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/TopLevelObject.kt) -and will be used to generate corresponding GraphQL root types. The -[config](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/SchemaGeneratorConfig.kt) -contains all the extra information you need to pass, including custom hooks, supported packages and name overrides. -`SchemaGeneratorConfig` has some default settings but you can override them and add custom behaviors for generating your -schema. - -* `supportedPackages` **[Required]** - List of Kotlin packages that can contain schema objects. Limits the scope of - packages that can be scanned using reflections. -* `topLevelNames` _[Optional]_ - Set the name of the top level GraphQL fields, defaults to `Query`, `Mutation` and - `Subscription` -* `hooks` _[Optional]_ - Set custom behaviors for generating the schema, see below for details. -* `dataFetcherFactory` _[Optional]_ - Sets custom behavior for generating data fetchers - -## Schema generator hooks - -Hooks are lifecycle events that are called and triggered while the schema is building that allow users to customize the -schema. - -For exact names and details of every hook, see the comments and descriptions in our latest -[javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-schema-generator) or directly in the source file: -[SchemaGeneratorHooks.kt](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/hooks/SchemaGeneratorHooks.kt) - -As an example here is how you would write a custom hook and provide it through the configuration - -```kotlin -class MyCustomHooks : SchemaGeneratorHooks { - // Only generate functions that start with "dog" - // This would probably be better just to use @GraphQLIgnore, but this is just an example - override fun isValidFunction(function: KFunction<*>) = function.name.startsWith("dog") -} - -class Query { - fun dogSound() = "bark" - - fun catSound() = "meow" -} - -val config = SchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = MyCustomHooks()) - -val queries = listOf(TopLevelObject(Query())) - -toSchema(queries = queries, config = config) -``` - -will generate - -```graphql - -schema { - query: Query -} - -type Query { - dogSound: String! -} -``` - -Notice there is no `catSound` function. +--- +id: generator-config +title: Generator Configuration +--- + +`graphql-kotlin-schema-generator` provides a single function, `toSchema,` to generate a schema from Kotlin objects. This +function accepts four arguments: config, queries, mutations and subscriptions. The queries, mutations and subscriptions +are a list of +[TopLevelObjects](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/TopLevelObject.kt) +and will be used to generate corresponding GraphQL root types. The +[config](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/SchemaGeneratorConfig.kt) +contains all the extra information you need to pass, including custom hooks, supported packages and name overrides. +`SchemaGeneratorConfig` has some default settings but you can override them and add custom behaviors for generating your +schema. + +* `supportedPackages` **[Required]** - List of Kotlin packages that can contain schema objects. Limits the scope of + packages that can be scanned using reflections. +* `topLevelNames` _[Optional]_ - Set the name of the top level GraphQL fields, defaults to `Query`, `Mutation` and + `Subscription` +* `hooks` _[Optional]_ - Set custom behaviors for generating the schema, see below for details. +* `dataFetcherFactory` _[Optional]_ - Sets custom behavior for generating data fetchers + +## Schema generator hooks + +Hooks are lifecycle events that are called and triggered while the schema is building that allow users to customize the +schema. + +For exact names and details of every hook, see the comments and descriptions in our latest +[javadocs](https://www.javadoc.io/doc/com.expediagroup/graphql-kotlin-schema-generator) or directly in the source file: +[SchemaGeneratorHooks.kt](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/hooks/SchemaGeneratorHooks.kt) + +As an example here is how you would write a custom hook and provide it through the configuration + +```kotlin +class MyCustomHooks : SchemaGeneratorHooks { + // Only generate functions that start with "dog" + // This would probably be better just to use @GraphQLIgnore, but this is just an example + override fun isValidFunction(function: KFunction<*>) = function.name.startsWith("dog") +} + +class Query { + fun dogSound() = "bark" + + fun catSound() = "meow" +} + +val config = SchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = MyCustomHooks()) + +val queries = listOf(TopLevelObject(Query())) + +toSchema(queries = queries, config = config) +``` + +will generate + +```graphql + +schema { + query: Query +} + +type Query { + dogSound: String! +} +``` + +Notice there is no `catSound` function. diff --git a/docs/customizing-schemas/renaming-fields.md b/docs/customizing-schemas/renaming-fields.md new file mode 100644 index 0000000000..d7e1f83846 --- /dev/null +++ b/docs/customizing-schemas/renaming-fields.md @@ -0,0 +1,19 @@ +--- +id: renaming-fields +title: Renaming Fields +--- + +By default, schema generator will use simple name of the underlying class for the type names and function/property names for field names. +You can change this default behavior by annotating target class/field with `@GraphQLName` annotation. The following Kotlin `Widget` class +will be renamed to `MyCustomName` GraphQL type. + +```kotlin +@GraphQLName("MyCustomName") +data class Widget(val value: Int?) +``` + +```graphql +type MyCustomName { + value: Int +} +``` diff --git a/docs/execution/contextual-data.md b/docs/execution/contextual-data.md new file mode 100644 index 0000000000..c00c4989e9 --- /dev/null +++ b/docs/execution/contextual-data.md @@ -0,0 +1,48 @@ +--- +id: contextual-data +title: Contextual Data +--- + +All GraphQL servers have a concept of a "context". A GraphQL context contains metadata that is useful to the GraphQL +server, but shouldn't necessarily be part of the GraphQL query's API. A prime example of something that is appropriate +for the GraphQL context would be trace headers for an OpenTracing system such as +[Haystack](https://expediadotcom.github.io/haystack). The GraphQL query itself does not need the information to perform +its function, but the server itself needs the information to ensure observability. + +The contents of the GraphQL context vary across applications and it is up to the GraphQL server developers to decide +what it should contain. For Spring based applications, `graphql-kotlin-spring-server` provides a simple mechanism to +build context per query execution through +[GraphQLContextFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/GraphQLContextFactory.kt). +Once context factory bean is available in the Spring application context it will then be used in a corresponding +[ContextWebFilter](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/ContextWebFilter.kt) +to populate GraphQL context based on the incoming request and make it available during query execution. + +Once your application is configured to build your custom `MyGraphQLContext`, simply add `@GraphQLContext` annotation to +any function argument and the corresponding GraphQL context from the environment will be automatically injected during +execution. + +```kotlin +class ContextualQuery { + + fun contextualQuery( + value: Int, + @GraphQLContext context: MyGraphQLContext + ): ContextualResponse = ContextualResponse(value, context.myCustomValue) +} +``` + +The above query would produce the following GraphQL schema: + +```graphql +schema { + query: Query +} + +type Query { + contextualQuery( + value: Int! + ): ContextualResponse! +} +``` + +Note that the `@GraphQLContext` annotated argument is not reflected in the GraphQL schema. diff --git a/docs/federated/directives.md b/docs/federated/federated-directives.md similarity index 96% rename from docs/federated/directives.md rename to docs/federated/federated-directives.md index 87899bc993..8675aacc06 100644 --- a/docs/federated/directives.md +++ b/docs/federated/federated-directives.md @@ -1,190 +1,190 @@ ---- -id: federated-directives -title: Directives ---- -`graphql-kotlin` supports a number of directives that can be used to annotate a schema and direct certain behaviors. - -## `@extends` directive - -```graphql -directive @extends on OBJECT | INTERFACE -``` - -`@extends` directive is used to represent type extensions in the schema. Native type extensions are currently -unsupported by the `graphql-kotlin` libraries. Federated extended types should have corresponding `@key` directive -defined that specifies primary key required to fetch the underlying object. - -Example - -```kotlin -@KeyDirective(FieldSet("id")) -@ExtendsDirective -class Product(@property:ExternalDirective val id: String) { - fun newFunctionality(): String = "whatever" -} -``` - -will generate - -```graphql -type Product @extends @key(fields : "id") { - id: String! @external - newFunctionality: String! -} -``` - -## `@external` directive - -```graphql -directive @external on FIELD_DEFINITION -``` - -The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from -service B while also knowing at runtime the types of that field. `@external` directive is only applicable on federated -extended types. All the external fields should either be referenced from the `@key`, `@requires` or `@provides` -directives field sets. - -Example - -```kotlin -@KeyDirective(FieldSet("id")) -@ExtendsDirective -class Product(@property:ExternalDirective val id: String) { - fun newFunctionality(): String = "whatever" -} -``` - -will generate - -```graphql -type Product @extends @key(fields : "id") { - id: String! @external - newFunctionality: String! -} -``` - -## `@key` directive - -```graphql -directive @key(fields: _FieldSet!) on OBJECT | INTERFACE -``` - -The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an -object or interface. Specified field set can represent single field (e.g. `"id"`), multiple fields (e.g. `"id name"`) or -nested selection sets (e.g. `"id user { name }"`). - -Key directive should be specified on the root base type as well as all the corresponding federated (i.e. extended) -types. Key fields specified in the directive field set should correspond to a valid field on the underlying GraphQL -interface/object. Federated extended types should also instrument all the referenced key fields with `@external` -directive. - -> NOTE: federation spec specifies that multiple @key directives can be applied on the field which is at odds with -> graphql-spec and currently unsupported by graphql-kotlin. - -Example - -```kotlin -@KeyDirective(FieldSet("id")) -class Product(val id: String, val name: String) -``` - -will generate - -```graphql -type Product @key(fields: "id") { - id: String! - name: String! -} -``` - -## `@provides` directive - -```graphql -directive @provides(fields: _FieldSet!) on FIELD_DEFINITION -``` - -The `@provides` directive is used to annotate the expected returned field set from a field on a base type that is -guaranteed to be selectable by the gateway. This allows you to expose only a subset of fields from the underlying -federated object type to be selectable from the federated schema. Provided fields specified in the directive field set -should correspond to a valid field on the underlying GraphQL interface/object type. `@provides` directive can only be -used on fields returning federated extended objects. - -Example: -We might want to expose only name of the user that submitted a review. - -```kotlin -@KeyDirective(FieldSet("id")) -class Review(val id: String) { - @ProvidesDirective(FieldSet("name")) - fun user(): User = // implementation goes here -} - -@KeyDirective(FieldSet("userId")) -@ExtendsDirective -class User( - @property:ExternalDirective val userId: String, - @property:ExternalDirective val name: String -) -``` - -will generate - -```graphql -type Review @key(fields : "id") { - id: String! - user: User! @provides(fields : "name") -} - -type User @extends @key(fields : "userId") { - userId: String! @external - name: String! @external -} -``` - -## `@requires` directive - -```graphql -directive @requires(fields: _FieldSet!) on FIELD_DEFINITON -``` - -The `@requires` directive is used to annotate the required input field set from a base type for a resolver. It is used -to develop a query plan where the required fields may not be needed by the client, but the service may need additional -information from other services. Required fields specified in the directive field set should correspond to a valid field -on the underlying GraphQL interface/object and should be instrumented with `@external` directive. Since `@requires` -directive specifies additional fields (besides the one specified in `@key` directive) that are required to resolve -federated type fields, this directive can only be specified on federated extended objects fields. - -NOTE: fields specified in the `@requires` directive will only be specified in the queries that reference those fields. -This is problematic for Kotlin as the non nullable primitive properties have to be initialized when they are declared. -Simplest workaround for this problem is to initialize the underlying property to some dummy value that will be used if -it is not specified. This approach might become problematic though as it might be impossible to determine whether fields -was initialized with the default value or the invalid/default value was provided by the federated query. Another -potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure -that exception will be thrown if queries attempt to resolve fields that reference the uninitialized property. - -Example: - -```kotlin -@KeyDirective(FieldSet("id")) -@ExtendsDirective -class Product(@property:ExternalDirective val id: String) { - @ExternalDirective - var weight: Double by Delegates.notNull() - - @RequiresDirective(FieldSet("weight")) - fun shippingCost(): String { ... } - - fun additionalInfo(): String { ... } -} -``` - -will generate - -```graphql -type Product @extends @key(fields : "id") { - additionalInfo: String! - id: String! @external - shippingCost: String! @requires(fields : "weight") - weight: Float! @external -} -``` +--- +id: federated-directives +title: Federated Directives +--- +`graphql-kotlin` supports a number of directives that can be used to annotate a schema and direct certain behaviors. + +## `@extends` directive + +```graphql +directive @extends on OBJECT | INTERFACE +``` + +`@extends` directive is used to represent type extensions in the schema. Native type extensions are currently +unsupported by the `graphql-kotlin` libraries. Federated extended types should have corresponding `@key` directive +defined that specifies primary key required to fetch the underlying object. + +Example + +```kotlin +@KeyDirective(FieldSet("id")) +@ExtendsDirective +class Product(@property:ExternalDirective val id: String) { + fun newFunctionality(): String = "whatever" +} +``` + +will generate + +```graphql +type Product @extends @key(fields : "id") { + id: String! @external + newFunctionality: String! +} +``` + +## `@external` directive + +```graphql +directive @external on FIELD_DEFINITION +``` + +The `@external` directive is used to mark a field as owned by another service. This allows service A to use fields from +service B while also knowing at runtime the types of that field. `@external` directive is only applicable on federated +extended types. All the external fields should either be referenced from the `@key`, `@requires` or `@provides` +directives field sets. + +Example + +```kotlin +@KeyDirective(FieldSet("id")) +@ExtendsDirective +class Product(@property:ExternalDirective val id: String) { + fun newFunctionality(): String = "whatever" +} +``` + +will generate + +```graphql +type Product @extends @key(fields : "id") { + id: String! @external + newFunctionality: String! +} +``` + +## `@key` directive + +```graphql +directive @key(fields: _FieldSet!) on OBJECT | INTERFACE +``` + +The `@key` directive is used to indicate a combination of fields that can be used to uniquely identify and fetch an +object or interface. Specified field set can represent single field (e.g. `"id"`), multiple fields (e.g. `"id name"`) or +nested selection sets (e.g. `"id user { name }"`). + +Key directive should be specified on the root base type as well as all the corresponding federated (i.e. extended) +types. Key fields specified in the directive field set should correspond to a valid field on the underlying GraphQL +interface/object. Federated extended types should also instrument all the referenced key fields with `@external` +directive. + +> NOTE: federation spec specifies that multiple @key directives can be applied on the field which is at odds with +> graphql-spec and currently unsupported by graphql-kotlin. + +Example + +```kotlin +@KeyDirective(FieldSet("id")) +class Product(val id: String, val name: String) +``` + +will generate + +```graphql +type Product @key(fields: "id") { + id: String! + name: String! +} +``` + +## `@provides` directive + +```graphql +directive @provides(fields: _FieldSet!) on FIELD_DEFINITION +``` + +The `@provides` directive is used to annotate the expected returned field set from a field on a base type that is +guaranteed to be selectable by the gateway. This allows you to expose only a subset of fields from the underlying +federated object type to be selectable from the federated schema. Provided fields specified in the directive field set +should correspond to a valid field on the underlying GraphQL interface/object type. `@provides` directive can only be +used on fields returning federated extended objects. + +Example: +We might want to expose only name of the user that submitted a review. + +```kotlin +@KeyDirective(FieldSet("id")) +class Review(val id: String) { + @ProvidesDirective(FieldSet("name")) + fun user(): User = // implementation goes here +} + +@KeyDirective(FieldSet("userId")) +@ExtendsDirective +class User( + @property:ExternalDirective val userId: String, + @property:ExternalDirective val name: String +) +``` + +will generate + +```graphql +type Review @key(fields : "id") { + id: String! + user: User! @provides(fields : "name") +} + +type User @extends @key(fields : "userId") { + userId: String! @external + name: String! @external +} +``` + +## `@requires` directive + +```graphql +directive @requires(fields: _FieldSet!) on FIELD_DEFINITON +``` + +The `@requires` directive is used to annotate the required input field set from a base type for a resolver. It is used +to develop a query plan where the required fields may not be needed by the client, but the service may need additional +information from other services. Required fields specified in the directive field set should correspond to a valid field +on the underlying GraphQL interface/object and should be instrumented with `@external` directive. Since `@requires` +directive specifies additional fields (besides the one specified in `@key` directive) that are required to resolve +federated type fields, this directive can only be specified on federated extended objects fields. + +NOTE: fields specified in the `@requires` directive will only be specified in the queries that reference those fields. +This is problematic for Kotlin as the non nullable primitive properties have to be initialized when they are declared. +Simplest workaround for this problem is to initialize the underlying property to some dummy value that will be used if +it is not specified. This approach might become problematic though as it might be impossible to determine whether fields +was initialized with the default value or the invalid/default value was provided by the federated query. Another +potential workaround is to rely on delegation to initialize the property after the object gets created. This will ensure +that exception will be thrown if queries attempt to resolve fields that reference the uninitialized property. + +Example: + +```kotlin +@KeyDirective(FieldSet("id")) +@ExtendsDirective +class Product(@property:ExternalDirective val id: String) { + @ExternalDirective + var weight: Double by Delegates.notNull() + + @RequiresDirective(FieldSet("weight")) + fun shippingCost(): String { ... } + + fun additionalInfo(): String { ... } +} +``` + +will generate + +```graphql +type Product @extends @key(fields : "id") { + additionalInfo: String! + id: String! @external + shippingCost: String! @requires(fields : "weight") + weight: Float! @external +} +``` diff --git a/docs/federated/federated-schemas.md b/docs/federated/federated-schemas.md index 6dfc99e950..8f4ffb8656 100644 --- a/docs/federated/federated-schemas.md +++ b/docs/federated/federated-schemas.md @@ -1,165 +1,165 @@ ---- -id: federated-schemas -title: Federated Schemas ---- -In many cases, exposing single GraphQL API that exposes unified view of all the available data provides tremendous value -to their clients. As the underlying graph scales, managing single monolithic GraphQL server might become less and less -feasible making it much harder to manage and leading to unnecessary bottlenecks. Migrating towards federated model with -an API gateway and a number of smaller GraphQL services behind it alleviates some of those problems and allows teams to -scale their graphs more easily. - -## Apollo Federation - -[Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) is an architecture for -composing multiple GraphQL services into a single graph. Each individual GraphQL server generates valid GraphQL schema -and can be developed and run independently. - -`graphql-kotlin-federation` extends the functionality of `graphql-kotlin-schema-generator` and allows you to easily -generate federated GraphQL schemas directly from the code. Federated schemas rely on a number of directives to -instrument the behavior of the underlying graph. See the [Directives](federated-directives) documentation here to learn -more about new directives. Once all the federated objects are annotated, you will also have to configure corresponding -[FederatedTypeResolver]s that are used to instantiate federated objects and finally generate the schema using -`toFederatedSchema` function -([link](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/federation/toFederatedSchema.kt#L34)). - -**In order to generate valid federated schemas, you will need to annotate both your base schema and the one extending -it**. Federated Gateway (e.g. Apollo) will then combine the individual graphs to form single federated graph. - -> NOTE: If you are using custom `Query` type then all of you federated GraphQL services have to use the same type. It is -> not possible for federated services to have multiple definitions of `Query` type. - -### Base Schema - -Base schema defines GraphQL types that will be extended by schemas exposed by other GraphQL services. In the example -below, we define base `Product` type with `id` and `description` fields. `id` is the primary key that uniquely -identifies the `Product` type object and is specified in `@key` directive. - -```kotlin -@KeyDirective(fields = FieldSet("id")) -data class Product(val id: Int, val description: String) - -class ProductQuery { - fun product(id: Int): Product? { - // grabs product from a data source, might return null - } -} - -// Generate the schema -val federatedTypeRegistry = FederatedTypeRegistry(emptyMap()) -val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = FederatedSchemaGeneratorHooks(federatedTypeRegistry)) -val queries = listOf(TopLevelObject(ProductQuery())) - -toFederatedSchema(config, queries) -``` - -Generates the following schema with additional federated types - -```graphql -schema { - query: Query -} - -union _Entity = Product - -type Product @key(fields : "id") { - description: Int! - id: String! -} - -type Query @extends { - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service - product(id: Int!): Product! -} - -type _Service { - sdl: String! -} -``` - -#### Extended Schema - -Extended federated GraphQL schemas provide additional functionality to the types already exposed by other GraphQL -services. In the example below, `Product` type is extended to add new `reviews` field to it. Primary key needed to -instantiate the `Product` type (i.e. `id`) has to match the `@key` definition on the base type. Since primary keys are -defined on the base type and are only referenced from the extended type, all of the fields that are part of the field -set specified in `@key` directive have to be marked as `@external`. - -```kotlin -@KeyDirective(fields = FieldSet("id")) -@ExtendsDirective -data class Product(@ExternalDirective val id: Int) { - - fun reviews(): List { - // returns list of product reviews - } -} - -data class Review(val reviewId: String, val text: String) - -// Generate the schema -val productResolver = object: FederatedTypeResolver { - override fun resolve(keys: Map): Product { - val id = keys["id"]?.toString()?.toIntOrNull() - // instantiate product using id - } -} -val federatedTypeRegistry = FederatedTypeRegistry(mapOf("Product" to productResolver)) -val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = FederatedSchemaGeneratorHooks(federatedTypeRegistry)) - -toFederatedSchema(config) -``` - -Generates the following federated schema - -```graphql -schema { - query: Query -} - -union _Entity = Product - -type Product @extends @key(fields : "id") { - id: Int! @external - reviews: [Review!]! -} - -type Query @extends { - _entities(representations: [_Any!]!): [_Entity]! - _service: _Service -} - -type Review { - reviewId: String! - text: String! -} - -type _Service { - sdl: String! -} -``` - -Federated Gateway will then combine the schemas from the individual services to generate single schema. - -#### Federated GraphQL schema - -```graphql -schema { - query: Query -} - -type Product { - description: String! - id: String! - reviews: [Review!]! -} - -type Review { - reviewId: String! - text: String! -} - -type Query { - product(id: String!): Product! -} -``` +--- +id: federated-schemas +title: Federated Schemas +--- +In many cases, exposing single GraphQL API that exposes unified view of all the available data provides tremendous value +to their clients. As the underlying graph scales, managing single monolithic GraphQL server might become less and less +feasible making it much harder to manage and leading to unnecessary bottlenecks. Migrating towards federated model with +an API gateway and a number of smaller GraphQL services behind it alleviates some of those problems and allows teams to +scale their graphs more easily. + +## Apollo Federation + +[Apollo Federation](https://www.apollographql.com/docs/apollo-server/federation/introduction/) is an architecture for +composing multiple GraphQL services into a single graph. Each individual GraphQL server generates valid GraphQL schema +and can be developed and run independently. + +`graphql-kotlin-federation` extends the functionality of `graphql-kotlin-schema-generator` and allows you to easily +generate federated GraphQL schemas directly from the code. Federated schemas rely on a number of [new directives](federated-directives) to +instrument the behavior of the underlying graph. Once all the federated objects are annotated, you will also have to configure corresponding +FederatedTypeResolvers that are used to instantiate underlying federated objects and finally generate the schema using +`toFederatedSchema` function +([link](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/federation/toFederatedSchema.kt#L34)). +See [type resolution](type-resolution) for more details on how federated types are resolved. + +**In order to generate valid federated schemas, you will need to annotate both your base schema and the one extending +it**. Federated Gateway (e.g. Apollo) will then combine the individual graphs to form single federated graph. + +> NOTE: If you are using custom `Query` type then all of you federated GraphQL services have to use the same type. It is +> not possible for federated services to have multiple definitions of `Query` type. + +### Base Schema + +Base schema defines GraphQL types that will be extended by schemas exposed by other GraphQL services. In the example +below, we define base `Product` type with `id` and `description` fields. `id` is the primary key that uniquely +identifies the `Product` type object and is specified in `@key` directive. + +```kotlin +@KeyDirective(fields = FieldSet("id")) +data class Product(val id: Int, val description: String) + +class ProductQuery { + fun product(id: Int): Product? { + // grabs product from a data source, might return null + } +} + +// Generate the schema +val federatedTypeRegistry = FederatedTypeRegistry(emptyMap()) +val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = FederatedSchemaGeneratorHooks(federatedTypeRegistry)) +val queries = listOf(TopLevelObject(ProductQuery())) + +toFederatedSchema(config, queries) +``` + +Generates the following schema with additional federated types + +```graphql +schema { + query: Query +} + +union _Entity = Product + +type Product @key(fields : "id") { + description: Int! + id: String! +} + +type Query @extends { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service + product(id: Int!): Product! +} + +type _Service { + sdl: String! +} +``` + +#### Extended Schema + +Extended federated GraphQL schemas provide additional functionality to the types already exposed by other GraphQL +services. In the example below, `Product` type is extended to add new `reviews` field to it. Primary key needed to +instantiate the `Product` type (i.e. `id`) has to match the `@key` definition on the base type. Since primary keys are +defined on the base type and are only referenced from the extended type, all of the fields that are part of the field +set specified in `@key` directive have to be marked as `@external`. + +```kotlin +@KeyDirective(fields = FieldSet("id")) +@ExtendsDirective +data class Product(@ExternalDirective val id: Int) { + + fun reviews(): List { + // returns list of product reviews + } +} + +data class Review(val reviewId: String, val text: String) + +// Generate the schema +val productResolver = object: FederatedTypeResolver { + override fun resolve(keys: Map): Product { + val id = keys["id"]?.toString()?.toIntOrNull() + // instantiate product using id + } +} +val federatedTypeRegistry = FederatedTypeRegistry(mapOf("Product" to productResolver)) +val config = FederatedSchemaGeneratorConfig(supportedPackages = listOf("org.example"), hooks = FederatedSchemaGeneratorHooks(federatedTypeRegistry)) + +toFederatedSchema(config) +``` + +Generates the following federated schema + +```graphql +schema { + query: Query +} + +union _Entity = Product + +type Product @extends @key(fields : "id") { + id: Int! @external + reviews: [Review!]! +} + +type Query @extends { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service +} + +type Review { + reviewId: String! + text: String! +} + +type _Service { + sdl: String! +} +``` + +#### Federated GraphQL schema + +Federated Gateway will then combine the schemas from the individual services to generate single schema. + +```graphql +schema { + query: Query +} + +type Product { + description: String! + id: String! + reviews: [Review!]! +} + +type Review { + reviewId: String! + text: String! +} + +type Query { + product(id: String!): Product! +} +``` diff --git a/docs/writing-schemas/annotations.md b/docs/writing-schemas/annotations.md index 460d6a049c..ee7bad9206 100644 --- a/docs/writing-schemas/annotations.md +++ b/docs/writing-schemas/annotations.md @@ -1,67 +1,15 @@ ---- -id: annotations -title: Annotations ---- - -`graphql-kotlin-schema-generator` ships with a number of annotation classes to allow you to enhance your GraphQL schema -for things that can't be directly derived from Kotlin reflection. - -* [@GraphQLContext](annotations#GraphQLContext) - Autowire `GraphQLContext` - from the environment -* [@GraphQLDescription](fields#documenting-fields) - Provide a - description for a GraphQL field -* [@GraphQLDirective](directives) - Registers directive on a GraphQL - field -* [@GraphQLID](scalars#id) - Marks given field as GraphQL `ID` -* [@GraphQLIgnore](fields#excluding-fields-from-schema) - Exclude - field from the GraphQL schema -* [@GraphQLName](fields#documenting-fields) - Override the name - used for the type -* Kotlin built in [@Deprecated](fields#deprecating-fields) - Apply - the GraphQL `@deprecated` directive on the field - -## `@GraphQLContext` - -All GraphQL servers have a concept of a "context". A GraphQL context contains metadata that is useful to the GraphQL -server, but shouldn't necessarily be part of the GraphQL query's API. A prime example of something that is appropriate -for the GraphQL context would be trace headers for an OpenTracing system such as -[Haystack](https://expediadotcom.github.io/haystack). The GraphQL query itself does not need the information to perform -its function, but the server itself needs the information to ensure observability. - -The contents of the GraphQL context vary across applications and it is up to the GraphQL server developers to decide -what it should contain. For Spring based applications, `graphql-kotlin-spring-server` provides a simple mechanism to -build context per query execution through -[GraphQLContextFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/GraphQLContextFactory.kt). -Once context factory bean is available in the Spring application context it will then be used in a corresponding -[ContextWebFilter](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-spring-server/src/main/kotlin/com/expediagroup/graphql/spring/execution/ContextWebFilter.kt) -to populate GraphQL context based on the incoming request and make it available during query execution. - -Once your application is configured to build your custom `MyGraphQLContext`, simply add `@GraphQLContext` annotation to -any function argument and the corresponding GraphQL context from the environment will be automatically injected during -execution. - -```kotlin -class ContextualQuery { - - fun contextualQuery( - value: Int, - @GraphQLContext context: MyGraphQLContext - ): ContextualResponse = ContextualResponse(value, context.myCustomValue) -} -``` - -The above query would produce the following GraphQL schema: - -```graphql -schema { - query: Query -} - -type Query { - contextualQuery( - value: Int! - ): ContextualResponse! -} -``` - -Note that the `@GraphQLContext` annotated argument is not reflected in the GraphQL schema. +--- +id: annotations +title: Annotations +--- + +`graphql-kotlin-schema-generator` ships with a number of annotation classes to allow you to enhance your GraphQL schema +for things that can't be directly derived from Kotlin reflection. + +* [@GraphQLContext](../execution/contextual-data) - Autowire `GraphQLContext` from the environment +* [@GraphQLDescription](../customizing-schemas/documenting-fields) - Provide a description for a GraphQL field +* [@GraphQLDirective](../customizing-schemas/directives) - Registers directive on a GraphQL field +* [@GraphQLID](scalars#id) - Marks given field as GraphQL `ID` +* [@GraphQLIgnore](../customizing-schemas/excluding-fields) - Exclude field from the GraphQL schema +* [@GraphQLName](../customizing-schemas/renaming-fields) - Override the name used for the type +* Kotlin built in [@Deprecated](../customizing-schemas/evolving-schema) - Apply the GraphQL `@deprecated` directive on the field diff --git a/docs/writing-schemas/basics.md b/docs/writing-schemas/basics.md index c1e8ee5bd5..1d4093823b 100644 --- a/docs/writing-schemas/basics.md +++ b/docs/writing-schemas/basics.md @@ -1,63 +1,63 @@ ---- -id: basics -title: Basics ---- -`graphql-kotlin-schema-generator` provides a single function, `toSchema`, to generate a schema from Kotlin objects. This -function accepts four arguments: `config`, `queries`, `mutations` and `subscriptions`. The `queries`, `mutations` and -`subscriptions` are a list of `TopLevelObject`s and will be used to generate corresponding GraphQL root types. See below -on why we use this wrapper class. The `config` contains all the extra information you need to pass, including custom -hooks, supported packages, and name overrides. -See the [Generator Configuration](generator-config) documentation for more information. - -A query/mutation/subscription type is simply a Kotlin class that specifies **fields**, which can be functions or -properties: - -```kotlin -data class Widget(val id: Int, val value: String) - -class WidgetQuery { - fun widgetById(id: Int): Widget? { - // grabs widget from a data source - } -} - -class WidgetMutation { - fun saveWidget(value: String): Widget { - // some logic goes here - } -} - -val widgetQuery = WidgetQuery() -val widgetMutation = WidgetMutation() -val schema = toSchema( - config = yourCustomConfig() - queries = listOf(TopLevelObject(widgetQuery)), - mutations = listOf(TopLevelObject(widgetMutation)) -) -``` - -will generate: - -```graphql -schema { - query: Query - mutation: Mutation -} - -type Query { - widgetById(id: Int!): Widget -} - -type Mutation { - saveWidget(value: String!): Widget! -} - -type Widget { - id: Int! - value: String! -} -``` - -Any `public` functions defined on a query or mutation Kotlin class will be translated into GraphQL fields on the object -type. `toSchema` will then recursively apply Kotlin reflection on the specified queries and mutations to generate all -remaining object types, their properties, functions, and function arguments. +--- +id: basics +title: Basics +--- +`graphql-kotlin-schema-generator` provides a single function, `toSchema`, to generate a schema from Kotlin objects. This +function accepts four arguments: `config`, `queries`, `mutations` and `subscriptions`. The `queries`, `mutations` and +`subscriptions` are a list of `TopLevelObject`s and will be used to generate corresponding GraphQL root types. See below +on why we use this wrapper class. The `config` contains all the extra information you need to pass, including custom +hooks, supported packages, and name overrides. +See the [Generator Configuration](generator-config) documentation for more information. + +A query, mutation or a subscription type is simply a Kotlin class that specifies **fields**, which can be functions or +properties: + +```kotlin +data class Widget(val id: Int, val value: String) + +class WidgetQuery { + fun widgetById(id: Int): Widget? { + // grabs widget from a data source + } +} + +class WidgetMutation { + fun saveWidget(value: String): Widget { + // some logic for saving widget + } +} + +val widgetQuery = WidgetQuery() +val widgetMutation = WidgetMutation() +val schema = toSchema( + config = yourCustomConfig(), + queries = listOf(TopLevelObject(widgetQuery)), + mutations = listOf(TopLevelObject(widgetMutation)) +) +``` + +will generate: + +```graphql +schema { + query: Query + mutation: Mutation +} + +type Query { + widgetById(id: Int!): Widget +} + +type Mutation { + saveWidget(value: String!): Widget! +} + +type Widget { + id: Int! + value: String! +} +``` + +Any `public` functions defined on a query or mutation Kotlin class will be translated into GraphQL fields on the object +type. `toSchema` will then recursively apply Kotlin reflection on the specified queries and mutations to generate all +remaining object types, their properties, functions, and function arguments. diff --git a/docs/writing-schemas/fields.md b/docs/writing-schemas/fields.md deleted file mode 100644 index 5fd1df65e1..0000000000 --- a/docs/writing-schemas/fields.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -id: fields -title: Fields ---- - -Any **public** fields on the returned objects will be exposed as part of the schema unless they are explicitly marked to -be ignored with `@GraphQLIgnore` annotation. Documentation and deprecation information is also supported. For more -details about different annotations see sections below. - -### Documenting Fields - -Since Javadocs are not available at runtime for introspection, `graphql-kotlin-schema-generator` includes an annotation -class `@GraphQLDescription` that can be used to add schema descriptions to *any* GraphQL schema element: - -```kotlin -@GraphQLDescription("A useful widget") -data class Widget( - @GraphQLDescription("The widget's value that can be null") - val value: Int? -) - -class WidgetQuery: Query { - - @GraphQLDescription("creates new widget for given ID") - fun widgetById(@GraphQLDescription("The special ingredient") id: Int): Widget? = Widget(id) -} -``` - -The above query would produce the following GraphQL schema: - -```graphql -schema { - query: Query -} - -type Query { - """creates new widget for given ID""" - widgetById( - """The special ingredient""" - id: Int! - ): Widget -} - -"""A useful widget""" -type Widget { - """The widget's value that can be null""" - value: Int -} -``` - -You can also override the name used by the generator with `@GraphQLName`. The following schema would be renamed after -generation - -```kotlin -@GraphQLDescription("A useful widget") -@GraphQLName("MyCustomName") -data class Widget(val value: Int?) -``` - -```graphql -"""A useful widget""" -type MyCustomName { - value: Int -} -``` - -### Excluding Fields from Schema - -There are two ways to ensure the GraphQL schema generation omits fields when using Kotlin reflection: - -* The first is by marking the field as non-`public` scope (`private`, `protected`, `internal`) -* The second method is by annotating the field with `@GraphQLIgnore`. - -```kotlin -class SimpleQuery { - @GraphQLIgnore - fun notPartOfSchema() = "ignore me!" - - private fun privateFunctionsAreNotVisible() = "ignored private function" - - fun doSomething(value: Int): Boolean = true -} -``` - -The above query would produce the following GraphQL schema: - -```graphql -schema { - query: Query -} - -type Query { - doSomething(value: Int!): Boolean! -} -``` - -Note that the public method `notPartOfSchema` is not included in the schema. - -### Deprecating Fields - -GraphQL schemas can have fields marked as deprecated. Instead of creating a custom annotation, -`graphql-kotlin-schema-generator` just looks for the `kotlin.Deprecated` annotation and will use that annotation message -for the deprecated reason. - -```kotlin -class SimpleQuery { - @Deprecated(message = "this query is deprecated", replaceWith = ReplaceWith("shinyNewQuery")) - @GraphQLDescription("old query that should not be used always returns false") - fun simpleDeprecatedQuery(): Boolean = false - - @GraphQLDescription("new query that always returns true") - fun shinyNewQuery(): Boolean = true -} -``` - -The above query would produce the following GraphQL schema: - -```graphql -schema { - query: Query -} - -type Query { - - """old query that should not be used always returns false""" - simpleDeprecatedQuery: Boolean! @deprecated(reason: "this query is deprecated, replace with shinyNewQuery") - - """new query that always returns true""" - shinyNewQuery: Boolean! -} -``` - -While you can deprecate any fields/functions/classes in your Kotlin code, GraphQL only supports deprecation directive on -the fields (which correspond to Kotlin fields and functions) and enum values. diff --git a/docs/writing-schemas/scalars.md b/docs/writing-schemas/scalars.md index 05c7cea4d8..e42a52466b 100644 --- a/docs/writing-schemas/scalars.md +++ b/docs/writing-schemas/scalars.md @@ -1,83 +1,83 @@ ---- -id: scalars -title: Scalars ---- - -`graphql-kotlin-schema-generator` can directly map most Kotlin "primitive" types to standard GraphQL scalar types or -extended scalar types provided by `graphql-java`. - -| Kotlin Type | GraphQL Type | -|---------------------|--------------| -| `kotlin.Int` | `Int` | -| `kotlin.Float` | `Float` | -| `kotlin.String` | `String` | -| `kotlin.Boolean` | `Boolean` | -| | **Extended GraphQL Types** | -| `kotlin.Long` | `Long` | -| `kotlin.Short` | `Short` | -| `kotlin.Double` | `Float` | -| `kotlin.BigInteger` | `BigInteger` | -| `kotlin.BigDecimal` | `BigDecimal` | -| `kotlin.Char` | `Char` | - -> NOTE: Extended GraphQL scalar types provided by `graphql-java` are generated as custom scalar types. When using those custom scalar types your GraphQL clients will have to know how to correctly parse and serialize them. See `graphql-java` [documentation](https://www.graphql-java.com/documentation/v13/scalars/) for more details. - -## ID - -GraphQL supports a the scalar type ID, a unique identifier that is not intended to be human readable. ID's are -serialized as a String. To mark given field as an ID field you have to annotate it with `@GraphQLID` annotation. - -> NOTE: `graphql-java` supports other types (`String`, `Int`, `Long`, or `UUID`) but [due to serialization issues (https://github.com/ExpediaGroup/graphql-kotlin/issues/317) we can only directly support Strings. You can still use a type like UUID internally just as long as you convert or parse the value yourself and handle the errors. - -```kotlin -data class Person( - @GraphQLID - val id: String, - val name: String -) - -fun createPerson(@GraphQLID id: String) = Person(id, "Jane Doe") -``` - -This would produce the following schema: - -```graphql -schema { - query: Query -} - -type Query { - createPerson(id: ID!): Person! -} - -type Person { - id: ID! - name: String! -} -``` - -## Custom Scalars - -By default, `graphql-kotlin-schema-generator` uses Kotlin reflections to generate all schema objects. If you want to -apply custom behavior to the objects, you can also define your own custom scalars. Custom scalars have to be explicitly -added to the schema through `SchemaGeneratorHooks.willGenerateGraphQLType`. -See the [Generator Configuration](generator-config) documentation for more information. - -Example usage - -```kotlin -class CustomSchemaGeneratorHooks : SchemaGeneratorHooks { - - override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) { - URL::class -> graphqlURLType - else -> null - } -} - -val graphqlURLType = GraphQLScalarType("URL", - "A type representing a formatted java.net.URL", - object: Coercing { ... } -) -``` - -Once the scalars are registered you can use them anywhere in the schema as regular objects. +--- +id: scalars +title: Scalars +--- + +`graphql-kotlin-schema-generator` can directly map most Kotlin "primitive" types to standard GraphQL scalar types or +extended scalar types provided by `graphql-java`. + +| Kotlin Type | GraphQL Type | +|---------------------|--------------| +| `kotlin.Int` | `Int` | +| `kotlin.Float` | `Float` | +| `kotlin.String` | `String` | +| `kotlin.Boolean` | `Boolean` | +| | **Extended GraphQL Types** | +| `kotlin.Long` | `Long` | +| `kotlin.Short` | `Short` | +| `kotlin.Double` | `Float` | +| `kotlin.BigInteger` | `BigInteger` | +| `kotlin.BigDecimal` | `BigDecimal` | +| `kotlin.Char` | `Char` | + +> NOTE: Extended GraphQL scalar types provided by `graphql-java` are generated as custom scalar types. When using those custom scalar types your GraphQL clients will have to know how to correctly parse and serialize them. See `graphql-java` [documentation](https://www.graphql-java.com/documentation/v13/scalars/) for more details. + +## ID + +GraphQL supports a the scalar type ID, a unique identifier that is not intended to be human readable. ID's are +serialized as a String. To mark given field as an ID field you have to annotate it with `@GraphQLID` annotation. + +> NOTE: `graphql-java` supports other types (`String`, `Int`, `Long`, or `UUID`) but [due to serialization issues](https://github.com/ExpediaGroup/graphql-kotlin/issues/317) we can only directly support Strings. You can still use a type like UUID internally just as long as you convert or parse the value yourself and handle the errors. + +```kotlin +data class Person( + @GraphQLID + val id: String, + val name: String +) + +fun createPerson(@GraphQLID id: String) = Person(id, "Jane Doe") +``` + +This would produce the following schema: + +```graphql +schema { + query: Query +} + +type Query { + createPerson(id: ID!): Person! +} + +type Person { + id: ID! + name: String! +} +``` + +## Custom Scalars + +By default, `graphql-kotlin-schema-generator` uses Kotlin reflections to generate all schema objects. If you want to +apply custom behavior to the objects, you can also define your own custom scalars. Custom scalars have to be explicitly +added to the schema through `SchemaGeneratorHooks.willGenerateGraphQLType`. +See the [Generator Configuration](generator-config) documentation for more information. + +Example usage + +```kotlin +class CustomSchemaGeneratorHooks : SchemaGeneratorHooks { + + override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) { + URL::class -> graphqlURLType + else -> null + } +} + +val graphqlURLType = GraphQLScalarType("URL", + "A type representing a formatted java.net.URL", + object: Coercing { ... } +) +``` + +Once the scalars are registered you can use them anywhere in the schema as regular objects. diff --git a/website/sidebars.json b/website/sidebars.json index c959ac8d43..4e40fe381c 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -10,25 +10,35 @@ "label": "Writing schemas with Kotlin", "ids": [ "writing-schemas/basics", - "writing-schemas/scalars", "writing-schemas/nullability", - "writing-schemas/lists", - "writing-schemas/fields", "writing-schemas/arguments", + "writing-schemas/scalars", "writing-schemas/enums", + "writing-schemas/lists", "writing-schemas/interfaces", "writing-schemas/unions", - "writing-schemas/directives", "writing-schemas/nested-queries", - "writing-schemas/generator-config", "writing-schemas/annotations" ] }, + { + "type": "subcategory", + "label": "Customizing Schema", + "ids": [ + "customizing-schemas/generator-config", + "customizing-schemas/documenting-fields", + "customizing-schemas/excluding-fields", + "customizing-schemas/renaming-fields", + "customizing-schemas/directives", + "customizing-schemas/evolving-schema" + ] + }, { "type": "subcategory", "label": "Execution", "ids": [ "execution/data-fetching-environment", + "execution/contextual-data", "execution/async-models", "execution/subscriptions" ]