|
1 | | ---- |
2 | | -id: directives |
3 | | -title: Directives |
4 | | ---- |
5 | | - |
6 | | -GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime |
7 | | -behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the |
8 | | -user authentication and authorization. While [GraphQL |
9 | | -spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives - |
10 | | -`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by |
11 | | -`graphql-kotlin-schema-generator`. |
12 | | - |
13 | | -## Default Directives |
14 | | - |
15 | | -`@deprecated` - schema directive used to represent deprecated portion of the schema. |
16 | | -See [@Deprecated](customizing-schemas/evolving-schema) annotation documentation for more details |
17 | | - |
18 | | -```graphql |
19 | | -type Query { |
20 | | - deprecatedQuery: Boolean! @deprecated(reason: "No longer supported") |
21 | | -} |
22 | | -``` |
23 | | - |
24 | | -`@skip` - query directive that allows for conditional exclusion of fields or fragments |
25 | | - |
26 | | -```graphql |
27 | | -query myQuery($shouldSkip: Boolean) { |
28 | | - myField @skip(if: $shouldSkip) |
29 | | -} |
30 | | -``` |
31 | | - |
32 | | -`@include` - query directive that allows for conditional inclusion of fields or fragments |
33 | | - |
34 | | -```graphql |
35 | | -query myQuery($shouldInclude: Boolean) { |
36 | | - myField @include(if: $shouldInclude) |
37 | | -} |
38 | | -``` |
39 | | - |
40 | | -## Custom Directives |
41 | | - |
42 | | -Custom directives can be added to the schema using custom annotations: |
43 | | - |
44 | | -```kotlin |
45 | | -@GraphQLDirective( |
46 | | - name = "awesome", |
47 | | - description = "This element is great", |
48 | | - locations = [FIELD_DEFINITION] |
49 | | -) |
50 | | -annotation class AwesomeDirective(val value: String) |
51 | | - |
52 | | -class MyQuery { |
53 | | - @AwesomeDirective("cool stuff") |
54 | | - val somethingGreat: String = "Hello World" |
55 | | -} |
56 | | -``` |
57 | | - |
58 | | -The directive will then added to the schema as: |
59 | | - |
60 | | -```graphql |
61 | | -# This element is great |
62 | | -directive @awesome(value: String) on FIELD_DEFINITION |
63 | | - |
64 | | -type MyQuery { |
65 | | - somethingGreat: String @awesome("cool stuff") |
66 | | -} |
67 | | -``` |
68 | | - |
69 | | -Directives can be added to various places in the schema. See the |
70 | | -[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332) |
71 | | -enum from `graphql-java` for a full list of valid locations. |
72 | | - |
73 | | -**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly |
74 | | -instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL |
75 | | -issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java |
76 | | -issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue. |
77 | | - |
78 | | -### Naming Convention |
79 | | - |
80 | | -As described in the example above, the directive name in the schema will by default come from the |
81 | | -`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the |
82 | | -directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in |
83 | | -the example above). |
84 | | - |
85 | | -### Customizing Behavior |
86 | | - |
87 | | -Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to |
88 | | -modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through |
89 | | -`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`. |
90 | | - |
91 | | -Example of a directive that converts field to lowercase |
92 | | - |
93 | | -```kotlin |
94 | | -@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase") |
95 | | -annotation class LowercaseDirective |
96 | | - |
97 | | -class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { |
98 | | - |
99 | | - override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { |
100 | | - val field = environment.element |
101 | | - val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher() |
102 | | - |
103 | | - val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( |
104 | | - originalDataFetcher, |
105 | | - BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() } |
106 | | - ) |
107 | | - environment.setDataFetcher(lowerCaseFetcher) |
108 | | - return field |
109 | | - } |
110 | | -} |
111 | | -``` |
112 | | - |
113 | | -While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in |
114 | | -`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our |
115 | | -[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt) |
116 | | -to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or |
117 | | -could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts |
118 | | -`KotlinSchemaDirectiveEnvironment`. |
119 | | - |
120 | | -```kotlin |
121 | | -val queries = ... |
122 | | -val customWiringFactory = KotlinDirectiveWiringFactory( |
123 | | - manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring())) |
124 | | -val customHooks = object : SchemaGeneratorHooks { |
125 | | - override val wiringFactory: KotlinDirectiveWiringFactory |
126 | | - get() = customWiringFactory |
127 | | -} |
128 | | -val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks) |
129 | | -val schema = toSchema(queries = queries, config = schemaGeneratorConfig) |
130 | | -``` |
131 | | - |
132 | | -While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in |
133 | | -mind though that data fetchers are used to resolve the fields so only field directives (and by association their |
134 | | -arguments directives) can modify runtime behavior based on the context and user input. |
135 | | - |
136 | | -**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the |
137 | | -`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will |
138 | | -first attempt to use `WiringFactory` and then fallback to manual overrides.** |
139 | | - |
140 | | -For more details please refer to the example usage of directives in our [example |
141 | | -app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring). |
142 | | - |
143 | | -## Directive Chaining |
144 | | - |
145 | | -Directives are applied in the order annotations are declared on the given object. Given |
146 | | - |
147 | | -```kotlin |
148 | | - @Directive1 |
149 | | - @Directive2 |
150 | | - fun doSomething(): String { |
151 | | - // does something |
152 | | - } |
153 | | -``` |
154 | | - |
155 | | -`Directive1` will be applied first followed by the `Directive2`. |
| 1 | +--- |
| 2 | +id: directives |
| 3 | +title: Directives |
| 4 | +--- |
| 5 | + |
| 6 | +GraphQL directives can be used to transform the schema types, fields and arguments as well as modify the runtime |
| 7 | +behavior of the query (e.g. implement access control, etc). Common use cases involve limiting functionality based on the |
| 8 | +user authentication and authorization. While [GraphQL |
| 9 | +spec](https://graphql.github.io/graphql-spec/draft/#sec-Type-System.Directives) specifies two types of directives - |
| 10 | +`executable` (aka query) and `type system` (aka schema) directives, only the latter one is supported by |
| 11 | +`graphql-kotlin-schema-generator`. |
| 12 | + |
| 13 | +## Default Directives |
| 14 | + |
| 15 | +`@deprecated` - schema directive used to represent deprecated portion of the schema. |
| 16 | +See [@Deprecated](customizing-schemas/deprecating-schema) annotation documentation for more details |
| 17 | + |
| 18 | +```graphql |
| 19 | +type Query { |
| 20 | + deprecatedQuery: Boolean! @deprecated(reason: "No longer supported") |
| 21 | +} |
| 22 | +``` |
| 23 | + |
| 24 | +`@skip` - query directive that allows for conditional exclusion of fields or fragments |
| 25 | + |
| 26 | +```graphql |
| 27 | +query myQuery($shouldSkip: Boolean) { |
| 28 | + myField @skip(if: $shouldSkip) |
| 29 | +} |
| 30 | +``` |
| 31 | + |
| 32 | +`@include` - query directive that allows for conditional inclusion of fields or fragments |
| 33 | + |
| 34 | +```graphql |
| 35 | +query myQuery($shouldInclude: Boolean) { |
| 36 | + myField @include(if: $shouldInclude) |
| 37 | +} |
| 38 | +``` |
| 39 | + |
| 40 | +## Custom Directives |
| 41 | + |
| 42 | +Custom directives can be added to the schema using custom annotations: |
| 43 | + |
| 44 | +```kotlin |
| 45 | +@GraphQLDirective( |
| 46 | + name = "awesome", |
| 47 | + description = "This element is great", |
| 48 | + locations = [FIELD_DEFINITION] |
| 49 | +) |
| 50 | +annotation class AwesomeDirective(val value: String) |
| 51 | + |
| 52 | +class MyQuery { |
| 53 | + @AwesomeDirective("cool stuff") |
| 54 | + val somethingGreat: String = "Hello World" |
| 55 | +} |
| 56 | +``` |
| 57 | + |
| 58 | +The directive will then added to the schema as: |
| 59 | + |
| 60 | +```graphql |
| 61 | +# This element is great |
| 62 | +directive @awesome(value: String) on FIELD_DEFINITION |
| 63 | + |
| 64 | +type MyQuery { |
| 65 | + somethingGreat: String @awesome("cool stuff") |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +Directives can be added to various places in the schema. See the |
| 70 | +[graphql.introspection.Introspection.DirectiveLocation](https://github.com/graphql-java/graphql-java/blob/v13.0/src/main/java/graphql/introspection/Introspection.java#L332) |
| 71 | +enum from `graphql-java` for a full list of valid locations. |
| 72 | + |
| 73 | +**Note that GraphQL directives are currently not available through introspection and you have to use SDL directly |
| 74 | +instead (you can use convenient `print` extension function of `GraphQLSchema`)**. See [GraphQL |
| 75 | +issue](https://github.com/facebook/graphql/issues/300) and corresponding [graphql-java |
| 76 | +issue](https://github.com/graphql-java/graphql-java/issues/1017) for more details about the introspection issue. |
| 77 | + |
| 78 | +### Naming Convention |
| 79 | + |
| 80 | +As described in the example above, the directive name in the schema will by default come from the |
| 81 | +`@GraphQLDirective.name` attribute which should follow `lowerCamelCase` format. If this value is not specified, the |
| 82 | +directive name will default to the normalized decapitalized name of the annotated annotation (eg: `awesomeDirective` in |
| 83 | +the example above). |
| 84 | + |
| 85 | +### Customizing Behavior |
| 86 | + |
| 87 | +Directives allow you to customize the behavior of your schema based on some predefined conditions. Simplest way to |
| 88 | +modify the default behavior of your GraphQLTypes is by providing your custom `KotlinSchemaDirectiveWiring` through |
| 89 | +`KotlinDirectiveWiringFactory` factory used by your `SchemaGeneratorHooks`. |
| 90 | + |
| 91 | +Example of a directive that converts field to lowercase |
| 92 | + |
| 93 | +```kotlin |
| 94 | +@GraphQLDirective(name = "lowercase", description = "Modifies the string field to lowercase") |
| 95 | +annotation class LowercaseDirective |
| 96 | + |
| 97 | +class LowercaseSchemaDirectiveWiring : KotlinSchemaDirectiveWiring { |
| 98 | + |
| 99 | + override fun onField(environment: KotlinFieldDirectiveEnvironment): GraphQLFieldDefinition { |
| 100 | + val field = environment.element |
| 101 | + val originalDataFetcher: DataFetcher<Any> = environment.getDataFetcher() |
| 102 | + |
| 103 | + val lowerCaseFetcher = DataFetcherFactories.wrapDataFetcher( |
| 104 | + originalDataFetcher, |
| 105 | + BiFunction<DataFetchingEnvironment, Any, Any>{ _, value -> value.toString().toLowerCase() } |
| 106 | + ) |
| 107 | + environment.setDataFetcher(lowerCaseFetcher) |
| 108 | + return field |
| 109 | + } |
| 110 | +} |
| 111 | +``` |
| 112 | + |
| 113 | +While you can manually apply all the runtime wirings to the corresponding GraphQL types directly in |
| 114 | +`SchemaGeneratorHooks#onRewireGraphQLType`, we recommend the usage of our |
| 115 | +[KotlinDirectiveWiringFactory](https://github.com/ExpediaGroup/graphql-kotlin/blob/master/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/directives/KotlinDirectiveWiringFactory.kt) |
| 116 | +to simplify the integrations. `KotlinDirectiveWiringFactory` accepts a mapping of directives to corresponding wirings or |
| 117 | +could be extended to provide the wirings through `KotlinDirectiveWiringFactory#getSchemaDirectiveWiring` that accepts |
| 118 | +`KotlinSchemaDirectiveEnvironment`. |
| 119 | + |
| 120 | +```kotlin |
| 121 | +val queries = ... |
| 122 | +val customWiringFactory = KotlinDirectiveWiringFactory( |
| 123 | + manualWiring = mapOf<String, KotlinSchemaDirectiveWiring>("lowercase" to LowercaseSchemaDirectiveWiring())) |
| 124 | +val customHooks = object : SchemaGeneratorHooks { |
| 125 | + override val wiringFactory: KotlinDirectiveWiringFactory |
| 126 | + get() = customWiringFactory |
| 127 | +} |
| 128 | +val schemaGeneratorConfig = SchemaGeneratorConfig(hooks = customHooks) |
| 129 | +val schema = toSchema(queries = queries, config = schemaGeneratorConfig) |
| 130 | +``` |
| 131 | + |
| 132 | +While providing directives on different schema elements you will be able to modify the underlying GraphQL types. Keep in |
| 133 | +mind though that data fetchers are used to resolve the fields so only field directives (and by association their |
| 134 | +arguments directives) can modify runtime behavior based on the context and user input. |
| 135 | + |
| 136 | +**NOTE: `graphql-kotlin` prioritizes manual wiring mappings over the wirings provided by the |
| 137 | +`KotlinDirectiveWiringFactory#getSchemaDirectiveWiring`. This is a different behavior than `graphql-java` which will |
| 138 | +first attempt to use `WiringFactory` and then fallback to manual overrides.** |
| 139 | + |
| 140 | +For more details please refer to the example usage of directives in our [example |
| 141 | +app](https://github.com/ExpediaGroup/graphql-kotlin/tree/master/examples/spring). |
| 142 | + |
| 143 | +## Directive Chaining |
| 144 | + |
| 145 | +Directives are applied in the order annotations are declared on the given object. Given |
| 146 | + |
| 147 | +```kotlin |
| 148 | + @Directive1 |
| 149 | + @Directive2 |
| 150 | + fun doSomething(): String { |
| 151 | + // does something |
| 152 | + } |
| 153 | +``` |
| 154 | + |
| 155 | +`Directive1` will be applied first followed by the `Directive2`. |
0 commit comments