Skip to content

Add docs for new schema generator feature #594

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
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
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets keep it as more generic evolving schema and add notes about adding new fields etc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mainly doing this to help the sidebar be a little more clear. Right now it is not super descriptive for what is there.

Do you think we can just include this all on one page?

Screen Shot 2020-02-03 at 2 48 46 PM

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only info in the topic right now is on deprecating schema elements, so I'd recomment the title: Deprecating Schema Elements.

If or when you talk about other ways to evolve the schema or its elements, I'd change to something like Evolving Schema.

Just my two cents.

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