Skip to content

Commit

Permalink
Add docs for new schema generator feature (#594)
Browse files Browse the repository at this point in the history
Added new features in #593 that we did not document
  • Loading branch information
smyrick authored Feb 7, 2020
1 parent cb7ba81 commit 999c555
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 159 deletions.
32 changes: 32 additions & 0 deletions docs/customizing-schemas/advanced-features.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
id: advanced-features
title: Advanced Features
---

## Adding Custom Additional Types

There are a couple ways you can add more types to the schema without having them be directly consumed by a type in your schema.
This may be required for [Apollo Federation](federated/apollo-federation), or maybe adding other interface implementations that are not picked up.


### `SchemaGenerator::addAdditionalTypesWithAnnotation`

This method is protected so if you override the `SchemaGenerator` used you can call this method to add types that have a specific annotation.
You can see how this is used in `graphql-kotlin-federation` as [an example](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-federation/src/main/kotlin/com/expediagroup/graphql/federation/FederatedSchemaGenerator.kt).

### `SchemaGenerator::generateAdditionalTypes`

This method is called by `SchemaGenerator::get` after all the queries, mutations, and subscriptions have been generated and it is going to add all the types saved in an internal set that were not generated by reflection.
To change the behaviour, you can update the set and then call the super method with the new value.

Example:

```kotlin
class CustomSchemaGenerator(config: SchemaGeneratorConfig) : SchemaGenerator(config) {

override fun generateAdditionalTypes(types: Set<KType>): Set<GraphQLType> {
val newTypes = types.toMutableSet().add(MyNewType()::class.createType())
return super.generateAdditionalTypes(newTypes)
}
}
```
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
id: evolving-schema
title: Evolving Schema
id: deprecating-schema
title: Deprecating Schema
---

### Deprecating Fields
Expand Down
310 changes: 155 additions & 155 deletions docs/customizing-schemas/directives.md
Original file line number Diff line number Diff line change
@@ -1,155 +1,155 @@
---
id: directives
title: Directives
---

GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime
behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the
user authentication and authorization. While [GraphQL
spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives -
`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by
`graphql-kotlin-schema-generator`.

## Default Directives

`@deprecated` - schema directive used to represent deprecated portion of the schema.
See [@Deprecated](customizing-schemas/evolving-schema) annotation documentation for more details

```graphql
type Query {
deprecatedQuery: Boolean! @deprecated(reason: "No longer supported")
}
```

`@skip` - query directive that allows for conditional exclusion of fields or fragments

```graphql
query myQuery($shouldSkip: Boolean) {
myField @skip(if: $shouldSkip)
}
```

`@include` - query directive that allows for conditional inclusion of fields or fragments

```graphql
query myQuery($shouldInclude: Boolean) {
myField @include(if: $shouldInclude)
}
```

## Custom Directives

Custom directives can be added to the schema using custom annotations:

```kotlin
@GraphQLDirective(
name = "awesome",
description = "This element is great",
locations = [FIELD_DEFINITION]
)
annotation class AwesomeDirective(val value: String)

class MyQuery {
@AwesomeDirective("cool stuff")
val somethingGreat: String = "Hello World"
}
```

The directive will then added to the schema as:

```graphql
# This element is great
directive @awesome(value: String) on FIELD_DEFINITION

type MyQuery {
somethingGreat: String @awesome("cool stuff")
}
```

Directives can be added to various places in the schema. See the
[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332)
enum from `graphql-java` for a full list of valid locations.

**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly
instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL
issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java
issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue.

### Naming Convention

As described in the example above, the directive name in the schema will by default come from the
`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the
directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in
the example above).

### Customizing Behavior

Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to
modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through
`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`.

Example of a directive that converts field to lowercase

```kotlin
@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase")
annotation class LowercaseDirective

class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring {

override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition {
val field = environment.element
val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher()

val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher(
originalDataFetcher,
BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() }
)
environment.setDataFetcher(lowerCaseFetcher)
return field
}
}
```

While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in
`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our
[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt)
to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or
could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts
`KotlinSchemaDirectiveEnvironment`.

```kotlin
val queries = ...
val customWiringFactory = KotlinDirectiveWiringFactory(
manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring()))
val customHooks = object : SchemaGeneratorHooks {
override val wiringFactory: KotlinDirectiveWiringFactory
get() = customWiringFactory
}
val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks)
val schema = toSchema(queries = queries, config = schemaGeneratorConfig)
```

While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in
mind though that data fetchers are used to resolve the fields so only field directives (and by association their
arguments directives) can modify runtime behavior based on the context and user input.

**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the
`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will
first attempt to use `WiringFactory` and then fallback to manual overrides.**

For more details please refer to the example usage of directives in our [example
app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring).

## Directive Chaining

Directives are applied in the order annotations are declared on the given object. Given

```kotlin
@Directive1
@Directive2
fun doSomething(): String {
// does something
}
```

`Directive1` will be applied first followed by the `Directive2`.
---
id: directives
title: Directives
---

GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime
behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the
user authentication and authorization. While [GraphQL
spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives -
`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by
`graphql-kotlin-schema-generator`.

## Default Directives

`@deprecated` - schema directive used to represent deprecated portion of the schema.
See [@Deprecated](customizing-schemas/deprecating-schema) annotation documentation for more details

```graphql
type Query {
deprecatedQuery: Boolean! @deprecated(reason: "No longer supported")
}
```

`@skip` - query directive that allows for conditional exclusion of fields or fragments

```graphql
query myQuery($shouldSkip: Boolean) {
myField @skip(if: $shouldSkip)
}
```

`@include` - query directive that allows for conditional inclusion of fields or fragments

```graphql
query myQuery($shouldInclude: Boolean) {
myField @include(if: $shouldInclude)
}
```

## Custom Directives

Custom directives can be added to the schema using custom annotations:

```kotlin
@GraphQLDirective(
name = "awesome",
description = "This element is great",
locations = [FIELD_DEFINITION]
)
annotation class AwesomeDirective(val value: String)

class MyQuery {
@AwesomeDirective("cool stuff")
val somethingGreat: String = "Hello World"
}
```

The directive will then added to the schema as:

```graphql
# This element is great
directive @awesome(value: String) on FIELD_DEFINITION

type MyQuery {
somethingGreat: String @awesome("cool stuff")
}
```

Directives can be added to various places in the schema. See the
[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332)
enum from `graphql-java` for a full list of valid locations.

**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly
instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL
issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java
issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue.

### Naming Convention

As described in the example above, the directive name in the schema will by default come from the
`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the
directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in
the example above).

### Customizing Behavior

Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to
modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through
`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`.

Example of a directive that converts field to lowercase

```kotlin
@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase")
annotation class LowercaseDirective

class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring {

override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition {
val field = environment.element
val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher()

val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher(
originalDataFetcher,
BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() }
)
environment.setDataFetcher(lowerCaseFetcher)
return field
}
}
```

While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in
`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our
[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt)
to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or
could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts
`KotlinSchemaDirectiveEnvironment`.

```kotlin
val queries = ...
val customWiringFactory = KotlinDirectiveWiringFactory(
manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring()))
val customHooks = object : SchemaGeneratorHooks {
override val wiringFactory: KotlinDirectiveWiringFactory
get() = customWiringFactory
}
val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks)
val schema = toSchema(queries = queries, config = schemaGeneratorConfig)
```

While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in
mind though that data fetchers are used to resolve the fields so only field directives (and by association their
arguments directives) can modify runtime behavior based on the context and user input.

**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the
`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will
first attempt to use `WiringFactory` and then fallback to manual overrides.**

For more details please refer to the example usage of directives in our [example
app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring).

## Directive Chaining

Directives are applied in the order annotations are declared on the given object. Given

```kotlin
@Directive1
@Directive2
fun doSomething(): String {
// does something
}
```

`Directive1` will be applied first followed by the `Directive2`.
2 changes: 1 addition & 1 deletion docs/writing-schemas/annotations.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ for things that can't be directly derived from Kotlin reflection.
* [@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
* Kotlin built in [@Deprecated](../customizing-schemas/deprecating-schema) - Apply the GraphQL `@deprecated` directive on the field
3 changes: 2 additions & 1 deletion website/sidebars.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"customizing-schemas/excluding-fields",
"customizing-schemas/renaming-fields",
"customizing-schemas/directives",
"customizing-schemas/evolving-schema"
"customizing-schemas/deprecating-schema",
"customizing-schemas/advanced-features"
]
},
{
Expand Down

0 comments on commit 999c555

Please sign in to comment.