Skip to content

Commit 10910a1

Browse files
authored
Add a fail-safe mode to disable 2-step introspection and use minimal introspection query (#6360)
* Add an introspection fail-safe mode to disable 2-step introspection, and use minimal introspection query * Fallback fail-safe mode automatically
1 parent 027cacb commit 10910a1

File tree

6 files changed

+117
-28
lines changed

6 files changed

+117
-28
lines changed

libraries/apollo-gradle-plugin/src/test-java17/kotlin/test/DownloadSchemaTests.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ class DownloadSchemaTests {
130130
""".trimIndent()
131131

132132
private val schemaString2 = schemaString1.replace("foo", "bar")
133+
private val schemaStringError = """
134+
{
135+
"data": null,
136+
"errors": [
137+
{
138+
"message": "unexpected field specifiedByURL type on __Type"
139+
}
140+
]
141+
}
142+
""".trimIndent()
133143

134144
private val apolloConfiguration = """
135145
apollo {
@@ -256,6 +266,22 @@ class DownloadSchemaTests {
256266
}
257267
}
258268

269+
@Test
270+
fun `manually downloading a schema when 2-step mode fails is working`() {
271+
TestUtils.withSimpleProject(apolloConfiguration = "") { dir ->
272+
mockServer.enqueue(MockResponse().setBody(preIntrospectionResponse))
273+
mockServer.enqueue(MockResponse().setBody(schemaStringError))
274+
mockServer.enqueue(MockResponse().setBody(schemaString1))
275+
val schema = File("build/testProject/schema.json")
276+
TestUtils.executeGradle(dir, "downloadApolloSchema",
277+
"--schema=${schema.absolutePath}",
278+
"--endpoint=${mockServer.url("/")}",
279+
)
280+
281+
Assert.assertEquals(schemaString1, schema.readText())
282+
}
283+
}
284+
259285
@Test
260286
fun `download a schema from a real server is working`() {
261287
val executableSchema = ExecutableSchema.Builder()
@@ -274,4 +300,4 @@ class DownloadSchemaTests {
274300

275301
server.stop()
276302
}
277-
}
303+
}

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

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,31 @@ object SchemaDownloader {
5959

6060
when {
6161
endpoint != null -> {
62-
introspectionDataJson = downloadIntrospection(
63-
endpoint = endpoint,
64-
headers = headers,
65-
insecure = insecure,
66-
)
67-
introspectionSchema = try {
68-
introspectionDataJson.toIntrospectionSchema()
62+
try {
63+
introspectionDataJson = downloadIntrospection(
64+
endpoint = endpoint,
65+
headers = headers,
66+
insecure = insecure,
67+
failSafe = false,
68+
)
69+
introspectionSchema = try {
70+
introspectionDataJson.toIntrospectionSchema()
71+
} catch (e: Exception) {
72+
throw Exception("Introspection response from $endpoint can not be parsed", e)
73+
}
6974
} catch (e: Exception) {
70-
throw Exception("Introspection response from $endpoint can not be parsed", e)
75+
// 2-step introspection didn't work: fallback to no pre-introspection query and minimal introspection query
76+
introspectionDataJson = downloadIntrospection(
77+
endpoint = endpoint,
78+
headers = headers,
79+
insecure = insecure,
80+
failSafe = true,
81+
)
82+
introspectionSchema = try {
83+
introspectionDataJson.toIntrospectionSchema()
84+
} catch (e: Exception) {
85+
throw Exception("Introspection response from $endpoint can not be parsed", e)
86+
}
7187
}
7288
}
7389

@@ -124,7 +140,8 @@ object SchemaDownloader {
124140
*/
125141
@ApolloExperimental
126142
fun getIntrospectionQuery(features: Set<GraphQLFeature>): String {
127-
val baseIntrospectionSource = SchemaHelper::class.java.classLoader!!.getResourceAsStream("base-introspection.graphql")!!.source().buffer()
143+
val baseIntrospectionSource =
144+
SchemaHelper::class.java.classLoader!!.getResourceAsStream("base-introspection.graphql")!!.source().buffer()
128145
val baseIntrospectionGql: GQLDocument = baseIntrospectionSource.parseAsGQLDocument().value!!
129146
val introspectionGql: GQLDocument = baseIntrospectionGql.copy(
130147
definitions = baseIntrospectionGql.definitions
@@ -135,17 +152,29 @@ object SchemaDownloader {
135152
return introspectionGql.toUtf8()
136153
}
137154

155+
@Deprecated(level = DeprecationLevel.HIDDEN, message = "Kept for binary compatibility")
156+
fun downloadIntrospection(
157+
endpoint: String,
158+
headers: Map<String, String>,
159+
insecure: Boolean,
160+
): String = downloadIntrospection(endpoint, headers, insecure, failSafe = false)
161+
138162
fun downloadIntrospection(
139163
endpoint: String,
140164
headers: Map<String, String>,
141165
insecure: Boolean,
166+
failSafe: Boolean = false,
142167
): String {
143-
val preIntrospectionData: PreIntrospectionQuery.Data = SchemaHelper.executePreIntrospectionQuery(
144-
endpoint = endpoint,
145-
headers = headers,
146-
insecure = insecure,
147-
)
148-
val features = preIntrospectionData.getFeatures()
168+
val features = if (failSafe) {
169+
emptySet()
170+
} else {
171+
val preIntrospectionData: PreIntrospectionQuery.Data = SchemaHelper.executePreIntrospectionQuery(
172+
endpoint = endpoint,
173+
headers = headers,
174+
insecure = insecure,
175+
)
176+
preIntrospectionData.getFeatures()
177+
}
149178
val introspectionQuery = getIntrospectionQuery(features)
150179
return SchemaHelper.executeIntrospectionQuery(
151180
introspectionQuery = introspectionQuery,
@@ -193,6 +222,6 @@ object SchemaDownloader {
193222
SchemaHelper.client.dispatcher.executorService.shutdown()
194223
SchemaHelper.client.connectionPool.evictAll()
195224
}
196-
225+
197226
inline fun <reified T> Any?.cast() = this as? T
198227
}
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 {\n name\n description\n locations\n args {\n ...InputValue\n }\n }\n }\n}\n\nfragment FullType on __Type {\n kind\n name\n description\n fields(includeDeprecated: true) {\n name\n description\n args {\n ...InputValue\n }\n type {\n ...TypeRef\n }\n isDeprecated\n deprecationReason\n }\n inputFields {\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}\n\nfragment InputValue on __InputValue {\n name\n description\n type {\n ...TypeRef\n }\n defaultValue\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"}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"data": null,
3+
"errors": [
4+
{
5+
"message": "unexpected field specifiedByURL type on __Type"
6+
}
7+
]
8+
}

libraries/apollo-tooling/src/test/kotlin/com/apollographql/apollo/tooling/SchemaDownloaderTests.kt

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
package com.apollographql.apollo.tooling
44

5-
import com.apollographql.mockserver.MockServer
6-
import com.apollographql.mockserver.enqueueString
75
import com.apollographql.apollo.testing.internal.runTest
86
import com.apollographql.apollo.testing.pathToUtf8
7+
import com.apollographql.mockserver.MockServer
8+
import com.apollographql.mockserver.enqueueString
99
import org.junit.Assert.assertEquals
1010
import org.junit.Test
1111
import java.io.File
@@ -22,7 +22,10 @@ private val introspectionRequestDraft = pathToUtf8("apollo-tooling/src/test/fixt
2222
private val preIntrospectionResponseOneOf = pathToUtf8("apollo-tooling/src/test/fixtures/pre-introspection-response-oneOf.json")
2323
private val introspectionRequestOneOf = pathToUtf8("apollo-tooling/src/test/fixtures/introspection-request-oneOf.json")
2424

25-
private val introspectionResponse = pathToUtf8("apollo-tooling/src/test/fixtures/introspection-response.json")
25+
private val introspectionRequestFailSafe = pathToUtf8("apollo-tooling/src/test/fixtures/introspection-request-failSafe.json")
26+
27+
private val introspectionResponseSuccess = pathToUtf8("apollo-tooling/src/test/fixtures/introspection-response-success.json")
28+
private val introspectionResponseFail = pathToUtf8("apollo-tooling/src/test/fixtures/introspection-response-fail.json")
2629

2730
class SchemaDownloaderTests {
2831
private lateinit var mockServer: MockServer
@@ -41,7 +44,7 @@ class SchemaDownloaderTests {
4144
@Test
4245
fun `schema is downloaded correctly when server supports June 2018 spec`() = runTest(before = { setUp() }, after = { tearDown() }) {
4346
mockServer.enqueueString(preIntrospectionResponseJune2018)
44-
mockServer.enqueueString(introspectionResponse)
47+
mockServer.enqueueString(introspectionResponseSuccess)
4548

4649
SchemaDownloader.download(
4750
endpoint = mockServer.url(),
@@ -54,13 +57,13 @@ class SchemaDownloaderTests {
5457
mockServer.takeRequest()
5558
val introspectionRequest = mockServer.takeRequest().body.utf8()
5659
assertEquals(introspectionRequestJune2018, introspectionRequest)
57-
assertEquals(introspectionResponse, tempFile.readText())
60+
assertEquals(introspectionResponseSuccess, tempFile.readText())
5861
}
5962

6063
@Test
6164
fun `schema is downloaded correctly when server supports October 2021 spec`() = runTest(before = { setUp() }, after = { tearDown() }) {
6265
mockServer.enqueueString(preIntrospectionResponseOctober2021)
63-
mockServer.enqueueString(introspectionResponse)
66+
mockServer.enqueueString(introspectionResponseSuccess)
6467

6568
SchemaDownloader.download(
6669
endpoint = mockServer.url(),
@@ -73,13 +76,13 @@ class SchemaDownloaderTests {
7376
mockServer.takeRequest()
7477
val introspectionRequest = mockServer.takeRequest().body.utf8()
7578
assertEquals(introspectionRequestOctober2021, introspectionRequest)
76-
assertEquals(introspectionResponse, tempFile.readText())
79+
assertEquals(introspectionResponseSuccess, tempFile.readText())
7780
}
7881

7982
@Test
8083
fun `schema is downloaded correctly when server supports Draft spec as of 2023-11-15`() = runTest(before = { setUp() }, after = { tearDown() }) {
8184
mockServer.enqueueString(preIntrospectionResponseDraft)
82-
mockServer.enqueueString(introspectionResponse)
85+
mockServer.enqueueString(introspectionResponseSuccess)
8386

8487
SchemaDownloader.download(
8588
endpoint = mockServer.url(),
@@ -92,13 +95,13 @@ class SchemaDownloaderTests {
9295
mockServer.takeRequest()
9396
val introspectionRequest = mockServer.takeRequest().body.utf8()
9497
assertEquals(introspectionRequestDraft, introspectionRequest)
95-
assertEquals(introspectionResponse, tempFile.readText())
98+
assertEquals(introspectionResponseSuccess, tempFile.readText())
9699
}
97100

98101
@Test
99102
fun `schema is downloaded correctly when server supports oneOf`() = runTest(before = { setUp() }, after = { tearDown() }) {
100103
mockServer.enqueueString(preIntrospectionResponseOneOf)
101-
mockServer.enqueueString(introspectionResponse)
104+
mockServer.enqueueString(introspectionResponseSuccess)
102105

103106
SchemaDownloader.download(
104107
endpoint = mockServer.url(),
@@ -111,6 +114,28 @@ class SchemaDownloaderTests {
111114
mockServer.takeRequest()
112115
val introspectionRequest = mockServer.takeRequest().body.utf8()
113116
assertEquals(introspectionRequestOneOf, introspectionRequest)
114-
assertEquals(introspectionResponse, tempFile.readText())
117+
assertEquals(introspectionResponseSuccess, tempFile.readText())
118+
}
119+
120+
@Test
121+
fun `schema is downloaded correctly in fail-safe mode after 2-step fails`() = runTest(before = { setUp() }, after = { tearDown() }) {
122+
mockServer.enqueueString(preIntrospectionResponseDraft)
123+
mockServer.enqueueString(introspectionResponseFail)
124+
mockServer.enqueueString(introspectionResponseSuccess)
125+
126+
SchemaDownloader.download(
127+
endpoint = mockServer.url(),
128+
graph = null,
129+
key = null,
130+
graphVariant = "",
131+
schema = tempFile,
132+
)
133+
134+
mockServer.takeRequest()
135+
mockServer.takeRequest()
136+
val introspectionRequest = mockServer.takeRequest().body.utf8()
137+
assertEquals(introspectionRequestFailSafe, introspectionRequest)
138+
assertEquals(introspectionResponseSuccess, tempFile.readText())
115139
}
140+
116141
}

0 commit comments

Comments
 (0)