Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate parsing and normalization #2841

Merged
merged 13 commits into from
Jan 7, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ fun Operation<*>.composeRequestBody(

/**
* Parses GraphQL operation raw response from the [source] with provided [customScalarAdapters] and returns result [Response]
*
* This will consume [source] so you don't need to close it. Also, you cannot reuse it
*/
@JvmOverloads
fun <D : Operation.Data> Operation<D>.parse(
Expand All @@ -128,5 +130,15 @@ fun <D : Operation.Data> Operation<D>.parse(
byteString: ByteString,
customScalarAdapters: CustomScalarAdapters = DEFAULT
): Response<D> {
return SimpleOperationResponseParser.parse(Buffer().write(byteString), this, customScalarAdapters)
return parse(Buffer().write(byteString), customScalarAdapters)
}

/**
* Parses GraphQL operation raw response from the [byteString] with provided [customScalarAdapters] and returns result [Response]
*/
fun <D : Operation.Data> Operation<D>.parse(
string: String,
customScalarAdapters: CustomScalarAdapters = DEFAULT
): Response<D> {
return parse(Buffer().writeUtf8(string), customScalarAdapters)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class RealResponseReader<R : Map<String, Any?>>(
private val recordSet: R,
internal val fieldValueResolver: FieldValueResolver<R>,
internal val customScalarAdapters: CustomScalarAdapters,
internal val resolveDelegate: ResolveDelegate<R>
) : ResponseReader {
private val variableValues: Map<String, Any?> = operationVariables.valueMap()
private var selectedFieldIndex = -1
Expand All @@ -31,120 +30,72 @@ class RealResponseReader<R : Map<String, Any?>>(
override fun readString(field: ResponseField): String? {
val value = fieldValueResolver.valueFor<String>(recordSet, field)
checkValue(field, value)
willResolve(field, value)
if (value == null) {
resolveDelegate.didResolveNull()
} else {
resolveDelegate.didResolveScalar(value)
}
didResolve(field)
return value
}

override fun readInt(field: ResponseField): Int? {
val value = fieldValueResolver.valueFor<BigDecimal>(recordSet, field)
checkValue(field, value)
willResolve(field, value)
if (value == null) {
resolveDelegate.didResolveNull()
} else {
resolveDelegate.didResolveScalar(value)
}
didResolve(field)

return value?.toNumber()?.toInt()
}

override fun readDouble(field: ResponseField): Double? {
val value = fieldValueResolver.valueFor<BigDecimal>(recordSet, field)
checkValue(field, value)
willResolve(field, value)
if (value == null) {
resolveDelegate.didResolveNull()
} else {
resolveDelegate.didResolveScalar(value)
}
didResolve(field)

return value?.toNumber()?.toDouble()
}

override fun readBoolean(field: ResponseField): Boolean? {
val value = fieldValueResolver.valueFor<Boolean>(recordSet, field)
checkValue(field, value)
willResolve(field, value)
if (value == null) {
resolveDelegate.didResolveNull()
} else {
resolveDelegate.didResolveScalar(value)
}
didResolve(field)

return value
}

override fun <T : Any> readObject(field: ResponseField, block: (ResponseReader) -> T): T? {
val value: R? = fieldValueResolver.valueFor(recordSet, field)
checkValue(field, value)
willResolve(field, value)
resolveDelegate.willResolveObject(field, value)
val parsedValue: T?
parsedValue = if (value == null) {
resolveDelegate.didResolveNull()
val parsedValue: T? = if (value == null) {
null
} else {
block(RealResponseReader(operationVariables, value, fieldValueResolver, customScalarAdapters, resolveDelegate))
block(RealResponseReader(operationVariables, value, fieldValueResolver, customScalarAdapters))
}
resolveDelegate.didResolveObject(field, value)
didResolve(field)
return parsedValue
}

override fun <T : Any> readList(field: ResponseField, block: (ResponseReader.ListItemReader) -> T): List<T?>? {
val values = fieldValueResolver.valueFor<List<*>>(recordSet, field)
checkValue(field, values)
willResolve(field, values)
val result = if (values == null) {
resolveDelegate.didResolveNull()
null
} else {
values.mapIndexed { index, value ->
resolveDelegate.willResolveElement(index)
if (value == null) {
resolveDelegate.didResolveNull()
null
} else {
block(ListItemReader(field, value))
}.also { resolveDelegate.didResolveElement(index) }
}.also { resolveDelegate.didResolveList(values) }
}
}
}
didResolve(field)
return result
}

override fun <T : Any> readCustomScalar(field: ResponseField.CustomScalarField): T? {
val value = fieldValueResolver.valueFor<Any>(recordSet, field)
checkValue(field, value)
willResolve(field, value)
val result: T?
if (value == null) {
resolveDelegate.didResolveNull()
result = null
} else {
val scalarTypeAdapter: CustomScalarAdapter<T> = customScalarAdapters.adapterFor(field.customScalar)
result = scalarTypeAdapter.decode(fromRawValue(value))
checkValue(field, result)
resolveDelegate.didResolveScalar(value)
}
didResolve(field)
return result
}

private fun willResolve(field: ResponseField, value: Any?) {
resolveDelegate.willResolve(field, operationVariables, value)
}

private fun didResolve(field: ResponseField) {
resolveDelegate.didResolve(field, operationVariables)
}

private fun checkValue(field: ResponseField, value: Any?) {
check(field.optional || value != null) {
"corrupted response reader, expected non null value for ${field.fieldName}"
Expand Down Expand Up @@ -177,52 +128,42 @@ class RealResponseReader<R : Map<String, Any?>>(
) : ResponseReader.ListItemReader {

override fun readString(): String {
resolveDelegate.didResolveScalar(value)
return value as String
}

override fun readInt(): Int {
resolveDelegate.didResolveScalar(value)
return (value as BigDecimal).toNumber().toInt()
}

override fun readDouble(): Double {
resolveDelegate.didResolveScalar(value)
return (value as BigDecimal).toNumber().toDouble()
}

override fun readBoolean(): Boolean {
resolveDelegate.didResolveScalar(value)
return value as Boolean
}

override fun <T : Any> readCustomScalar(customScalar: CustomScalar): T {
val scalarTypeAdapter: CustomScalarAdapter<T> = customScalarAdapters.adapterFor(customScalar)
resolveDelegate.didResolveScalar(value)
return scalarTypeAdapter.decode(fromRawValue(value))
}

@Suppress("UNCHECKED_CAST")
override fun <T : Any> readObject(block: (ResponseReader) -> T): T {
val value = value as R
resolveDelegate.willResolveObject(field, value)
val item = block(RealResponseReader(operationVariables, value, fieldValueResolver, customScalarAdapters, resolveDelegate))
resolveDelegate.didResolveObject(field, value)
val item = block(RealResponseReader(operationVariables, value, fieldValueResolver, customScalarAdapters))
return item
}

override fun <T : Any> readList(block: (ResponseReader.ListItemReader) -> T): List<T?> {
val values = value as List<*>
val result = values.mapIndexed { index, value ->
resolveDelegate.willResolveElement(index)
if (value == null) {
resolveDelegate.didResolveNull()
null
} else {
block(ListItemReader(field, value))
}.also { resolveDelegate.didResolveElement(index) }
}
}
resolveDelegate.didResolveList(values)
return result
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.apollographql.apollo.internal.response
package com.apollographql.apollo.api.internal.response

import com.apollographql.apollo.api.BigDecimal
import com.apollographql.apollo.api.Operation
Expand All @@ -7,6 +7,8 @@ import com.apollographql.apollo.api.CustomScalar
import com.apollographql.apollo.api.CustomScalarAdapters
import com.apollographql.apollo.api.internal.ResolveDelegate
import com.apollographql.apollo.api.internal.ResponseWriter
import com.apollographql.apollo.api.internal.Utils.shouldSkip
import com.apollographql.apollo.api.internal.json.JsonWriter.Companion.of

class RealResponseWriter(
private val operationVariables: Operation.Variables,
Expand All @@ -20,15 +22,15 @@ class RealResponseWriter(
}

override fun writeInt(field: ResponseField, value: Int?) {
writeScalarFieldValue(field, if (value != null) BigDecimal(value.toLong()) else null)
writeScalarFieldValue(field, if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeLong(field: ResponseField, value: Long?) {
writeScalarFieldValue(field, if (value != null) BigDecimal(value) else null)
writeScalarFieldValue(field, if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeDouble(field: ResponseField, value: Double?) {
writeScalarFieldValue(field, if (value != null) BigDecimal(value) else null)
writeScalarFieldValue(field, if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeBoolean(field: ResponseField, value: Boolean?) {
Expand All @@ -41,7 +43,11 @@ class RealResponseWriter(
}

override fun writeObject(field: ResponseField, block: ((ResponseWriter) -> Unit)?) {
if (field.shouldSkip(variableValues = operationVariables.valueMap())) {
return
}
checkFieldValue(field, block)

if (block == null) {
buffer[field.responseName] = FieldDescriptor(field, null)
return
Expand All @@ -55,7 +61,11 @@ class RealResponseWriter(
field: ResponseField, values: List<T>?,
block: (items: List<T>?, listItemWriter: ResponseWriter.ListItemWriter) -> Unit
) {
if (field.shouldSkip(variableValues = operationVariables.valueMap())) {
return
}
checkFieldValue(field, values)

if (values == null) {
buffer[field.responseName] = FieldDescriptor(field, null)
return
Expand All @@ -70,6 +80,9 @@ class RealResponseWriter(
}

private fun writeScalarFieldValue(field: ResponseField, value: Any?) {
if (field.shouldSkip(variableValues = operationVariables.valueMap())) {
return
}
checkFieldValue(field, value)
buffer[field.responseName] = FieldDescriptor(field, value)
}
Expand Down Expand Up @@ -181,15 +194,15 @@ class RealResponseWriter(
}

override fun writeInt(value: Int?) {
accumulator.add(if (value != null) BigDecimal(value.toLong()) else null)
accumulator.add(if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeLong(value: Long?) {
accumulator.add(if (value != null) BigDecimal(value) else null)
accumulator.add(if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeDouble(value: Double?) {
accumulator.add(if (value != null) BigDecimal(value) else null)
accumulator.add(if (value != null) BigDecimal(value.toString()) else null)
}

override fun writeBoolean(value: Boolean?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ object SimpleOperationResponseParser {
adapter: ResponseAdapter<D>,
variables: Operation.Variables,
customScalarAdapters: CustomScalarAdapters,
): D {
): D? {
if (peek() == JsonReader.Token.NULL) {
return nextNull<D>()
}

beginObject()
val data = adapter.fromResponse(
StreamResponseReader(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.apollographql.apollo.api.internal

import com.apollographql.apollo.api.ResponseField
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic

Expand Down Expand Up @@ -44,4 +45,24 @@ object Utils {
}
return reference
}

internal fun ResponseField.shouldSkip(variableValues: Map<String, Any?>): Boolean {
for (condition in conditions) {
if (condition is ResponseField.BooleanCondition) {
val conditionValue = variableValues[condition.variableName] as Boolean
if (condition.isInverted) {
// means it's a skip directive
if (conditionValue) {
return true
}
} else {
// means it's an include directive
if (!conditionValue) {
return true
}
}
}
}
return false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.apollographql.apollo.interceptor.ApolloRequest
import com.apollographql.apollo.interceptor.ApolloRequestInterceptor
import com.apollographql.apollo.interceptor.ApolloResponse
import com.apollographql.apollo.api.internal.RealResponseReader
import com.apollographql.apollo.internal.response.RealResponseWriter
import com.apollographql.apollo.api.internal.response.RealResponseWriter
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
Expand Down Expand Up @@ -69,7 +69,7 @@ class ApolloCacheInterceptor<S>(private val store: S) : ApolloRequestInterceptor
responseNormalizer.willResolveRootQuery(operation);
writer.resolveFields(responseNormalizer)

store.merge(responseNormalizer.records()?.filterNotNull() ?: emptySet(), CacheHeaders.NONE)
store.merge(responseNormalizer.records().toList(), CacheHeaders.NONE)
}

private fun <D : Operation.Data> readFromCache(request: ApolloRequest<D>): Response<D>? {
Expand All @@ -82,7 +82,7 @@ class ApolloCacheInterceptor<S>(private val store: S) : ApolloRequestInterceptor
CacheHeaders.NONE,
RealCacheKeyBuilder()
)
val responseReader = RealResponseReader(operation.variables(), rootRecord, fieldValueResolver, request.customScalarAdapters, NoOpResolveDelegate())
val responseReader = RealResponseReader(operation.variables(), rootRecord, fieldValueResolver, request.customScalarAdapters)
val data = operation.adapter().fromResponse(responseReader)
return builder<D>(operation)
.data(data)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
query EpisodeHeroNameWithId($episode: Episode) {
hero(episode: $episode) {
id
name
}
}