Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,25 @@ fun <D : Executable.Data> D.withErrors(
/**
* Returns this data with the given [errors] inlined.
*/
private fun Map<String, ApolloJsonElement>.withErrors(errors: List<Error>?): DataWithErrors {
internal fun Map<String, ApolloJsonElement>.withErrors(errors: List<Error>?): DataWithErrors {
if (errors == null || errors.isEmpty()) return this
var dataWithErrors = this
for (error in errors) {
val path = error.path
if (path == null) continue
dataWithErrors = dataWithErrors.withValueAt(path, error)
val path = error.path ?: continue
dataWithErrors = dataWithErrors.withErrorAt(path, error)
}
return dataWithErrors
}

@Suppress("UNCHECKED_CAST")
private fun Map<String, ApolloJsonElement>.withValueAt(path: List<Any>, value: Any?): DataWithErrors {
private fun Map<String, ApolloJsonElement>.withErrorAt(path: List<Any>, error: Error): DataWithErrors {
var node: Any? = this.toMutableMap()
val root = node
for ((i, key) in path.withIndex()) {
if (key is String) {
node as MutableMap<String, Any?>
if (i == path.lastIndex) {
node[key] = value
node[key] = error
} else {
when (val v = node[key]) {
is Map<*, *> -> {
Expand All @@ -64,15 +63,18 @@ private fun Map<String, ApolloJsonElement>.withValueAt(path: List<Any>, value: A
node[key] = v.toMutableList()
}

else -> break
else -> {
node[key] = error
break
}
}
}
node = node[key]!!
} else {
key as Int
node as MutableList<Any?>
if (i == path.lastIndex) {
node[key] = value
node[key] = error
} else {
when (val v = node[key]) {
is Map<*, *> -> {
Expand All @@ -83,7 +85,10 @@ private fun Map<String, ApolloJsonElement>.withValueAt(path: List<Any>, value: A
node[key] = v.toMutableList()
}

else -> break
else -> {
node[key] = error
break
}
}
}
node = node[key]!!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
@file:Suppress("UNCHECKED_CAST")

package com.apollographql.cache.normalized.api

import com.apollographql.apollo.api.Error
import com.apollographql.apollo.api.internal.readErrors
import com.apollographql.apollo.api.json.ApolloJsonElement
import com.apollographql.apollo.api.json.BufferedSourceJsonReader
import com.apollographql.apollo.api.json.readAny
import com.apollographql.cache.normalized.testing.assertErrorsEquals
import okio.Buffer
import kotlin.test.Test

class WithErrorsTest {
@Test
fun pathAtPresentPosition() {
val dataJson =
// language=JSON
"""
{
"hero": {
"name": "R2-D2",
"heroFriends": [
{
"id": "1000",
"name": "Luke Skywalker"
},
{
"id": "1002",
"name": null
},
{
"id": "1003",
"name": "Leia Organa"
}
]
}
}
""".trimIndent()
val data = BufferedSourceJsonReader(Buffer().writeUtf8(dataJson)).readAny() as Map<String, ApolloJsonElement>

val errorsJson =
// language=JSON
"""
[
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{"line": 6,"column": 7}],
"path": ["hero", "heroFriends", 1, "name"]
}
]
""".trimIndent()
val errors = BufferedSourceJsonReader(Buffer().writeUtf8(errorsJson)).readErrors()

val dataWithErrors = data.withErrors(errors)
assertErrorsEquals(
expected = Error.Builder("Name for character with ID 1002 could not be fetched.")
.locations(listOf(Error.Location(6, 7)))
.path(listOf("hero", "heroFriends", 1, "name"))
.build(),
actual = dataWithErrors["hero"].asMap()["heroFriends"].asList()[1].asMap()["name"] as Error,
)
}

@Test
fun pathAtAbsentPositionInList() {
val dataJson =
// language=JSON
"""
{
"hero": {
"name": "R2-D2",
"heroFriends": [
{
"id": "1000",
"name": "Luke Skywalker"
},
null,
{
"id": "1003",
"name": "Leia Organa"
}
]
}
}
""".trimIndent()
val data = BufferedSourceJsonReader(Buffer().writeUtf8(dataJson)).readAny() as Map<String, ApolloJsonElement>

val errorsJson =
// language=JSON
"""
[
{
"message": "Name for character with ID 1002 could not be fetched.",
"locations": [{"line": 6,"column": 7}],
"path": ["hero", "heroFriends", 1, "name"]
}
]
""".trimIndent()
val errors = BufferedSourceJsonReader(Buffer().writeUtf8(errorsJson)).readErrors()

val dataWithErrors = data.withErrors(errors)
assertErrorsEquals(
expected = Error.Builder("Name for character with ID 1002 could not be fetched.")
.locations(listOf(Error.Location(6, 7)))
.path(listOf("hero", "heroFriends", 1, "name"))
.build(),
actual = dataWithErrors["hero"].asMap()["heroFriends"].asList()[1] as Error,
)
}

@Test
fun pathAtAbsentPositionInObject() {
val dataJson =
// language=JSON
"""
{
"foo": null
}
""".trimIndent()
val data = BufferedSourceJsonReader(Buffer().writeUtf8(dataJson)).readAny() as Map<String, ApolloJsonElement>

val errorsJson =
// language=JSON
"""
[
{
"message": "Baz is null",
"path": ["foo", "bar", 1, "baz"]
}
]
""".trimIndent()
val errors = BufferedSourceJsonReader(Buffer().writeUtf8(errorsJson)).readErrors()

val dataWithErrors = data.withErrors(errors)
assertErrorsEquals(
expected = Error.Builder("Baz is null")
.path(listOf("foo", "bar", 1, "baz"))
.build(),
actual = dataWithErrors["foo"] as Error,
)
}
}

private fun Any?.asMap(): Map<String, Any?> = this as Map<String, Any?>
private fun Any?.asList(): List<Any?> = this as List<Any?>
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.apollographql.cache.normalized.testing

import com.apollographql.apollo.api.Error
import com.apollographql.apollo.api.Error.Location
import kotlin.test.assertContentEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
Expand All @@ -11,24 +10,26 @@ import kotlin.test.assertNull
*/
private data class ComparableError(
val message: String,
val locations: List<Location>?,
val locations: List<ComparableLocation>?,
val path: List<Any>?,
)

fun assertErrorsEquals(expected: Iterable<Error>?, actual: Iterable<Error>?) =
assertContentEquals(expected?.map {
ComparableError(
message = it.message,
locations = it.locations,
path = it.path,
)
}, actual?.map {
ComparableError(
message = it.message,
locations = it.locations,
path = it.path,
)
})
private data class ComparableLocation(
val line: Int,
val column: Int,
)

private fun Error.toComparableError(): ComparableError = ComparableError(
message = message,
locations = locations?.map { location -> ComparableLocation(location.line, location.column) },
path = path,
)

fun assertErrorsEquals(expected: Iterable<Error>?, actual: Iterable<Error>?) {
val expectedList = expected?.map(Error::toComparableError)
val actualList = actual?.map(Error::toComparableError)
assertContentEquals(expectedList, actualList)
}

fun assertErrorsEquals(expected: Error?, actual: Error?) {
if (expected == null) {
Expand Down