From 999c555ad931d9e1a66e1ea257d0921734d51347 Mon Sep 17 00:00:00 2001 From: Shane Myrick Date: Thu, 6 Feb 2020 21:05:37 -0800 Subject: [PATCH] Add docs for new schema generator feature (#594) Added new features in https://github.com/ExpediaGroup/graphql-kotlin/pull/593 that we did not document --- docs/customizing-schemas/advanced-features.md | 32 ++ ...olving-schema.md => deprecating-schema.md} | 4 +- docs/customizing-schemas/directives.md | 310 +++++++++--------- docs/writing-schemas/annotations.md | 2 +- website/sidebars.json | 3 +- 5 files changed, 192 insertions(+), 159 deletions(-) create mode 100644 docs/customizing-schemas/advanced-features.md rename docs/customizing-schemas/{evolving-schema.md => deprecating-schema.md} (96%) diff --git a/docs/customizing-schemas/advanced-features.md b/docs/customizing-schemas/advanced-features.md new file mode 100644 index 0000000000..3d424efe93 --- /dev/null +++ b/docs/customizing-schemas/advanced-features.md @@ -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): Set { + val newTypes = types.toMutableSet().add(MyNewType()::class.createType()) + return super.generateAdditionalTypes(newTypes) + } +} +``` diff --git a/docs/customizing-schemas/evolving-schema.md b/docs/customizing-schemas/deprecating-schema.md similarity index 96% rename from docs/customizing-schemas/evolving-schema.md rename to docs/customizing-schemas/deprecating-schema.md index 66b14b6a10..189f6176fb 100644 --- a/docs/customizing-schemas/evolving-schema.md +++ b/docs/customizing-schemas/deprecating-schema.md @@ -1,6 +1,6 @@ --- -id: evolving-schema -title: Evolving Schema +id: deprecating-schema +title: Deprecating Schema --- ### Deprecating Fields diff --git a/docs/customizing-schemas/directives.md b/docs/customizing-schemas/directives.md index 7ea017729e..61bf6fdfa6 100644 --- a/docs/customizing-schemas/directives.md +++ b/docs/customizing-schemas/directives.md @@ -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 = environment.getDataFetcher() - - val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( - originalDataFetcher, - BiFunction{ _, 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("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 = environment.getDataFetcher() + + val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( + originalDataFetcher, + BiFunction{ _, 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("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`. diff --git a/docs/writing-schemas/annotations.md b/docs/writing-schemas/annotations.md index ee7bad9206..9002438977 100644 --- a/docs/writing-schemas/annotations.md +++ b/docs/writing-schemas/annotations.md @@ -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 diff --git a/website/sidebars.json b/website/sidebars.json index 5fcf02c19a..81b8fdc84c 100644 --- a/website/sidebars.json +++ b/website/sidebars.json @@ -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" ] }, {