https://kotlinlang.org logo
#apollo-kotlin
Title
# apollo-kotlin
a

agrosner

06/28/2022, 3:37 PM
hey is there an easy way to change how the top-level response is handled . Context: I want to rewrite the response: a server graphql endpoint (non-federated) returns data like:
Copy code
{
	"summary": {
		....
	},
	"response": {
		"data": {
vs the expected:
Copy code
{
	"errors": ...,
	"data": {
use-case - i want the apollo client, with code generation etc, but the response shape is a little different. I tried using an
Httpinterceptor
but it never reaches that interceptor, and didnt know how to convert the stream to a subset of json
m

mbonnin

06/28/2022, 3:39 PM
That's highly unusual for a GraphQL server so I'd recommend fixing that in the backend if possible (you can put "summary" in an "extensions" field IIRC)
But if you have no control over hte backend, I was going to suggest HttpInterceptor indeed
Let me check quickly
a

agrosner

06/28/2022, 3:43 PM
yeah its unusual haha. its a legacy graphql api, that we cannot move off of right away. (lots of uses in our company for it, but the move to federation is in progress).
sure, thank you! all i want to be able to do is skip to the
response
. which will follow similiar format as regular graphql responses
m

mbonnin

06/28/2022, 3:50 PM
Copy code
val mockServer = MockServer()
    val apolloClient = ApolloClient.Builder()
        .serverUrl(mockServer.url())
        .addHttpInterceptor(object: HttpInterceptor {
          override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
            val response = chain.proceed(request)

            val json = AnyAdapter.fromJson(response.body!!.jsonReader(), CustomScalarAdapters.Empty) as Map<String, Any?>

            val jsonString = buildJsonString {
              writeAny(json["response"])
            }

            return HttpResponse.Builder(response.statusCode)
                .body(jsonString.encodeUtf8())
                .headers(response.headers)
                .build()
          }
        })
        .build()

    mockServer.enqueue("""
      {
        "summary": "something something",
        "response": {
          "data": {
            "hero": {
              "__typename": "Droid",
              "name": "R2-D2"
            }
          }
        }
      }
    """.trimIndent())

    val data = apolloClient.query(EpisodeHeroNameQuery(Episode.NEWHOPE)).execute().data
That's a very crude working example
A better implementation would stream the Json
(instead of converting everything to a Map in both direction like here)
a

agrosner

06/28/2022, 4:00 PM
ah interesting. i had this to test:
Copy code
.addHttpInterceptor(object: HttpInterceptor {
    override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
        val response = chain.proceed(request)
        val body = response.body!!
        return response.newBuilder()
            .body(body)
            .build()
    }
})
obviously doesnt actually do anything but it wasnt even hitting this breakpoint. im going to try again
m

mbonnin

06/28/2022, 4:01 PM
Copy code
response.newBuilder().body(body)
this fails on "impossible to set body twice"
This is a bit weird. We could close the previously existing source silently but that's a bit awkward
We should certainly take a look at what OkHttp is doing, pretty sure they have solved this issue
BTW, if you're Android-only, you can use OkHttp interceptors for this
a

agrosner

06/28/2022, 4:05 PM
thanks! yeah I searched for okhttp samples, but nothing looked good.
ill try your sample first, and try to get it working with streaming
👍 1
that worked! thank you.
🎉 1
m

mbonnin

06/28/2022, 4:18 PM
Sure thing!
a

agrosner

06/28/2022, 5:21 PM
Copy code
@Suppress("BlockingMethodInNonBlockingContext")
    override suspend fun intercept(request: HttpRequest, chain: HttpInterceptorChain): HttpResponse {
        val response = chain.proceed(request)

        val bodySource = extractNestedResponse(response)
        return HttpResponse.Builder(response.statusCode)
            .body(bodySource)
            .headers(response.headers)
            .build()
    }

    private fun extractNestedResponse(response: HttpResponse) =
        Buffer().apply {
            @Suppress("UNCHECKED_CAST")
            val json = AnyAdapter.fromJson(response.body!!.jsonReader(), CustomScalarAdapters.Empty) as Map<String, Any?>
            BufferedSinkJsonWriter(this, null).writeAny(json["response"])
        }
bit more efficient, as we just use a buffer directly rather than turn it into string and encode utf8 back again.
👍 1
6 Views