diff --git a/libraries/apollo-api/api/apollo-api.api b/libraries/apollo-api/api/apollo-api.api index 876dd68907a..2654939090c 100644 --- a/libraries/apollo-api/api/apollo-api.api +++ b/libraries/apollo-api/api/apollo-api.api @@ -50,7 +50,7 @@ public final class com/apollographql/apollo/api/ApolloOptionalAdapter : com/apol } public final class com/apollographql/apollo/api/ApolloRequest : com/apollographql/apollo/api/ExecutionOptions { - public synthetic fun (Lcom/apollographql/apollo/api/Operation;Ljava/util/UUID;Lcom/apollographql/apollo/api/ExecutionContext;Lcom/apollographql/apollo/api/http/HttpMethod;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public synthetic fun (Lcom/apollographql/apollo/api/Operation;Ljava/util/UUID;Lcom/apollographql/apollo/api/ExecutionContext;Lcom/apollographql/apollo/api/http/HttpMethod;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;ZLkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCanBeBatched ()Ljava/lang/Boolean; public fun getEnableAutoPersistedQueries ()Ljava/lang/Boolean; public fun getExecutionContext ()Lcom/apollographql/apollo/api/ExecutionContext; @@ -64,6 +64,7 @@ public final class com/apollographql/apollo/api/ApolloRequest : com/apollographq public final fun getRetryOnError ()Ljava/lang/Boolean; public fun getSendApqExtensions ()Ljava/lang/Boolean; public fun getSendDocument ()Ljava/lang/Boolean; + public final fun getSendEnhancedClientAwareness ()Z public final fun newBuilder ()Lcom/apollographql/apollo/api/ApolloRequest$Builder; public final fun newBuilder (Lcom/apollographql/apollo/api/Operation;)Lcom/apollographql/apollo/api/ApolloRequest$Builder; } @@ -94,6 +95,7 @@ public final class com/apollographql/apollo/api/ApolloRequest$Builder : com/apol public final fun getRetryOnError ()Ljava/lang/Boolean; public fun getSendApqExtensions ()Ljava/lang/Boolean; public fun getSendDocument ()Ljava/lang/Boolean; + public final fun getSendEnhancedClientAwareness ()Z public fun httpHeaders (Ljava/util/List;)Lcom/apollographql/apollo/api/ApolloRequest$Builder; public synthetic fun httpHeaders (Ljava/util/List;)Ljava/lang/Object; public fun httpMethod (Lcom/apollographql/apollo/api/http/HttpMethod;)Lcom/apollographql/apollo/api/ApolloRequest$Builder; @@ -107,6 +109,7 @@ public final class com/apollographql/apollo/api/ApolloRequest$Builder : com/apol public synthetic fun sendApqExtensions (Ljava/lang/Boolean;)Ljava/lang/Object; public fun sendDocument (Ljava/lang/Boolean;)Lcom/apollographql/apollo/api/ApolloRequest$Builder; public synthetic fun sendDocument (Ljava/lang/Boolean;)Ljava/lang/Object; + public final fun sendEnhancedClientAwareness (Z)Lcom/apollographql/apollo/api/ApolloRequest$Builder; } public final class com/apollographql/apollo/api/ApolloResponse { @@ -928,8 +931,9 @@ public final class com/apollographql/apollo/api/http/DefaultHttpRequestComposer public final class com/apollographql/apollo/api/http/DefaultHttpRequestComposer$Companion { public final fun appendQueryParameters (Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String; public final fun buildParamsMap (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;ZZ)Lokio/ByteString; + public final fun buildParamsMap (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;ZZZ)Lokio/ByteString; public final fun buildPostBody (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lcom/apollographql/apollo/api/http/HttpBody; - public final fun buildPostBody (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;ZLjava/lang/String;)Lcom/apollographql/apollo/api/http/HttpBody; + public final fun buildPostBody (Lcom/apollographql/apollo/api/Operation;Lcom/apollographql/apollo/api/CustomScalarAdapters;ZZLjava/lang/String;)Lcom/apollographql/apollo/api/http/HttpBody; public final fun composePayload (Lcom/apollographql/apollo/api/ApolloRequest;)Ljava/util/Map; public final fun getHEADER_ACCEPT_NAME ()Ljava/lang/String; public final fun getHEADER_ACCEPT_VALUE_DEFER ()Ljava/lang/String; diff --git a/libraries/apollo-api/api/apollo-api.klib.api b/libraries/apollo-api/api/apollo-api.klib.api index 65d009682d3..15357f92a74 100644 --- a/libraries/apollo-api/api/apollo-api.klib.api +++ b/libraries/apollo-api/api/apollo-api.klib.api @@ -322,6 +322,8 @@ final class <#A: com.apollographql.apollo.api/Operation.Data> com.apollographql. final fun (): kotlin/Boolean? // com.apollographql.apollo.api/ApolloRequest.sendApqExtensions.|(){}[0] final val sendDocument // com.apollographql.apollo.api/ApolloRequest.sendDocument|{}sendDocument[0] final fun (): kotlin/Boolean? // com.apollographql.apollo.api/ApolloRequest.sendDocument.|(){}[0] + final val sendEnhancedClientAwareness // com.apollographql.apollo.api/ApolloRequest.sendEnhancedClientAwareness|{}sendEnhancedClientAwareness[0] + final fun (): kotlin/Boolean // com.apollographql.apollo.api/ApolloRequest.sendEnhancedClientAwareness.|(){}[0] final fun <#A1: com.apollographql.apollo.api/Operation.Data> newBuilder(com.apollographql.apollo.api/Operation<#A1>): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.newBuilder|newBuilder(com.apollographql.apollo.api.Operation<0:0>){0§}[0] final fun newBuilder(): com.apollographql.apollo.api/ApolloRequest.Builder<#A> // com.apollographql.apollo.api/ApolloRequest.newBuilder|newBuilder(){}[0] @@ -356,6 +358,8 @@ final class <#A: com.apollographql.apollo.api/Operation.Data> com.apollographql. final fun (): kotlin/Boolean? // com.apollographql.apollo.api/ApolloRequest.Builder.sendApqExtensions.|(){}[0] final var sendDocument // com.apollographql.apollo.api/ApolloRequest.Builder.sendDocument|{}sendDocument[0] final fun (): kotlin/Boolean? // com.apollographql.apollo.api/ApolloRequest.Builder.sendDocument.|(){}[0] + final var sendEnhancedClientAwareness // com.apollographql.apollo.api/ApolloRequest.Builder.sendEnhancedClientAwareness|{}sendEnhancedClientAwareness[0] + final fun (): kotlin/Boolean // com.apollographql.apollo.api/ApolloRequest.Builder.sendEnhancedClientAwareness.|(){}[0] final fun addExecutionContext(com.apollographql.apollo.api/ExecutionContext): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.addExecutionContext|addExecutionContext(com.apollographql.apollo.api.ExecutionContext){}[0] final fun addHttpHeader(kotlin/String, kotlin/String): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.addHttpHeader|addHttpHeader(kotlin.String;kotlin.String){}[0] @@ -372,6 +376,7 @@ final class <#A: com.apollographql.apollo.api/Operation.Data> com.apollographql. final fun retryOnError(kotlin/Boolean?): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.retryOnError|retryOnError(kotlin.Boolean?){}[0] final fun sendApqExtensions(kotlin/Boolean?): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.sendApqExtensions|sendApqExtensions(kotlin.Boolean?){}[0] final fun sendDocument(kotlin/Boolean?): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.sendDocument|sendDocument(kotlin.Boolean?){}[0] + final fun sendEnhancedClientAwareness(kotlin/Boolean): com.apollographql.apollo.api/ApolloRequest.Builder<#A1> // com.apollographql.apollo.api/ApolloRequest.Builder.sendEnhancedClientAwareness|sendEnhancedClientAwareness(kotlin.Boolean){}[0] } } @@ -501,7 +506,8 @@ final class com.apollographql.apollo.api.http/DefaultHttpRequestComposer : com.a final fun (kotlin/String).appendQueryParameters(kotlin.collections/Map): kotlin/String // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.appendQueryParameters|appendQueryParameters@kotlin.String(kotlin.collections.Map){}[0] final fun <#A2: com.apollographql.apollo.api/Operation.Data> buildParamsMap(com.apollographql.apollo.api/Operation<#A2>, com.apollographql.apollo.api/CustomScalarAdapters, kotlin/Boolean, kotlin/Boolean): okio/ByteString // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.buildParamsMap|buildParamsMap(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;kotlin.Boolean;kotlin.Boolean){0§}[0] - final fun <#A2: com.apollographql.apollo.api/Operation.Data> buildPostBody(com.apollographql.apollo.api/Operation<#A2>, com.apollographql.apollo.api/CustomScalarAdapters, kotlin/Boolean, kotlin/String?): com.apollographql.apollo.api.http/HttpBody // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.buildPostBody|buildPostBody(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;kotlin.Boolean;kotlin.String?){0§}[0] + final fun <#A2: com.apollographql.apollo.api/Operation.Data> buildParamsMap(com.apollographql.apollo.api/Operation<#A2>, com.apollographql.apollo.api/CustomScalarAdapters, kotlin/Boolean, kotlin/Boolean, kotlin/Boolean): okio/ByteString // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.buildParamsMap|buildParamsMap(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;kotlin.Boolean;kotlin.Boolean;kotlin.Boolean){0§}[0] + final fun <#A2: com.apollographql.apollo.api/Operation.Data> buildPostBody(com.apollographql.apollo.api/Operation<#A2>, com.apollographql.apollo.api/CustomScalarAdapters, kotlin/Boolean, kotlin/Boolean, kotlin/String?): com.apollographql.apollo.api.http/HttpBody // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.buildPostBody|buildPostBody(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;kotlin.Boolean;kotlin.Boolean;kotlin.String?){0§}[0] final fun <#A2: com.apollographql.apollo.api/Operation.Data> buildPostBody(com.apollographql.apollo.api/Operation<#A2>, com.apollographql.apollo.api/CustomScalarAdapters, kotlin/String?, kotlin/Function1): com.apollographql.apollo.api.http/HttpBody // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.buildPostBody|buildPostBody(com.apollographql.apollo.api.Operation<0:0>;com.apollographql.apollo.api.CustomScalarAdapters;kotlin.String?;kotlin.Function1){0§}[0] final fun <#A2: com.apollographql.apollo.api/Operation.Data> composePayload(com.apollographql.apollo.api/ApolloRequest<#A2>): kotlin.collections/Map // com.apollographql.apollo.api.http/DefaultHttpRequestComposer.Companion.composePayload|composePayload(com.apollographql.apollo.api.ApolloRequest<0:0>){0§}[0] } diff --git a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/ApolloRequest.kt b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/ApolloRequest.kt index df3c274f73d..156c496299d 100644 --- a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/ApolloRequest.kt +++ b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/ApolloRequest.kt @@ -40,6 +40,7 @@ private constructor( val retryOnError: Boolean?, @ApolloExperimental val failFastIfOffline: Boolean?, + val sendEnhancedClientAwareness: Boolean, ) : ExecutionOptions { fun newBuilder(): Builder = newBuilder(operation) @@ -59,6 +60,7 @@ private constructor( .failFastIfOffline(failFastIfOffline) .ignoreApolloClientHttpHeaders(ignoreApolloClientHttpHeaders) .ignoreUnknownKeys(ignoreUnknownKeys) + .sendEnhancedClientAwareness(sendEnhancedClientAwareness) } class Builder( @@ -90,6 +92,8 @@ private constructor( @ApolloExperimental var failFastIfOffline: Boolean? = null private set + var sendEnhancedClientAwareness: Boolean = true + private set fun requestUuid(requestUuid: Uuid) = apply { @@ -150,6 +154,10 @@ private constructor( this.failFastIfOffline = failFastIfOffline } + fun sendEnhancedClientAwareness(sendEnhancedClientAwareness: Boolean): Builder = apply { + this.sendEnhancedClientAwareness = sendEnhancedClientAwareness + } + fun build(): ApolloRequest { return ApolloRequest( operation = operation, @@ -164,7 +172,8 @@ private constructor( ignoreApolloClientHttpHeaders = ignoreApolloClientHttpHeaders, retryOnError = retryOnError, failFastIfOffline = failFastIfOffline, - ignoreUnknownKeys = ignoreUnknownKeys + ignoreUnknownKeys = ignoreUnknownKeys, + sendEnhancedClientAwareness = sendEnhancedClientAwareness, ) } } diff --git a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/http/DefaultHttpRequestComposer.kt b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/http/DefaultHttpRequestComposer.kt index fc8be331639..9170e69bc59 100644 --- a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/http/DefaultHttpRequestComposer.kt +++ b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo/api/http/DefaultHttpRequestComposer.kt @@ -50,19 +50,21 @@ class DefaultHttpRequestComposer( } val sendApqExtensions = apolloRequest.sendApqExtensions ?: false + val sendEnhancedClientAwarenessExtensions = apolloRequest.sendEnhancedClientAwareness val sendDocument = apolloRequest.sendDocument ?: true val httpRequestBuilder = when (apolloRequest.httpMethod ?: HttpMethod.Post) { HttpMethod.Get -> { HttpRequest.Builder( method = HttpMethod.Get, - url = buildGetUrl(serverUrl, operation, customScalarAdapters, sendApqExtensions, sendDocument), + url = buildGetUrl(serverUrl, operation, customScalarAdapters, sendApqExtensions, sendDocument, sendEnhancedClientAwarenessExtensions), ).addHeader(HEADER_APOLLO_REQUIRE_PREFLIGHT, "true") } HttpMethod.Post -> { val query = if (sendDocument) operation.document() else null - val body = buildPostBody(operation, customScalarAdapters, query, apqExtensionsWriter(operation.id(), sendApqExtensions)) + val body = + buildPostBody(operation, customScalarAdapters, query, extensionsWriter(operation.id(), sendApqExtensions, sendEnhancedClientAwarenessExtensions)) HttpRequest.Builder( method = HttpMethod.Post, url = serverUrl, @@ -106,9 +108,10 @@ class DefaultHttpRequestComposer( customScalarAdapters: CustomScalarAdapters, sendApqExtensions: Boolean, sendDocument: Boolean, + sendEnhancedClientAwarenessExtensions: Boolean, ): String { return serverUrl.appendQueryParameters( - composeGetParams(operation, customScalarAdapters, sendApqExtensions, sendDocument) + composeGetParams(operation, customScalarAdapters, sendApqExtensions, sendDocument, sendEnhancedClientAwarenessExtensions) ) } @@ -147,22 +150,37 @@ class DefaultHttpRequestComposer( operation: Operation, customScalarAdapters: CustomScalarAdapters, sendApqExtensions: Boolean, + sendEnhancedClientAwarenessExtensions: Boolean, query: String?, ): Map { return composePostParams( - writer, operation, customScalarAdapters, query, apqExtensionsWriter(operation.id(), sendApqExtensions) + writer, operation, customScalarAdapters, query, extensionsWriter(operation.id(), sendApqExtensions, sendEnhancedClientAwarenessExtensions) ) } - private fun apqExtensionsWriter(id: String, sendApqExtensions: Boolean): JsonWriter.() -> Unit { + private fun extensionsWriter( + apqId: String, + sendApqExtensions: Boolean, + sendEnhancedClientAwarenessExtensions: Boolean, + ): JsonWriter.() -> Unit { + if (!sendApqExtensions && !sendEnhancedClientAwarenessExtensions) return { } + return { - if (sendApqExtensions) { - name("extensions") - writeObject { + name("extensions") + writeObject { + if (sendApqExtensions) { name("persistedQuery") writeObject { name("version").value(1) - name("sha256Hash").value(id) + name("sha256Hash").value(apqId) + } + } + + if (sendEnhancedClientAwarenessExtensions) { + name("clientLibrary") + writeObject { + name("name").value("apollo-kotlin") + name("version").value(com.apollographql.apollo.api.apolloApiVersion) } } } @@ -179,6 +197,7 @@ class DefaultHttpRequestComposer( customScalarAdapters: CustomScalarAdapters, autoPersistQueries: Boolean, sendDocument: Boolean, + sendEnhancedClientAwareness: Boolean, ): Map { val queryParams = mutableMapOf() @@ -200,18 +219,29 @@ class DefaultHttpRequestComposer( queryParams.put("query", operation.document()) } - if (autoPersistQueries) { - val extensions = buildJsonString { - writeObject { + val extensions = buildJsonString { + writeObject { + if (autoPersistQueries) { name("persistedQuery") writeObject { name("version").value(1) name("sha256Hash").value(operation.id()) } } + if (sendEnhancedClientAwareness) { + name("clientLibrary") + writeObject { + name("name").value("apollo-kotlin") + name("version").value(com.apollographql.apollo.api.apolloApiVersion) + } + } } + } + + if (!extensions.isEmpty()) { queryParams.put("extensions", extensions) } + return queryParams } @@ -241,9 +271,10 @@ class DefaultHttpRequestComposer( operation: Operation, customScalarAdapters: CustomScalarAdapters, autoPersistQueries: Boolean, + sendEnhancedClientAwarenessExtensions: Boolean, query: String?, ): HttpBody { - return buildPostBody(operation, customScalarAdapters, query, apqExtensionsWriter(operation.id(), autoPersistQueries)) + return buildPostBody(operation, customScalarAdapters, query, extensionsWriter(operation.id(), autoPersistQueries, sendEnhancedClientAwarenessExtensions)) } fun buildPostBody( @@ -278,15 +309,29 @@ class DefaultHttpRequestComposer( } } + @Deprecated("Use new function with additional parameters instead.", ReplaceWith("buildParamsMap(operation = operation, customScalarAdapters = customScalarAdapters, autoPersistQueries = autoPersistQueries, sendDocument = sendDocument, sendEnhancedClientAwarenessExtensions = true)")) + fun buildParamsMap( + operation: Operation, + customScalarAdapters: CustomScalarAdapters, + autoPersistQueries: Boolean, + sendDocument: Boolean, + ): ByteString { + return buildJsonByteString { + val query = if (sendDocument) operation.document() else null + composePostParams(this, operation, customScalarAdapters, autoPersistQueries, true, query) + } + } + fun buildParamsMap( operation: Operation, customScalarAdapters: CustomScalarAdapters, autoPersistQueries: Boolean, sendDocument: Boolean, + sendEnhancedClientAwarenessExtensions: Boolean, ): ByteString { return buildJsonByteString { val query = if (sendDocument) operation.document() else null - composePostParams(this, operation, customScalarAdapters, autoPersistQueries, query) + composePostParams(this, operation, customScalarAdapters, autoPersistQueries, sendEnhancedClientAwarenessExtensions, query) } } @@ -296,12 +341,13 @@ class DefaultHttpRequestComposer( ): Map { val operation = apolloRequest.operation val sendApqExtensions = apolloRequest.sendApqExtensions ?: false + val sendEnhancedClientAwarenessExtensions = apolloRequest.sendEnhancedClientAwareness val sendDocument = apolloRequest.sendDocument ?: true val customScalarAdapters = apolloRequest.executionContext[CustomScalarAdapters] ?: CustomScalarAdapters.Empty val query = if (sendDocument) operation.document() else null return buildJsonMap { - composePostParams(this, operation, customScalarAdapters, sendApqExtensions, query) + composePostParams(this, operation, customScalarAdapters, sendApqExtensions, sendEnhancedClientAwarenessExtensions, query) } as Map } } diff --git a/libraries/apollo-compiler/src/test/graphql/com/example/measurements b/libraries/apollo-compiler/src/test/graphql/com/example/measurements index 0a4f536cd96..6f3fb517538 100644 --- a/libraries/apollo-compiler/src/test/graphql/com/example/measurements +++ b/libraries/apollo-compiler/src/test/graphql/com/example/measurements @@ -2,16 +2,17 @@ // If you updated the codegen and test fixtures, you should commit this file too. Test: Total LOC: -aggregate-all 195528 -aggregate-kotlin-responseBased 61630 -aggregate-kotlin-operationBased 39804 +aggregate-all 201251 +aggregate-kotlin-responseBased 63247 +aggregate-kotlin-operationBased 41359 aggregate-kotlin-compat 0 -aggregate-java-operationBased 94094 +aggregate-java-operationBased 96645 java-operationBased-fragments_with_defer_and_include_directives 5754 kotlin-operationBased-fragments_with_defer_and_include_directives 3416 java-operationBased-data_builders 3013 kotlin-responseBased-data_builders 2626 +java-operationBased-typepolicy 2551 java-operationBased-mutation_create_review 2419 kotlin-responseBased-fragment_with_inline_fragment 2407 java-operationBased-fragment_with_inline_fragment 2237 @@ -24,8 +25,10 @@ kotlin-responseBased-mutation_create_review java-operationBased-mutation_create_review_semantic_naming 1629 java-operationBased-unique_type_name 1627 java-operationBased-inline_fragment_intersection 1623 +kotlin-responseBased-typepolicy 1617 kotlin-responseBased-nested_named_fragments 1610 java-operationBased-root_query_fragment_with_nested_fragments 1569 +kotlin-operationBased-typepolicy 1555 java-operationBased-named_fragment_delegate 1457 java-operationBased-simple_fragment 1438 java-operationBased-named_fragment_with_variables 1398 diff --git a/libraries/apollo-runtime/api/android/apollo-runtime.api b/libraries/apollo-runtime/api/android/apollo-runtime.api index e022cfd8ffd..1eb9c3330bb 100644 --- a/libraries/apollo-runtime/api/android/apollo-runtime.api +++ b/libraries/apollo-runtime/api/android/apollo-runtime.api @@ -108,6 +108,7 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra public final fun getRetryOnErrorInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public fun getSendApqExtensions ()Ljava/lang/Boolean; public fun getSendDocument ()Ljava/lang/Boolean; + public final fun getSendEnhancedClientAwareness ()Z public final fun getSubscriptionNetworkTransport ()Lcom/apollographql/apollo/network/NetworkTransport; public final fun getWebSocketEngine ()Lcom/apollographql/apollo/network/ws/WebSocketEngine; public final fun getWebSocketIdleTimeoutMillis ()Ljava/lang/Long; @@ -140,6 +141,7 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra public synthetic fun sendApqExtensions (Ljava/lang/Boolean;)Ljava/lang/Object; public fun sendDocument (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder; public synthetic fun sendDocument (Ljava/lang/Boolean;)Ljava/lang/Object; + public final fun sendEnhancedClientAwareness (Z)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun subscriptionNetworkTransport (Lcom/apollographql/apollo/network/NetworkTransport;)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun webSocketEngine (Lcom/apollographql/apollo/network/ws/WebSocketEngine;)Lcom/apollographql/apollo/ApolloClient$Builder; diff --git a/libraries/apollo-runtime/api/apollo-runtime.klib.api b/libraries/apollo-runtime/api/apollo-runtime.klib.api index a3a688e4296..29a86706313 100644 --- a/libraries/apollo-runtime/api/apollo-runtime.klib.api +++ b/libraries/apollo-runtime/api/apollo-runtime.klib.api @@ -660,6 +660,8 @@ final class com.apollographql.apollo/ApolloClient : com.apollographql.apollo.api final fun (): kotlin/Boolean? // com.apollographql.apollo/ApolloClient.Builder.sendApqExtensions.|(){}[0] final var sendDocument // com.apollographql.apollo/ApolloClient.Builder.sendDocument|{}sendDocument[0] final fun (): kotlin/Boolean? // com.apollographql.apollo/ApolloClient.Builder.sendDocument.|(){}[0] + final var sendEnhancedClientAwareness // com.apollographql.apollo/ApolloClient.Builder.sendEnhancedClientAwareness|{}sendEnhancedClientAwareness[0] + final fun (): kotlin/Boolean // com.apollographql.apollo/ApolloClient.Builder.sendEnhancedClientAwareness.|(){}[0] final var subscriptionNetworkTransport // com.apollographql.apollo/ApolloClient.Builder.subscriptionNetworkTransport|{}subscriptionNetworkTransport[0] final fun (): com.apollographql.apollo.network/NetworkTransport? // com.apollographql.apollo/ApolloClient.Builder.subscriptionNetworkTransport.|(){}[0] final var webSocketEngine // com.apollographql.apollo/ApolloClient.Builder.webSocketEngine|{}webSocketEngine[0] @@ -708,6 +710,7 @@ final class com.apollographql.apollo/ApolloClient : com.apollographql.apollo.api final fun retryOnErrorInterceptor(com.apollographql.apollo.interceptor/ApolloInterceptor?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.retryOnErrorInterceptor|retryOnErrorInterceptor(com.apollographql.apollo.interceptor.ApolloInterceptor?){}[0] final fun sendApqExtensions(kotlin/Boolean?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.sendApqExtensions|sendApqExtensions(kotlin.Boolean?){}[0] final fun sendDocument(kotlin/Boolean?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.sendDocument|sendDocument(kotlin.Boolean?){}[0] + final fun sendEnhancedClientAwareness(kotlin/Boolean): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.sendEnhancedClientAwareness|sendEnhancedClientAwareness(kotlin.Boolean){}[0] final fun serverUrl(kotlin/String): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.serverUrl|serverUrl(kotlin.String){}[0] final fun subscriptionNetworkTransport(com.apollographql.apollo.network/NetworkTransport?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.subscriptionNetworkTransport|subscriptionNetworkTransport(com.apollographql.apollo.network.NetworkTransport?){}[0] final fun webSocketEngine(com.apollographql.apollo.network.ws/WebSocketEngine?): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.apollo/ApolloClient.Builder.webSocketEngine|webSocketEngine(com.apollographql.apollo.network.ws.WebSocketEngine?){}[0] diff --git a/libraries/apollo-runtime/api/jvm/apollo-runtime.api b/libraries/apollo-runtime/api/jvm/apollo-runtime.api index d1a2b49d652..ae89653f943 100644 --- a/libraries/apollo-runtime/api/jvm/apollo-runtime.api +++ b/libraries/apollo-runtime/api/jvm/apollo-runtime.api @@ -108,6 +108,7 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra public final fun getRetryOnErrorInterceptor ()Lcom/apollographql/apollo/interceptor/ApolloInterceptor; public fun getSendApqExtensions ()Ljava/lang/Boolean; public fun getSendDocument ()Ljava/lang/Boolean; + public final fun getSendEnhancedClientAwareness ()Z public final fun getSubscriptionNetworkTransport ()Lcom/apollographql/apollo/network/NetworkTransport; public final fun getWebSocketEngine ()Lcom/apollographql/apollo/network/ws/WebSocketEngine; public final fun getWebSocketIdleTimeoutMillis ()Ljava/lang/Long; @@ -140,6 +141,7 @@ public final class com/apollographql/apollo/ApolloClient$Builder : com/apollogra public synthetic fun sendApqExtensions (Ljava/lang/Boolean;)Ljava/lang/Object; public fun sendDocument (Ljava/lang/Boolean;)Lcom/apollographql/apollo/ApolloClient$Builder; public synthetic fun sendDocument (Ljava/lang/Boolean;)Ljava/lang/Object; + public final fun sendEnhancedClientAwareness (Z)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun serverUrl (Ljava/lang/String;)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun subscriptionNetworkTransport (Lcom/apollographql/apollo/network/NetworkTransport;)Lcom/apollographql/apollo/ApolloClient$Builder; public final fun webSocketEngine (Lcom/apollographql/apollo/network/ws/WebSocketEngine;)Lcom/apollographql/apollo/ApolloClient$Builder; diff --git a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt index 2730172a400..3ae5aaef692 100644 --- a/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt +++ b/libraries/apollo-runtime/src/commonMain/kotlin/com/apollographql/apollo/ApolloClient.kt @@ -86,6 +86,7 @@ private constructor( private val retryOnError: ((ApolloRequest<*>) -> Boolean)? = builder.retryOnError private val retryOnErrorInterceptor: ApolloInterceptor? = builder.retryOnErrorInterceptor private val failFastIfOffline = builder.failFastIfOffline + private val sendEnhancedClientAwareness = builder.sendEnhancedClientAwareness override val executionContext: ExecutionContext = builder.executionContext override val httpMethod: HttpMethod? = builder.httpMethod @@ -288,8 +289,8 @@ private constructor( retryOnError(retryOnError ?: apolloClient.retryOnError?.invoke(apolloRequest)) failFastIfOffline(failFastIfOffline ?: apolloClient.failFastIfOffline) - ignoreUnknownKeys(ignoreUnknownKeys ?: apolloClient.ignoreUnknownKeys) + sendEnhancedClientAwareness(apolloClient.sendEnhancedClientAwareness) }.build() val allInterceptors = buildList { @@ -397,6 +398,18 @@ private constructor( var autoPersistedQueryInterceptor: ApolloInterceptor? = null private set + var sendEnhancedClientAwareness: Boolean = true + private set + + /** + * Configures whether client library metadata is sent in each request `extensions` key. + * Client library metadata is the Apollo Kotlin library name and version. + * + */ + fun sendEnhancedClientAwareness(sendEnhancedClientAwareness: Boolean): Builder = apply { + this.sendEnhancedClientAwareness = sendEnhancedClientAwareness + } + /** * Whether to fail fast if the device is offline. * Requires setting an interceptor that is aware of the network state with [retryOnErrorInterceptor]. @@ -963,6 +976,7 @@ private constructor( .cacheInterceptor(cacheInterceptor) .autoPersistedQueriesInterceptor(autoPersistedQueryInterceptor) .failFastIfOffline(failFastIfOffline) + .sendEnhancedClientAwareness(sendEnhancedClientAwareness) } } } diff --git a/scripts/release.main.kts b/scripts/release.main.kts index 762a8d16fb8..8a8c99998d6 100755 --- a/scripts/release.main.kts +++ b/scripts/release.main.kts @@ -72,6 +72,7 @@ println("Tag pushed.") val bumpVersionBranchName = "release-$versionToRelease-bump-snapshot" runCommand("git", "checkout", "-b", bumpVersionBranchName) setCurrentVersion(nextSnapshot) +setVersionInFixtures(nextSnapshot) runCommand("git", "commit", "-a", "-m", "version is now $nextSnapshot") runCommand("git", "push", "origin", bumpVersionBranchName) runCommand("gh", "pr", "create", "--base", startBranch, "--fill") @@ -243,6 +244,18 @@ fun setVersionInIntelliJPlugin(version: String) { } } +fun setVersionInFixtures(nextSnapshot: String) { + for (file in File("tests/integration-tests/testFixtures").walk()) { + if (file.isDirectory || !file.name.endsWith(".json")) continue + val content = file.readText() + .replace(Regex("""\{"name":"apollo-kotlin","version":"(.+)"\}""")) { + """{"name":"apollo-kotlin","version":"$nextSnapshot"}""" + } + file.writeText(content) + } +} + + fun mergeAndWait(branchName: String) { runCommand("gh", "pr", "merge", branchName, "--squash", "--auto", "--delete-branch") println("Waiting for the PR to be merged...") diff --git a/tests/integration-tests/src/commonTest/kotlin/test/BodyExtensionsTest.kt b/tests/integration-tests/src/commonTest/kotlin/test/BodyExtensionsTest.kt index 8d5b10bda71..880ed07d1f6 100644 --- a/tests/integration-tests/src/commonTest/kotlin/test/BodyExtensionsTest.kt +++ b/tests/integration-tests/src/commonTest/kotlin/test/BodyExtensionsTest.kt @@ -20,6 +20,7 @@ import com.apollographql.apollo.testing.internal.runTest import okio.Buffer import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertNull class WithExtensionsHttpRequestComposer(private val serverUrl: String) : HttpRequestComposer { override fun compose(apolloRequest: ApolloRequest): HttpRequest { @@ -71,4 +72,60 @@ class BodyExtensionsTest { apolloClient.close() mockServer.close() } + + @Suppress("UNCHECKED_CAST") + @Test + fun enhancedClientAwarenessExtensionsIncludedByDefault() = runTest { + val mockServer = MockServer() + + val apolloClient = ApolloClient.Builder() + .networkTransport( + HttpNetworkTransport.Builder() + .httpRequestComposer(DefaultHttpRequestComposer(mockServer.url())) + .build() + ) + .build() + + mockServer.enqueueError(statusCode = 500) + apolloClient.query(LaunchDetailsQuery("42")).execute() + + val request = mockServer.awaitRequest() + + @Suppress("UNCHECKED_CAST") + val asMap = Buffer().write(request.body).jsonReader().readAny() as Map + val expected: Map = mapOf("name" to "apollo-kotlin", "version" to com.apollographql.apollo.api.apolloApiVersion) + + assertEquals(expected, (asMap["extensions"] as Map).get("clientLibrary")) + + apolloClient.close() + mockServer.close() + } + + @Suppress("UNCHECKED_CAST") + @Test + fun enhancedClientAwarenessExtensionsExcludedWhenDisabled() = runTest { + val mockServer = MockServer() + + val apolloClient = ApolloClient.Builder() + .sendEnhancedClientAwareness(false) + .networkTransport( + HttpNetworkTransport.Builder() + .httpRequestComposer(DefaultHttpRequestComposer(mockServer.url())) + .build() + ) + .build() + + mockServer.enqueueError(statusCode = 500) + apolloClient.query(LaunchDetailsQuery("42")).execute() + + val request = mockServer.awaitRequest() + + @Suppress("UNCHECKED_CAST") + val asMap = Buffer().write(request.body).jsonReader().readAny() as Map + + assertNull((asMap["extensions"] as? Map)?.get("clientLibrary")) + + apolloClient.close() + mockServer.close() + } } diff --git a/tests/integration-tests/src/commonTest/kotlin/test/HttpGetTest.kt b/tests/integration-tests/src/commonTest/kotlin/test/HttpGetTest.kt index 0f99193ae95..1817cdc1622 100644 --- a/tests/integration-tests/src/commonTest/kotlin/test/HttpGetTest.kt +++ b/tests/integration-tests/src/commonTest/kotlin/test/HttpGetTest.kt @@ -1,6 +1,7 @@ package test import com.apollographql.apollo.ApolloClient +import com.apollographql.apollo.api.apolloApiVersion import com.apollographql.apollo.api.http.HttpMethod import com.apollographql.apollo.integration.normalizer.HeroAndFriendsNamesQuery import com.apollographql.apollo.integration.normalizer.SearchHeroQuery @@ -38,7 +39,7 @@ class HttpGetTest { .execute() assertEquals(response.data?.hero?.name, "R2-D2") assertEquals( - "/?operationName=HeroAndFriendsNames&variables=%7B%22episode%22%3A%22JEDI%22%7D&query=query%20HeroAndFriendsNames%28%24episode%3A%20Episode%29%20%7B%20hero%28episode%3A%20%24episode%29%20%7B%20name%20friends%20%7B%20name%20%7D%20%7D%20%7D", + "/?operationName=HeroAndFriendsNames&variables=%7B%22episode%22%3A%22JEDI%22%7D&query=query%20HeroAndFriendsNames%28%24episode%3A%20Episode%29%20%7B%20hero%28episode%3A%20%24episode%29%20%7B%20name%20friends%20%7B%20name%20%7D%20%7D%20%7D&extensions=%7B%22clientLibrary%22%3A%7B%22name%22%3A%22apollo-kotlin%22%2C%22version%22%3A%22${apolloApiVersion}%22%7D%7D", mockServer.awaitRequest().path ) } @@ -50,7 +51,7 @@ class HttpGetTest { .httpMethod(HttpMethod.Get) .execute() assertEquals( - "/?operationName=SearchHero&variables=%7B%22text%22%3A%22%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D%7B%7D%25%20%22%7D&query=query%20SearchHero%28%24text%3A%20String%29%20%7B%20search%28text%3A%20%24text%29%20%7B%20__typename%20...%20on%20Character%20%7B%20__typename%20name%20...%20on%20Human%20%7B%20homePlanet%20%7D%20...%20on%20Droid%20%7B%20primaryFunction%20%7D%20%7D%20%7D%20%7D", + "/?operationName=SearchHero&variables=%7B%22text%22%3A%22%21%23%24%26%27%28%29%2A%2B%2C%2F%3A%3B%3D%3F%40%5B%5D%7B%7D%25%20%22%7D&query=query%20SearchHero%28%24text%3A%20String%29%20%7B%20search%28text%3A%20%24text%29%20%7B%20__typename%20...%20on%20Character%20%7B%20__typename%20name%20...%20on%20Human%20%7B%20homePlanet%20%7D%20...%20on%20Droid%20%7B%20primaryFunction%20%7D%20%7D%20%7D%20%7D&extensions=%7B%22clientLibrary%22%3A%7B%22name%22%3A%22apollo-kotlin%22%2C%22version%22%3A%22${apolloApiVersion}%22%7D%7D", mockServer.awaitRequest().path ) } diff --git a/tests/integration-tests/src/commonTest/kotlin/test/LoggingInterceptorTest.kt b/tests/integration-tests/src/commonTest/kotlin/test/LoggingInterceptorTest.kt index f93a31e6b37..5221623290c 100644 --- a/tests/integration-tests/src/commonTest/kotlin/test/LoggingInterceptorTest.kt +++ b/tests/integration-tests/src/commonTest/kotlin/test/LoggingInterceptorTest.kt @@ -142,7 +142,7 @@ class LoggingInterceptorTest { Post http://0.0.0.0/ accept: multipart/mixed;deferspec=20220824, application/graphql-response+json, application/json [end of headers] - {"operationName":"HeroName","variables":{},"query":"query HeroName { hero { name } }"} + {"operationName":"HeroName","variables":{},"query":"query HeroName { hero { name } }","extensions":{"clientlibrary":{"name":"apollo-kotlin","version":"5.0.0-snapshot"}}} HTTP: 200 Content-Type: text/plain @@ -183,7 +183,7 @@ class LoggingInterceptorTest { Post http://0.0.0.0/ accept: multipart/mixed;deferspec=20220824, application/graphql-response+json, application/json [end of headers] - {"operationName":"HeroName","variables":{},"query":"query HeroName { hero { name } }"} + {"operationName":"HeroName","variables":{},"query":"query HeroName { hero { name } }","extensions":{"clientlibrary":{"name":"apollo-kotlin","version":"5.0.0-snapshot"}}} HTTP: 200 Content-Type: text/plain diff --git a/tests/integration-tests/src/jvmTest/kotlin/test/JvmFileUploadTest.kt b/tests/integration-tests/src/jvmTest/kotlin/test/JvmFileUploadTest.kt index e9d2552966e..e5357bc27d3 100644 --- a/tests/integration-tests/src/jvmTest/kotlin/test/JvmFileUploadTest.kt +++ b/tests/integration-tests/src/jvmTest/kotlin/test/JvmFileUploadTest.kt @@ -53,7 +53,7 @@ class JvmFileUploadTest { val request = mockServer.awaitRequest() val parts = request.parts() - val expectedBodyLength = 1009 + val expectedBodyLength = 1092 assertEquals(expectedBodyLength, request.body.size) assertEquals(expectedBodyLength.toString(), request.headers["Content-Length"]) assertEquals(4, parts.size) diff --git a/tests/integration-tests/testFixtures/allPlanets.json b/tests/integration-tests/testFixtures/allPlanets.json index 306fea73a37..7e104220d8b 100644 --- a/tests/integration-tests/testFixtures/allPlanets.json +++ b/tests/integration-tests/testFixtures/allPlanets.json @@ -1 +1 @@ -{"operationName":"AllPlanets","variables":{},"query":"query AllPlanets { allPlanets(first: 300) { planets { __typename ...PlanetFragment filmConnection { totalCount films { __typename title ...FilmFragment } } } } } fragment PlanetFragment on Planet { name climates surfaceWater } fragment FilmFragment on Film { title producers }"} \ No newline at end of file +{"operationName":"AllPlanets","variables":{},"query":"query AllPlanets { allPlanets(first: 300) { planets { __typename ...PlanetFragment filmConnection { totalCount films { __typename title ...FilmFragment } } } } } fragment PlanetFragment on Planet { name climates surfaceWater } fragment FilmFragment on Film { title producers }","extensions":{"clientLibrary":{"name":"apollo-kotlin","version":"5.0.0-SNAPSHOT"}}} \ No newline at end of file diff --git a/tests/integration-tests/testFixtures/expectedOperationsPartBodyMultiple.json b/tests/integration-tests/testFixtures/expectedOperationsPartBodyMultiple.json index 19c3baa3802..a1169a66e79 100644 --- a/tests/integration-tests/testFixtures/expectedOperationsPartBodyMultiple.json +++ b/tests/integration-tests/testFixtures/expectedOperationsPartBodyMultiple.json @@ -1 +1 @@ -{"operationName":"MultipleUpload","variables":{"files":[null,null]},"query":"mutation MultipleUpload($files: [Upload!]!) { multipleUpload(files: $files) { id path filename mimetype } }"} \ No newline at end of file +{"operationName":"MultipleUpload","variables":{"files":[null,null]},"query":"mutation MultipleUpload($files: [Upload!]!) { multipleUpload(files: $files) { id path filename mimetype } }","extensions":{"clientLibrary":{"name":"apollo-kotlin","version":"5.0.0-SNAPSHOT"}}} \ No newline at end of file diff --git a/tests/integration-tests/testFixtures/expectedOperationsPartBodyNested.json b/tests/integration-tests/testFixtures/expectedOperationsPartBodyNested.json index 7fbb1b15711..0d433c63fa7 100644 --- a/tests/integration-tests/testFixtures/expectedOperationsPartBodyNested.json +++ b/tests/integration-tests/testFixtures/expectedOperationsPartBodyNested.json @@ -1 +1 @@ -{"operationName":"NestedUpload","variables":{"topFile":null,"topFileList":[null,null],"nested":{"recursiveNested":[{"file":null,"fileList":[null,null]},{"file":null,"fileList":[null,null]}],"file":null,"fileList":[null,null]}},"query":"mutation NestedUpload($topFile: Upload, $topFileList: [Upload], $nested: NestedObject) { nestedUpload(topFile: $topFile, topFileList: $topFileList, nested: $nested) }"} \ No newline at end of file +{"operationName":"NestedUpload","variables":{"topFile":null,"topFileList":[null,null],"nested":{"recursiveNested":[{"file":null,"fileList":[null,null]},{"file":null,"fileList":[null,null]}],"file":null,"fileList":[null,null]}},"query":"mutation NestedUpload($topFile: Upload, $topFileList: [Upload], $nested: NestedObject) { nestedUpload(topFile: $topFile, topFileList: $topFileList, nested: $nested) }","extensions":{"clientLibrary":{"name":"apollo-kotlin","version":"5.0.0-SNAPSHOT"}}} \ No newline at end of file diff --git a/tests/integration-tests/testFixtures/expectedOperationsPartBodySingle.json b/tests/integration-tests/testFixtures/expectedOperationsPartBodySingle.json index 095fd3c9540..5c9ecf0caf5 100644 --- a/tests/integration-tests/testFixtures/expectedOperationsPartBodySingle.json +++ b/tests/integration-tests/testFixtures/expectedOperationsPartBodySingle.json @@ -1 +1 @@ -{"operationName":"SingleUpload","variables":{"file":null},"query":"mutation SingleUpload($file: Upload!) { singleUpload(file: $file) { id path filename mimetype } }"} \ No newline at end of file +{"operationName":"SingleUpload","variables":{"file":null},"query":"mutation SingleUpload($file: Upload!) { singleUpload(file: $file) { id path filename mimetype } }","extensions":{"clientLibrary":{"name":"apollo-kotlin","version":"5.0.0-SNAPSHOT"}}} \ No newline at end of file diff --git a/tests/integration-tests/testFixtures/expectedOperationsPartBodyTwice.json b/tests/integration-tests/testFixtures/expectedOperationsPartBodyTwice.json index d49e33e7459..9a3cee5902f 100644 --- a/tests/integration-tests/testFixtures/expectedOperationsPartBodyTwice.json +++ b/tests/integration-tests/testFixtures/expectedOperationsPartBodyTwice.json @@ -1 +1 @@ -{"operationName":"SingleUploadTwice","variables":{"file1":null,"file2":null},"query":"mutation SingleUploadTwice($file1: Upload!, $file2: Upload!) { file1: singleUpload(file: $file1) { id path filename mimetype } file2: singleUpload(file: $file2) { id path filename mimetype } }"} \ No newline at end of file +{"operationName":"SingleUploadTwice","variables":{"file1":null,"file2":null},"query":"mutation SingleUploadTwice($file1: Upload!, $file2: Upload!) { file1: singleUpload(file: $file1) { id path filename mimetype } file2: singleUpload(file: $file2) { id path filename mimetype } }","extensions":{"clientLibrary":{"name":"apollo-kotlin","version":"5.0.0-SNAPSHOT"}}} \ No newline at end of file