Skip to content

Commit bb9f30b

Browse files
dariuszkucsmyrick
authored and
smyrick
committed
Explicit support for deprecated directive in the schema (ExpediaGroup#247)
* Explicit support for deprecated directive in the schema * custom SDL printer * customize schema print extension, fix unit tests and detekt * add printer tests * revert changes to GraphQLName
1 parent 202e19e commit bb9f30b

File tree

14 files changed

+378
-35
lines changed

14 files changed

+378
-35
lines changed

example/src/main/kotlin/com/expedia/graphql/sample/Application.kt

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.expedia.graphql.SchemaGeneratorConfig
44
import com.expedia.graphql.TopLevelObject
55
import com.expedia.graphql.directives.KotlinDirectiveWiringFactory
66
import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider
7+
import com.expedia.graphql.extensions.print
78
import com.expedia.graphql.hooks.SchemaGeneratorHooks
89
import com.expedia.graphql.sample.datafetchers.CustomDataFetcherFactoryProvider
910
import com.expedia.graphql.sample.datafetchers.SpringDataFetcherFactory
@@ -20,7 +21,6 @@ import graphql.execution.AsyncSerialExecutionStrategy
2021
import graphql.execution.DataFetcherExceptionHandler
2122
import graphql.execution.SubscriptionExecutionStrategy
2223
import graphql.schema.GraphQLSchema
23-
import graphql.schema.idl.SchemaPrinter
2424
import org.slf4j.LoggerFactory
2525
import org.springframework.boot.autoconfigure.SpringBootApplication
2626
import org.springframework.boot.runApplication
@@ -51,23 +51,12 @@ class Application {
5151
dataFetcherFactoryProvider = dataFetcherFactoryProvider
5252
)
5353

54-
@Bean
55-
fun schemaPrinter() = SchemaPrinter(
56-
SchemaPrinter.Options.defaultOptions()
57-
.includeScalarTypes(true)
58-
.includeExtendedScalarTypes(true)
59-
.includeIntrospectionTypes(true)
60-
.includeSchemaDefintion(true)
61-
.includeDirectives(true)
62-
)
63-
6454
@Bean
6555
fun schema(
6656
queries: List<Query>,
6757
mutations: List<Mutation>,
6858
subscriptions: List<Subscription>,
69-
schemaConfig: SchemaGeneratorConfig,
70-
schemaPrinter: SchemaPrinter
59+
schemaConfig: SchemaGeneratorConfig
7160
): GraphQLSchema {
7261
fun List<Any>.toTopLevelObjects() = this.map {
7362
TopLevelObject(it)
@@ -80,7 +69,7 @@ class Application {
8069
subscriptions = subscriptions.toTopLevelObjects()
8170
)
8271

83-
logger.info(schemaPrinter.print(schema))
72+
logger.info(schema.print())
8473

8574
return schema
8675
}

example/src/main/kotlin/com/expedia/graphql/sample/RoutesConfiguration.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.expedia.graphql.sample
22

3+
import com.expedia.graphql.extensions.print
34
import com.fasterxml.jackson.databind.ObjectMapper
45
import com.fasterxml.jackson.databind.type.MapType
56
import com.fasterxml.jackson.databind.type.TypeFactory
67
import graphql.schema.GraphQLSchema
7-
import graphql.schema.idl.SchemaPrinter
88
import org.springframework.beans.factory.annotation.Value
99
import org.springframework.context.annotation.Bean
1010
import org.springframework.context.annotation.Configuration
@@ -21,7 +21,6 @@ import reactor.core.publisher.Mono
2121
@Configuration
2222
class RoutesConfiguration(
2323
val schema: GraphQLSchema,
24-
val schemaPrinter: SchemaPrinter,
2524
private val queryHandler: QueryHandler,
2625
private val objectMapper: ObjectMapper,
2726
@Value("classpath:/graphql-playground.html") private val playgroundHtml: Resource
@@ -42,7 +41,7 @@ class RoutesConfiguration(
4241
@Bean
4342
fun sdlRoute() = router {
4443
GET("/sdl") {
45-
ok().contentType(MediaType.TEXT_PLAIN).syncBody(schemaPrinter.print(schema))
44+
ok().contentType(MediaType.TEXT_PLAIN).syncBody(schema.print())
4645
}
4746
}
4847

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.expedia.graphql.directives
2+
3+
import graphql.Scalars
4+
import graphql.introspection.Introspection
5+
import graphql.schema.GraphQLArgument
6+
import graphql.schema.GraphQLDirective
7+
import graphql.schema.GraphQLNonNull
8+
9+
const val DEPRECATED_DIRECTIVE_NAME = "deprecated"
10+
11+
private val DefaultDeprecatedArgument: GraphQLArgument = GraphQLArgument.newArgument()
12+
.name("reason")
13+
.type(GraphQLNonNull.nonNull(Scalars.GraphQLString))
14+
.defaultValue("No longer supported")
15+
.build()
16+
17+
internal val DeprecatedDirective: GraphQLDirective = GraphQLDirective.newDirective()
18+
.name(DEPRECATED_DIRECTIVE_NAME)
19+
.description("Marks the target field/enum value as deprecated")
20+
.argument(DefaultDeprecatedArgument)
21+
.validLocations(Introspection.DirectiveLocation.FIELD_DEFINITION, Introspection.DirectiveLocation.ENUM_VALUE)
22+
.build()
23+
24+
internal fun deprecatedDirectiveWithReason(reason: String): GraphQLDirective = DeprecatedDirective.transform { directive ->
25+
directive.argument(DefaultDeprecatedArgument.transform { arg ->
26+
arg.value(reason)
27+
})
28+
}

src/main/kotlin/com/expedia/graphql/directives/KotlinDirectiveWiringFactory.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ import graphql.schema.GraphQLDirectiveContainer
99
import graphql.schema.GraphQLFieldDefinition
1010
import graphql.schema.GraphQLType
1111

12+
/**
13+
* Default no-op wiring for deprecated directive.
14+
*/
15+
private val defaultDeprecatedWiring = object : KotlinSchemaDirectiveWiring {}
16+
1217
/**
1318
* Wiring factory that is used to provide the directives.
1419
*/
@@ -66,10 +71,16 @@ open class KotlinDirectiveWiringFactory(
6671
return modifiedObject
6772
}
6873

69-
private fun discoverWiringProvider(directiveName: String, env: KotlinSchemaDirectiveEnvironment<GraphQLDirectiveContainer>): KotlinSchemaDirectiveWiring? =
70-
if (directiveName in manualWiring) {
74+
private fun discoverWiringProvider(directiveName: String, env: KotlinSchemaDirectiveEnvironment<GraphQLDirectiveContainer>): KotlinSchemaDirectiveWiring? {
75+
var wiring = if (directiveName in manualWiring) {
7176
manualWiring[directiveName]
7277
} else {
7378
getSchemaDirectiveWiring(env)
7479
}
80+
81+
if (null == wiring && DEPRECATED_DIRECTIVE_NAME == directiveName) {
82+
wiring = defaultDeprecatedWiring
83+
}
84+
return wiring
85+
}
7586
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.expedia.graphql.extensions
2+
3+
import com.expedia.graphql.directives.DeprecatedDirective
4+
import graphql.Directives
5+
import graphql.schema.GraphQLSchema
6+
import graphql.schema.idl.SchemaPrinter
7+
8+
/**
9+
* Prints out SDL representation of a target schema.
10+
*
11+
* @param includeIntrospectionTypes boolean flag indicating whether SDL should include introspection types
12+
* @param includeScalarTypes boolean flag indicating whether SDL should include custom schema scalars
13+
* @param includeExtendedScalarTypes boolean flag indicating whether SDL should include extended scalars (e.g. Long)
14+
* supported by graphql-java, if set will automatically also set the includeScalarTypes flag
15+
* @param includeDefaultSchemaDefinition boolean flag indicating whether SDL should include schema definition if using
16+
* default root type names
17+
* @param includeDirectives boolean flag indicating whether SDL should include directive information
18+
*/
19+
fun GraphQLSchema.print(
20+
includeIntrospectionTypes: Boolean = false,
21+
includeScalarTypes: Boolean = true,
22+
includeExtendedScalarTypes: Boolean = true,
23+
includeDefaultSchemaDefinition: Boolean = true,
24+
includeDirectives: Boolean = true
25+
): String {
26+
val schemaPrinter = SchemaPrinter(
27+
SchemaPrinter.Options.defaultOptions()
28+
.includeIntrospectionTypes(includeIntrospectionTypes)
29+
.includeScalarTypes(includeScalarTypes || includeExtendedScalarTypes)
30+
.includeExtendedScalarTypes(includeExtendedScalarTypes)
31+
.includeSchemaDefintion(includeDefaultSchemaDefinition)
32+
.includeDirectives(includeDirectives)
33+
)
34+
35+
var schemaString = schemaPrinter.print(this)
36+
if (includeDirectives) {
37+
// graphql-java SchemaPrinter filters out common directives, below is a workaround to print default built-in directives
38+
val defaultDirectives = arrayOf(Directives.IncludeDirective, Directives.SkipDirective, DeprecatedDirective)
39+
val directivesToString = defaultDirectives.joinToString("\n\n") { directive -> """
40+
#${directive.description}
41+
directive @${directive.name} on ${directive.validLocations().joinToString(" | ") { loc -> loc.name }}
42+
""".trimIndent()
43+
}
44+
schemaString += "\n" + directivesToString
45+
}
46+
return schemaString
47+
}

src/main/kotlin/com/expedia/graphql/generator/state/SchemaGeneratorState.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.expedia.graphql.generator.state
22

3+
import com.expedia.graphql.directives.DeprecatedDirective
4+
import graphql.Directives
35
import graphql.schema.GraphQLDirective
46
import graphql.schema.GraphQLType
57

@@ -9,4 +11,14 @@ internal class SchemaGeneratorState(supportedPackages: List<String>) {
911
val directives = mutableSetOf<GraphQLDirective>()
1012

1113
fun getValidAdditionalTypes(): List<GraphQLType> = additionalTypes.filter { cache.doesNotContainGraphQLType(it) }
14+
15+
init {
16+
// NOTE: @include and @defer query directives are added by graphql-java by default
17+
// adding them explicitly here to keep it consistent with missing deprecated directive
18+
directives.add(Directives.IncludeDirective)
19+
directives.add(Directives.SkipDirective)
20+
21+
// graphql-kotlin default directives
22+
directives.add(DeprecatedDirective)
23+
}
1224
}

src/main/kotlin/com/expedia/graphql/generator/types/EnumBuilder.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.expedia.graphql.generator.types
22

3+
import com.expedia.graphql.directives.deprecatedDirectiveWithReason
34
import com.expedia.graphql.generator.SchemaGenerator
45
import com.expedia.graphql.generator.TypeBuilder
56
import com.expedia.graphql.generator.extensions.getDeprecationReason
@@ -40,8 +41,11 @@ internal class EnumBuilder(generator: SchemaGenerator) : TypeBuilder(generator)
4041
}
4142

4243
valueBuilder.description(valueField.getGraphQLDescription())
43-
valueBuilder.deprecationReason(valueField.getDeprecationReason())
4444

45+
valueField.getDeprecationReason()?.let {
46+
valueBuilder.deprecationReason(it)
47+
valueBuilder.withDirective(deprecatedDirectiveWithReason(it))
48+
}
4549
return config.hooks.onRewireGraphQLType(valueBuilder.build()).safeCast()
4650
}
4751
}

src/main/kotlin/com/expedia/graphql/generator/types/FunctionBuilder.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.expedia.graphql.generator.types
22

3+
import com.expedia.graphql.directives.deprecatedDirectiveWithReason
34
import com.expedia.graphql.exceptions.InvalidInputFieldTypeException
45
import com.expedia.graphql.generator.SchemaGenerator
56
import com.expedia.graphql.generator.TypeBuilder
@@ -35,6 +36,7 @@ internal class FunctionBuilder(generator: SchemaGenerator) : TypeBuilder(generat
3536

3637
fn.getDeprecationReason()?.let {
3738
builder.deprecate(it)
39+
builder.withDirective(deprecatedDirectiveWithReason(it))
3840
}
3941

4042
generator.directives(fn).forEach {

src/main/kotlin/com/expedia/graphql/generator/types/PropertyBuilder.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.expedia.graphql.generator.types
22

3+
import com.expedia.graphql.directives.deprecatedDirectiveWithReason
34
import com.expedia.graphql.generator.SchemaGenerator
45
import com.expedia.graphql.generator.TypeBuilder
56
import com.expedia.graphql.generator.extensions.getPropertyDeprecationReason
@@ -22,7 +23,11 @@ internal class PropertyBuilder(generator: SchemaGenerator) : TypeBuilder(generat
2223
.description(prop.getPropertyDescription(parentClass))
2324
.name(prop.name)
2425
.type(propertyType)
25-
.deprecate(prop.getPropertyDeprecationReason(parentClass))
26+
27+
prop.getPropertyDeprecationReason(parentClass)?.let {
28+
fieldBuilder.deprecate(it)
29+
fieldBuilder.withDirective(deprecatedDirectiveWithReason(it))
30+
}
2631

2732
generator.directives(prop).forEach {
2833
fieldBuilder.withDirective(it)

0 commit comments

Comments
 (0)