Skip to content

Commit 898816b

Browse files
committed
introduce JsonReader.toApolloResponse
1 parent c435d6e commit 898816b

File tree

18 files changed

+162
-115
lines changed

18 files changed

+162
-115
lines changed

libraries/apollo-api/api/apollo-api.api

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,6 +786,11 @@ public final class com/apollographql/apollo3/api/Operations {
786786
public static final fun parseJsonResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/api/ApolloResponse;
787787
public static final fun parseJsonResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;)Lcom/apollographql/apollo3/api/ApolloResponse;
788788
public static synthetic fun parseJsonResponse$default (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;ILjava/lang/Object;)Lcom/apollographql/apollo3/api/ApolloResponse;
789+
public static final fun parseResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;)Lcom/apollographql/apollo3/api/ApolloResponse;
790+
public static final fun parseResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Ljava/util/UUID;)Lcom/apollographql/apollo3/api/ApolloResponse;
791+
public static final fun parseResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Ljava/util/UUID;Lcom/apollographql/apollo3/api/CustomScalarAdapters;)Lcom/apollographql/apollo3/api/ApolloResponse;
792+
public static final fun parseResponse (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Ljava/util/UUID;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;)Lcom/apollographql/apollo3/api/ApolloResponse;
793+
public static synthetic fun parseResponse$default (Lcom/apollographql/apollo3/api/Operation;Lcom/apollographql/apollo3/api/json/JsonReader;Ljava/util/UUID;Lcom/apollographql/apollo3/api/CustomScalarAdapters;Ljava/util/Set;ILjava/lang/Object;)Lcom/apollographql/apollo3/api/ApolloResponse;
789794
}
790795

791796
public abstract class com/apollographql/apollo3/api/Optional {
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,14 @@
11
@file:JvmName("Assertions")
22
package com.apollographql.apollo3.api
33

4-
import com.apollographql.apollo3.exception.ApolloException
54
import com.apollographql.apollo3.exception.DefaultApolloException
6-
import kotlin.contracts.ExperimentalContracts
7-
import kotlin.contracts.contract
85
import kotlin.jvm.JvmName
96

107
/**
118
* Helper function for the JavaCodegen
129
*/
1310
fun checkFieldNotMissing(value: Any?, name: String) {
1411
if (value == null) {
15-
throw DefaultApolloException("Field $name is missing")
12+
throw DefaultApolloException("Field '$name' is missing")
1613
}
1714
}

libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/api/Operations.kt

Lines changed: 68 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import com.apollographql.apollo3.annotations.ApolloExperimental
66
import com.apollographql.apollo3.api.internal.ResponseParser
77
import com.apollographql.apollo3.api.json.JsonReader
88
import com.apollographql.apollo3.api.json.JsonWriter
9-
import com.apollographql.apollo3.api.json.jsonReader
109
import com.apollographql.apollo3.api.json.writeObject
10+
import com.apollographql.apollo3.exception.ApolloException
11+
import com.apollographql.apollo3.exception.ApolloParseException
1112
import com.apollographql.apollo3.exception.JsonDataException
1213
import com.apollographql.apollo3.exception.JsonEncodingException
13-
import okio.Buffer
14+
import com.benasher44.uuid.Uuid
15+
import com.benasher44.uuid.uuid4
1416
import okio.IOException
1517
import okio.use
1618
import kotlin.jvm.JvmName
@@ -46,7 +48,8 @@ fun <D : Operation.Data> Operation<D>.composeJsonRequest(
4648
}
4749

4850
/**
49-
* Reads a GraphQL Json response like below to a [ApolloResponse]
51+
* Reads a GraphQL Json response to a [ApolloResponse]. GraphQL Json responses look like so:
52+
*
5053
* ```
5154
* {
5255
* "data": ...
@@ -62,26 +65,65 @@ fun <D : Operation.Data> Operation<D>.composeJsonRequest(
6265
* @throws JsonDataException if the data is not of the expected type
6366
*/
6467
@JvmOverloads
68+
@Deprecated("Use parseResponse or jsonReader.toApolloResponse() instead", ReplaceWith("parseResponse"))
6569
fun <D : Operation.Data> Operation<D>.parseJsonResponse(
6670
jsonReader: JsonReader,
6771
customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty,
6872
deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>? = null,
6973
): ApolloResponse<D> {
70-
return ResponseParser.parse(
71-
jsonReader,
72-
this,
73-
customScalarAdapters,
74-
deferredFragmentIdentifiers,
75-
)
74+
return jsonReader.use {
75+
ResponseParser.parse(
76+
it,
77+
this,
78+
null,
79+
customScalarAdapters,
80+
deferredFragmentIdentifiers,
81+
)
82+
}
7683
}
7784

85+
/**
86+
* Reads a GraphQL Json response like below to a [ApolloResponse]. GraphQL Json responses look like so:
87+
*
88+
* ```
89+
* {
90+
* "data": ...
91+
* "errors": ...
92+
* "extensions": ...
93+
* }
94+
* ```
95+
*
96+
* By default, this method does not close the [jsonReader]
97+
*/
7898
@JvmOverloads
79-
@ApolloExperimental
80-
fun <D : Operation.Data> Operation<D>.parseJsonResponse(
81-
json: String,
99+
fun <D : Operation.Data> Operation<D>.parseResponse(
100+
jsonReader: JsonReader,
101+
requestUuid: Uuid? = null,
82102
customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty,
103+
deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>? = null,
83104
): ApolloResponse<D> {
84-
return parseJsonResponse(Buffer().writeUtf8(json).jsonReader(), customScalarAdapters)
105+
return try {
106+
ResponseParser.parse(
107+
jsonReader,
108+
this,
109+
requestUuid,
110+
customScalarAdapters,
111+
deferredFragmentIdentifiers,
112+
)
113+
} catch (throwable: Throwable) {
114+
val apolloException = if (throwable is ApolloException) {
115+
throwable
116+
} else {
117+
// This happens for null pointer exceptions on missing fields
118+
ApolloParseException(
119+
message = "Failed to parse GraphQL http network response",
120+
cause = throwable
121+
)
122+
}
123+
return ApolloResponse.Builder(requestUuid = requestUuid ?: uuid4(), operation = this, exception = apolloException)
124+
.isLast(true)
125+
.build()
126+
}
85127
}
86128

87129
/**
@@ -103,3 +145,16 @@ fun <D : Operation.Data> Operation<D>.composeJsonResponse(
103145
}
104146
}
105147

148+
149+
@ApolloExperimental
150+
fun <D : Operation.Data> JsonReader.toApolloResponse(
151+
operation: Operation<D>,
152+
requestUuid: Uuid? = null,
153+
customScalarAdapters: CustomScalarAdapters = CustomScalarAdapters.Empty,
154+
deferredFragmentIdentifiers: Set<DeferredFragmentIdentifier>? = null,
155+
): ApolloResponse<D> {
156+
return use {
157+
operation.parseResponse(it, requestUuid, customScalarAdapters, deferredFragmentIdentifiers)
158+
}
159+
}
160+

libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/api/internal/ResponseParser.kt

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,9 @@ import com.apollographql.apollo3.api.falseVariables
99
import com.apollographql.apollo3.api.json.JsonReader
1010
import com.apollographql.apollo3.api.json.MapJsonReader
1111
import com.apollographql.apollo3.api.json.readAny
12-
import com.apollographql.apollo3.api.nullable
1312
import com.apollographql.apollo3.api.parseData
13+
import com.benasher44.uuid.Uuid
1414
import com.benasher44.uuid.uuid4
15-
import okio.use
1615

1716
/**
1817
* [ResponseParser] parses network responses, including data, errors and extensions from a [JsonReader]
@@ -21,33 +20,31 @@ internal object ResponseParser {
2120
fun <D : Operation.Data> parse(
2221
jsonReader: JsonReader,
2322
operation: Operation<D>,
23+
requestUuid: Uuid?,
2424
customScalarAdapters: CustomScalarAdapters,
2525
deferredFragmentIds: Set<DeferredFragmentIdentifier>?,
2626
): ApolloResponse<D> {
27-
@Suppress("NAME_SHADOWING")
28-
return jsonReader.use { jsonReader ->
29-
jsonReader.beginObject()
30-
31-
var data: D? = null
32-
var errors: List<Error>? = null
33-
var extensions: Map<String, Any?>? = null
34-
while (jsonReader.hasNext()) {
35-
@Suppress("UNCHECKED_CAST")
36-
when (jsonReader.nextName()) {
37-
"data" -> {
38-
val falseVariables = operation.falseVariables(customScalarAdapters)
39-
data = operation.parseData(jsonReader, customScalarAdapters, falseVariables, deferredFragmentIds)
40-
}
41-
"errors" -> errors = jsonReader.readErrors()
42-
"extensions" -> extensions = jsonReader.readAny() as? Map<String, Any?>
43-
else -> jsonReader.skipValue()
27+
jsonReader.beginObject()
28+
29+
var data: D? = null
30+
var errors: List<Error>? = null
31+
var extensions: Map<String, Any?>? = null
32+
while (jsonReader.hasNext()) {
33+
@Suppress("UNCHECKED_CAST")
34+
when (jsonReader.nextName()) {
35+
"data" -> {
36+
val falseVariables = operation.falseVariables(customScalarAdapters)
37+
data = operation.parseData(jsonReader, customScalarAdapters, falseVariables, deferredFragmentIds)
4438
}
39+
"errors" -> errors = jsonReader.readErrors()
40+
"extensions" -> extensions = jsonReader.readAny() as? Map<String, Any?>
41+
else -> jsonReader.skipValue()
4542
}
43+
}
4644

47-
jsonReader.endObject()
45+
jsonReader.endObject()
4846

49-
ApolloResponse.Builder(requestUuid = uuid4(), operation = operation, data = data, errors = errors, extensions = extensions).build()
50-
}
47+
return ApolloResponse.Builder(requestUuid = requestUuid ?: uuid4(), operation = operation, data = data, errors = errors, extensions = extensions).build()
5148
}
5249

5350
fun parseError(

libraries/apollo-runtime-java/src/main/java/com/apollographql/apollo3/runtime/java/network/http/HttpNetworkTransport.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,9 @@ public <D extends Operation.Data> void execute(@NotNull ApolloRequest<D> request
6262
}
6363
} else {
6464
BufferedSourceJsonReader jsonReader = new BufferedSourceJsonReader(response.getBody());
65-
try {
66-
CustomScalarAdapters customScalarAdapters = request.getExecutionContext().get(CustomScalarAdapters.Key);
67-
ApolloResponse<D> apolloResponse = Operations.parseJsonResponse(request.getOperation(), jsonReader, customScalarAdapters);
68-
callback.onResponse(apolloResponse);
69-
} catch (Exception e) {
70-
callback.onResponse(getExceptionResponse(request, new ApolloParseException("Cannot parse response", e)));
71-
}
65+
CustomScalarAdapters customScalarAdapters = request.getExecutionContext().get(CustomScalarAdapters.Key);
66+
ApolloResponse<D> apolloResponse = Operations.toApolloResponse(jsonReader, request.getOperation(), request.getRequestUuid(), customScalarAdapters, null);
67+
callback.onResponse(apolloResponse);
7268
}
7369
}
7470

libraries/apollo-runtime-java/src/main/java/com/apollographql/apollo3/runtime/java/network/ws/WebSocketNetworkTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ private void disposeSubscription(String id) {
141141
CustomScalarAdapters customScalarAdapters = request.getExecutionContext().get(CustomScalarAdapters.Key);
142142
JsonReader jsonReader = new MapJsonReader(payload);
143143
//noinspection rawtypes
144-
ApolloResponse apolloResponse = Operations.parseJsonResponse(request.getOperation(), jsonReader, customScalarAdapters).newBuilder().requestUuid(request.getRequestUuid()).build();
144+
ApolloResponse apolloResponse = Operations.toApolloResponse(jsonReader, request.getOperation(), request.getRequestUuid(), customScalarAdapters, null);
145145
//noinspection unchecked
146146
subscriptionInfo.callback.onResponse(apolloResponse);
147147
}

libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo3/network/http/HttpNetworkTransport.kt

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import com.apollographql.apollo3.api.http.HttpResponse
1313
import com.apollographql.apollo3.api.json.JsonReader
1414
import com.apollographql.apollo3.api.json.jsonReader
1515
import com.apollographql.apollo3.api.json.readAny
16-
import com.apollographql.apollo3.api.parseJsonResponse
16+
import com.apollographql.apollo3.api.toApolloResponse
1717
import com.apollographql.apollo3.exception.ApolloException
1818
import com.apollographql.apollo3.exception.ApolloHttpException
1919
import com.apollographql.apollo3.exception.ApolloParseException
@@ -26,7 +26,6 @@ import com.apollographql.apollo3.network.NetworkTransport
2626
import com.benasher44.uuid.Uuid
2727
import com.benasher44.uuid.uuid4
2828
import kotlinx.coroutines.flow.Flow
29-
import kotlinx.coroutines.flow.catch
3029
import kotlinx.coroutines.flow.emitAll
3130
import kotlinx.coroutines.flow.flow
3231
import kotlinx.coroutines.flow.flowOf
@@ -138,15 +137,11 @@ private constructor(
138137
customScalarAdapters: CustomScalarAdapters,
139138
httpResponse: HttpResponse,
140139
): Flow<ApolloResponse<D>> {
141-
val response = try {
142-
operation.parseJsonResponse(
143-
jsonReader = httpResponse.body!!.jsonReader(),
144-
customScalarAdapters = customScalarAdapters,
145-
deferredFragmentIdentifiers = null,
146-
)
147-
} catch (e: Exception) {
148-
errorResponse(operation, e)
149-
}
140+
val response = httpResponse.body!!.jsonReader().toApolloResponse(
141+
operation,
142+
customScalarAdapters = customScalarAdapters,
143+
deferredFragmentIdentifiers = null,
144+
)
150145

151146
return flowOf(response.newBuilder().isLast(true).build())
152147
}
@@ -194,11 +189,10 @@ private constructor(
194189
reader.beginObject()
195190
reader.nextName()
196191

197-
// TODO: make parseJsonResponse not close the jsonReader
198-
operation.parseJsonResponse(
199-
jsonReader = reader,
192+
reader.toApolloResponse(
193+
operation = operation,
200194
customScalarAdapters = customScalarAdapters,
201-
deferredFragmentIdentifiers = null,
195+
deferredFragmentIdentifiers = null
202196
)
203197
}
204198

@@ -220,15 +214,13 @@ private constructor(
220214
if (jsonMerger!!.isEmptyPayload) {
221215
null
222216
} else {
223-
operation.parseJsonResponse(
224-
jsonReader = merged.jsonReader(),
217+
merged.jsonReader().toApolloResponse(
218+
operation = operation,
225219
customScalarAdapters = customScalarAdapters,
226-
deferredFragmentIdentifiers = deferredFragmentIds,
220+
deferredFragmentIdentifiers = deferredFragmentIds
227221
).newBuilder().isLast(isLast).build()
228222
}
229223
}
230-
}.catch {
231-
emit(errorResponse(operation, it))
232224
}
233225
}
234226

libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo3/network/ws/WebSocketNetworkTransport.kt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import com.apollographql.apollo3.api.CustomScalarAdapters
66
import com.apollographql.apollo3.api.Operation
77
import com.apollographql.apollo3.api.http.HttpHeader
88
import com.apollographql.apollo3.api.json.jsonReader
9-
import com.apollographql.apollo3.api.parseJsonResponse
9+
import com.apollographql.apollo3.api.toApolloResponse
1010
import com.apollographql.apollo3.exception.ApolloException
1111
import com.apollographql.apollo3.exception.ApolloNetworkException
1212
import com.apollographql.apollo3.exception.SubscriptionOperationException
@@ -311,11 +311,13 @@ private constructor(
311311
} else {
312312
responsePayload to null
313313
}
314-
val apolloResponse: ApolloResponse<D> = request.operation
315-
.parseJsonResponse(jsonReader = payload.jsonReader(), customScalarAdapters = requestCustomScalarAdapters, deferredFragmentIdentifiers = mergedFragmentIds)
316-
.newBuilder()
317-
.requestUuid(request.requestUuid)
318-
.build()
314+
val apolloResponse: ApolloResponse<D> = payload.jsonReader().toApolloResponse(
315+
operation = request.operation,
316+
requestUuid = request.requestUuid,
317+
customScalarAdapters = requestCustomScalarAdapters,
318+
deferredFragmentIdentifiers = mergedFragmentIds
319+
)
320+
319321
if (!deferredJsonMerger.hasNext) {
320322
// Last deferred payload: reset the deferredJsonMerger for potential subsequent responses
321323
deferredJsonMerger.reset()

tests/include-skip-operation-based/src/test/kotlin/IncludeTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
12
import com.apollographql.apollo3.api.ApolloResponse
23
import com.apollographql.apollo3.api.CustomScalarAdapters
34
import com.apollographql.apollo3.api.GlobalBuilder
45
import com.apollographql.apollo3.api.Operation
56
import com.apollographql.apollo3.api.Optional
67
import com.apollographql.apollo3.api.json.MapJsonReader
7-
import com.apollographql.apollo3.api.parseJsonResponse
8+
import com.apollographql.apollo3.api.toApolloResponse
89
import com.apollographql.apollo3.cache.normalized.api.TypePolicyCacheKeyGenerator
910
import com.apollographql.apollo3.cache.normalized.api.normalize
1011
import com.example.GetCatIncludeFalseQuery
@@ -27,7 +28,7 @@ import kotlin.test.assertNull
2728
class IncludeTest {
2829

2930
private fun <D : Operation.Data> Operation<D>.parseData(data: Map<String, Any?>): ApolloResponse<D> {
30-
return parseJsonResponse(MapJsonReader(mapOf("data" to data)))
31+
return MapJsonReader(mapOf("data" to data)).toApolloResponse(this)
3132
}
3233

3334
@Test

tests/integration-tests/src/kotlinCodegenTest/kotlin/test/NormalizerTest.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import IdCacheKeyGenerator
44
import com.apollographql.apollo3.api.CustomScalarAdapters
55
import com.apollographql.apollo3.api.Operation
66
import com.apollographql.apollo3.api.parseJsonResponse
7+
import com.apollographql.apollo3.api.toApolloResponse
78
import com.apollographql.apollo3.cache.normalized.api.CacheHeaders
89
import com.apollographql.apollo3.cache.normalized.api.CacheKey
910
import com.apollographql.apollo3.cache.normalized.api.MemoryCacheFactory
@@ -248,7 +249,7 @@ class NormalizerTest {
248249

249250
companion object {
250251
internal fun <D : Operation.Data> records(operation: Operation<D>, name: String): Map<String, Record> {
251-
val response = operation.parseJsonResponse(testFixtureToJsonReader(name))
252+
val response = testFixtureToJsonReader(name).toApolloResponse(operation)
252253
return operation.normalize(data = response.data!!, CustomScalarAdapters.Empty, cacheKeyGenerator = IdCacheKeyGenerator)
253254
}
254255

0 commit comments

Comments
 (0)