Skip to content

Commit 4136952

Browse files
committed
Add support for deprecated directives in introspection
1 parent 0908fac commit 4136952

File tree

9 files changed

+132
-85
lines changed

9 files changed

+132
-85
lines changed

libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/GraphQLFeature.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,17 @@ enum class GraphQLFeature {
4848
*
4949
* Introspection: `__Type.isOneOf`.
5050
*/
51-
OneOf
51+
OneOf,
52+
53+
/**
54+
* Deprecated directives, introduced in [Add support for directives on directives](https://github.com/graphql/graphql-spec/pull/907/).
55+
*
56+
* Introspection:
57+
* - `__Directive.isDeprecated`
58+
* - `__Directive.deprecationReason`
59+
* - `__Schema.directives`'s `includeDeprecated` argument
60+
*/
61+
DeprecatedDirectives,
5262
}
5363

5464
internal fun PreIntrospectionQuery.Data.getFeatures(): Set<GraphQLFeature> {
@@ -77,6 +87,12 @@ internal fun PreIntrospectionQuery.Data.getFeatures(): Set<GraphQLFeature> {
7787
if (directiveFields.any { it.name == "isRepeatable" }) {
7888
add(RepeatableDirectives)
7989
}
90+
if (directiveFields.any { it.name == "isDeprecated" } &&
91+
directiveFields.any { it.name == "deprecationReason" } &&
92+
schema?.typeFields?.fields?.firstOrNull { it.name == "directives" }?.args?.any { it.name == "includeDeprecated" } == true
93+
) {
94+
add(GraphQLFeature.DeprecatedDirectives)
95+
}
8096
val directiveArgsIncludeDeprecated = directiveFields.firstOrNull { it.name == "args" }?.let { args ->
8197
args.args.any { it.name == "includeDeprecated" }
8298
} == true

libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo/tooling/SchemaHelper.kt

Lines changed: 86 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -108,82 +108,94 @@ internal object SchemaHelper {
108108
}
109109

110110
internal fun List<GQLDefinition>.reworkIntrospectionQuery(features: Set<GraphQLFeature>) =
111-
mapIf<_, GQLOperationDefinition>({ it.name == "IntrospectionQuery" }) {
112-
it.copy(
113-
selections = it.selections
114-
// Add __schema { description }
115-
.mapIf(SchemaDescription in features) { schemaField ->
116-
schemaField as GQLField
117-
schemaField.copy(
118-
selections = schemaField.selections + createField("description")
119-
)
120-
}
121-
// Add __schema { directives { isRepeatable } }
122-
.mapIf(RepeatableDirectives in features) { schemaField ->
123-
schemaField as GQLField
124-
schemaField.copy(
125-
selections = schemaField.selections.mapIf<_, GQLField>({ it.name == "directives" }) { directivesField ->
126-
directivesField.copy(selections = directivesField.selections + createField("isRepeatable"))
127-
}
128-
)
129-
}
130-
// Replace __schema { directives { args { ... } } } by __schema { directives { args(includeDeprecated: true) { ... } } }
131-
.mapIf(DeprecatedInputValues in features) { schemaField ->
132-
schemaField as GQLField
133-
schemaField.copy(
134-
selections = schemaField.selections.mapIf<_, GQLField>({ it.name == "directives" }) { directivesField ->
135-
directivesField.copy(
136-
selections = directivesField.selections.mapIf<_, GQLField>({ it.name == "args" }) { argsField ->
137-
argsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
138-
}
139-
)
140-
}
141-
)
142-
}
143-
)
144-
}
111+
mapIf<_, GQLOperationDefinition>({ it.name == "IntrospectionQuery" }) {
112+
it.copy(
113+
selections = it.selections
114+
// Add __schema { description }
115+
.mapIf(SchemaDescription in features) { schemaField ->
116+
schemaField as GQLField
117+
schemaField.copy(
118+
selections = schemaField.selections + createField("description")
119+
)
120+
}
121+
// Add __schema { directives { isRepeatable } }
122+
.mapIf(RepeatableDirectives in features) { schemaField ->
123+
schemaField as GQLField
124+
schemaField.copy(
125+
selections = schemaField.selections.mapIf<_, GQLField>({ it.name == "directives" }) { directivesField ->
126+
directivesField.copy(selections = directivesField.selections + createField("isRepeatable"))
127+
}
128+
)
129+
}
130+
// Add __schema { directives(includeDeprecated: true) { isDeprecated deprecationReason } }
131+
.mapIf(DeprecatedDirectives in features) { schemaField ->
132+
schemaField as GQLField
133+
schemaField.copy(
134+
selections = schemaField.selections.mapIf<_, GQLField>({ it.name == "directives" }) { directivesField ->
135+
directivesField.copy(
136+
arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))),
137+
selections = directivesField.selections + createField("isDeprecated") + createField("deprecationReason")
138+
)
139+
}
140+
)
141+
}
142+
// Replace __schema { directives { args { ... } } } by __schema { directives { args(includeDeprecated: true) { ... } } }
143+
.mapIf(DeprecatedInputValues in features) { schemaField ->
144+
schemaField as GQLField
145+
schemaField.copy(
146+
selections = schemaField.selections.mapIf<_, GQLField>({ it.name == "directives" }) { directivesField ->
147+
directivesField.copy(
148+
selections = directivesField.selections.mapIf<_, GQLField>({ it.name == "args" }) { argsField ->
149+
argsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
150+
}
151+
)
152+
}
153+
)
154+
}
155+
)
156+
}
145157

146158
internal fun List<GQLDefinition>.reworkFullTypeFragment(features: Set<GraphQLFeature>) =
147-
mapIf<_, GQLFragmentDefinition>({ it.name == "FullType" }) {
148-
it.copy(
149-
selections = it.selections
150-
// Add specifiedByUrl
151-
.letIf(SpecifiedBy in features) { fields ->
152-
fields + createField("specifiedByURL")
153-
}
154-
// Add isOneOf
155-
.letIf(OneOf in features) { fields ->
156-
fields + createField("isOneOf")
157-
}
158-
// Replace inputFields { ... } by inputFields(includeDeprecated: true) { ... }
159-
.mapIf<_, GQLField>({ DeprecatedInputValues in features && it.name == "inputFields" }) { inputFieldsField ->
160-
inputFieldsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
161-
}
162-
// Replace fields { args { ... } } by fields { args(includeDeprecated: true) { ... } }
163-
.mapIf<_, GQLField>({ DeprecatedInputValues in features && it.name == "fields" }) { fieldsField ->
164-
fieldsField.copy(
165-
selections = fieldsField.selections.mapIf<_, GQLField>({ it.name == "args" }) { argsField ->
166-
argsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
167-
}
168-
)
169-
}
170-
)
171-
}
159+
mapIf<_, GQLFragmentDefinition>({ it.name == "FullType" }) {
160+
it.copy(
161+
selections = it.selections
162+
// Add specifiedByUrl
163+
.letIf(SpecifiedBy in features) { fields ->
164+
fields + createField("specifiedByURL")
165+
}
166+
// Add isOneOf
167+
.letIf(OneOf in features) { fields ->
168+
fields + createField("isOneOf")
169+
}
170+
// Replace inputFields { ... } by inputFields(includeDeprecated: true) { ... }
171+
.mapIf<_, GQLField>({ DeprecatedInputValues in features && it.name == "inputFields" }) { inputFieldsField ->
172+
inputFieldsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
173+
}
174+
// Replace fields { args { ... } } by fields { args(includeDeprecated: true) { ... } }
175+
.mapIf<_, GQLField>({ DeprecatedInputValues in features && it.name == "fields" }) { fieldsField ->
176+
fieldsField.copy(
177+
selections = fieldsField.selections.mapIf<_, GQLField>({ it.name == "args" }) { argsField ->
178+
argsField.copy(arguments = listOf(GQLArgument(name = "includeDeprecated", value = GQLBooleanValue(value = true))))
179+
}
180+
)
181+
}
182+
)
183+
}
172184

173185
internal fun List<GQLDefinition>.reworkInputValueFragment(features: Set<GraphQLFeature>) =
174-
mapIf<_, GQLFragmentDefinition>({ it.name == "InputValue" }) {
175-
it.copy(
176-
selections = it.selections
177-
// Add isDeprecated
178-
.letIf(DeprecatedInputValues in features) { fields ->
179-
fields + createField("isDeprecated")
180-
}
181-
// Add deprecationReason
182-
.letIf(DeprecatedInputValues in features) { fields ->
183-
fields + createField("deprecationReason")
184-
}
185-
)
186-
}
186+
mapIf<_, GQLFragmentDefinition>({ it.name == "InputValue" }) {
187+
it.copy(
188+
selections = it.selections
189+
// Add isDeprecated
190+
.letIf(DeprecatedInputValues in features) { fields ->
191+
fields + createField("isDeprecated")
192+
}
193+
// Add deprecationReason
194+
.letIf(DeprecatedInputValues in features) { fields ->
195+
fields + createField("deprecationReason")
196+
}
197+
)
198+
}
187199

188200
private inline fun <T> T.letIf(condition: Boolean, block: (T) -> T): T = if (condition) block(this) else this
189201

@@ -194,7 +206,8 @@ internal object SchemaHelper {
194206
block: (E) -> E,
195207
): List<T> = map { if (it is E && condition(it)) block(it) else it }
196208

197-
private fun createField(name: String) = GQLField(alias = null, name = name, arguments = emptyList(), directives = emptyList(), selections = emptyList())
209+
private fun createField(name: String) =
210+
GQLField(alias = null, name = name, arguments = emptyList(), directives = emptyList(), selections = emptyList())
198211

199212
private fun OkHttpClient.Builder.applyInsecureTrustManager() = apply {
200213
val insecureTrustManager = InsecureTrustManager()

libraries/apollo-tooling/src/main/resources/base-introspection.graphql

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@ query IntrospectionQuery {
1212
types {
1313
...FullType
1414
}
15-
directives {
15+
directives { # directives(includeDeprecated: true) - introduced in https://github.com/graphql/graphql-spec/pull/907
1616
name
1717
description
1818
locations
1919
args { # args(includeDeprecated: true) - introduced in https://github.com/graphql/graphql-spec/pull/805/
2020
...InputValue
2121
}
2222
# isRepeatable - introduced in https://spec.graphql.org/October2021/
23+
# isDeprecated - introduced in https://github.com/graphql/graphql-spec/pull/907
24+
# deprecationReason - introduced in https://github.com/graphql/graphql-spec/pull/907
2325
}
2426
}
2527
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"operationName":"IntrospectionQuery","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives(includeDeprecated: true) {\n name\n description\n locations\n args(includeDeprecated: true) {\n ...InputValue\n }\n isRepeatable\n isDeprecated\n deprecationReason\n }\n description\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args(includeDeprecated: true) {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields(includeDeprecated: true) {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n specifiedByURL\n isOneOf\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n isDeprecated\n deprecationReason\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}\n"}

libraries/apollo-tooling/src/test/fixtures/introspection-request-september2025-oneOf.json

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"operationName":"IntrospectionQuery","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args(includeDeprecated: true) {\n ...InputValue\n }\n isRepeatable\n }\n description\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args(includeDeprecated: true) {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields(includeDeprecated: true) {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n specifiedByURL\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n isDeprecated\n deprecationReason\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}\n"}
1+
{"operationName":"IntrospectionQuery","query":"query IntrospectionQuery {\n __schema {\n queryType {\n name\n }\n mutationType {\n name\n }\n subscriptionType {\n name\n }\n types {\n ...FullType\n }\n directives {\n name\n description\n locations\n args(includeDeprecated: true) {\n ...InputValue\n }\n isRepeatable\n }\n description\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args(includeDeprecated: true) {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields(includeDeprecated: true) {\n ...InputValue\n }\n interfaces {\n ...TypeRef\n }\n enumValues(includeDeprecated: true) {\n name\n description\n isDeprecated\n deprecationReason\n }\n possibleTypes {\n ...TypeRef\n }\n specifiedByURL\n isOneOf\n}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\n isDeprecated\n deprecationReason\n}\n\nfragment TypeRef on __Type {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n ofType {\n kind\n name\n }\n }\n }\n }\n }\n }\n }\n}\n"}

libraries/apollo-tooling/src/test/fixtures/pre-introspection-response-september2025-oneOf.json renamed to libraries/apollo-tooling/src/test/fixtures/pre-introspection-response-september2025-deprecatedDirectives.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@
2525
},
2626
{
2727
"name": "directives",
28-
"args": []
28+
"args": [
29+
{
30+
"name": "includeDeprecated"
31+
}
32+
]
2933
},
3034
{
3135
"name": "description",
@@ -122,6 +126,14 @@
122126
{
123127
"name": "isRepeatable",
124128
"args": []
129+
},
130+
{
131+
"name": "isDeprecated",
132+
"args": []
133+
},
134+
{
135+
"name": "deprecationReason",
136+
"args": []
125137
}
126138
]
127139
},

libraries/apollo-tooling/src/test/fixtures/pre-introspection-response-september2025.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@
8888
{
8989
"name": "specifiedByURL",
9090
"args": []
91+
},
92+
{
93+
"name": "isOneOf",
94+
"args": []
9195
}
9296
]
9397
},

0 commit comments

Comments
 (0)