diff --git a/gradle/libraries.toml b/gradle/libraries.toml index 8fb50122d52..8155eb818c0 100644 --- a/gradle/libraries.toml +++ b/gradle/libraries.toml @@ -15,7 +15,7 @@ androidx-sqlite = "2.3.1" # This is used by the gradle integration tests to get the artifacts locally apollo = "4.0.0-beta.4-SNAPSHOT" # Used by the apollo-tooling project which uses a published version of Apollo -apollo-published = "4.0.0-beta.2" +apollo-published = "4.0.0-beta.3" cache = "2.0.2" # See https://developer.android.com/jetpack/androidx/releases/compose-kotlin compose-compiler = "1.5.5-dev-k2.0.0-Beta1-06b8ae672a4" diff --git a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryNetworking.kt b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryNetworking.kt index dff725d94d1..c7d6600dba8 100644 --- a/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryNetworking.kt +++ b/intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/telemetry/TelemetryNetworking.kt @@ -13,7 +13,7 @@ suspend fun executeTelemetryNetworkCall(telemetrySession: TelemetrySession) { instanceId = telemetrySession.instanceId, properties = telemetrySession.properties.map { it.toToolingTelemetryProperty() }, events = telemetrySession.events.map { it.toTelemetryEvent() }, - ).getOrThrow() + ) } @OptIn(ApolloInternal::class) diff --git a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt index 5e55c56af20..1acaed970d0 100644 --- a/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt +++ b/libraries/apollo-api/src/commonMain/kotlin/com/apollographql/apollo3/exception/Exceptions.kt @@ -64,9 +64,9 @@ class ApolloWebSocketClosedException( /** * The response was received but the response code was not 200 * - * @param statusCode: the HTTP status code - * @param headers: the HTTP headers - * @param body: the HTTP error body. By default, [body] is always null. You can opt-in [exposeHttpErrorBody] in [HttpNetworkTransport] + * @param statusCode the HTTP status code + * @param headers the HTTP headers + * @param body the HTTP error body. By default, [body] is always null. You can opt-in [HttpNetworkTransport.httpExposeErrorBody] * if you need it. If you're doing this, you **must** call [BufferedSource.close] on [body] to avoid sockets and other resources leaking. */ class ApolloHttpException( @@ -101,7 +101,7 @@ class JsonDataException(message: String) : ApolloException(message) */ class ApolloParseException(message: String? = null, cause: Throwable? = null) : ApolloException(message = message, cause = cause) -class ApolloGraphQLException(val error: Error): ApolloException("GraphQL error") { +class ApolloGraphQLException(val error: Error): ApolloException("GraphQL error: '${error.message}'") { constructor(errors: List): this(errors.first()) @Deprecated("Use error instead", level = DeprecationLevel.ERROR) diff --git a/libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls b/libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls index d5a35986076..2499c8b275b 100644 --- a/libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls +++ b/libraries/apollo-debug-server/src/androidMain/resources/schema.graphqls @@ -1,5 +1,8 @@ type Query { apolloClients: [ApolloClient!]! + """ + Returns null if an ApolloClient with the given id is not found. + """ apolloClient(id: ID!): ApolloClient } diff --git a/libraries/apollo-tooling/build.gradle.kts b/libraries/apollo-tooling/build.gradle.kts index a017d1f1a02..31c04ac02e7 100644 --- a/libraries/apollo-tooling/build.gradle.kts +++ b/libraries/apollo-tooling/build.gradle.kts @@ -57,6 +57,7 @@ apollo { endpointUrl.set("https://graphql.api.apollographql.com/api/graphql") schemaFile.set(file("src/main/graphql/platform-api/internal/schema.graphqls")) } + mapScalar("Void", "kotlin.Unit", "com.apollographql.apollo3.tooling.VoidAdapter") mapScalar("Timestamp", "java.time.Instant", "com.apollographql.apollo3.tooling.TimestampAdapter") } } diff --git a/libraries/apollo-tooling/src/main/graphql/graphql/extra.graphqls b/libraries/apollo-tooling/src/main/graphql/graphql/extra.graphqls new file mode 100644 index 00000000000..1ca17ae35cd --- /dev/null +++ b/libraries/apollo-tooling/src/main/graphql/graphql/extra.graphqls @@ -0,0 +1,2 @@ +extend schema @link(url: "https://specs.apollo.dev/nullability/v0.1", import: ["@catch", "CatchTo"]) +extend schema @catch(to: THROW) diff --git a/libraries/apollo-tooling/src/main/graphql/platform-api/internal/extra.graphqls b/libraries/apollo-tooling/src/main/graphql/platform-api/internal/extra.graphqls new file mode 100644 index 00000000000..324284c91ed --- /dev/null +++ b/libraries/apollo-tooling/src/main/graphql/platform-api/internal/extra.graphqls @@ -0,0 +1,9 @@ +extend schema @link(url: "https://specs.apollo.dev/nullability/v0.1", import: ["@semanticNonNull", "@catch", "CatchTo"]) +extend schema @catch(to: THROW) + +extend type Service @semanticNonNull(field: "statsWindow") +extend type ServiceMutation @semanticNonNull(field: "registerOperationsWithResponse") +extend type ServiceStatsWindow @semanticNonNull(field: "fieldLatencies") +extend type RegisterOperationsMutationResponse @semanticNonNull(field: "invalidOperations") +# Not sure if errors can be null or not +# extend type InvalidOperation @semanticNonNull(field: "errors") \ No newline at end of file diff --git a/libraries/apollo-tooling/src/main/graphql/platform-api/public/extra.graphqls b/libraries/apollo-tooling/src/main/graphql/platform-api/public/extra.graphqls new file mode 100644 index 00000000000..d682ff1c5f5 --- /dev/null +++ b/libraries/apollo-tooling/src/main/graphql/platform-api/public/extra.graphqls @@ -0,0 +1,7 @@ +extend schema @link(url: "https://specs.apollo.dev/nullability/v0.1", import: ["@semanticNonNull", "@catch", "CatchTo"]) +extend schema @catch(to: THROW) + +extend type GraphMutation @semanticNonNull(field: "uploadSchema") +extend type GraphMutation @semanticNonNull(field: "publishSubgraph") +extend type GraphVariant @semanticNonNull(field: "latestPublication") +extend type SchemaPublication @semanticNonNull(field: "schema") \ No newline at end of file diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/FieldInsights.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/FieldInsights.kt index a9d5306ff7e..c8da4f06d78 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/FieldInsights.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/FieldInsights.kt @@ -1,8 +1,6 @@ package com.apollographql.apollo3.tooling import com.apollographql.apollo3.annotations.ApolloExperimental -import com.apollographql.apollo3.exception.ApolloGraphQLException -import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.tooling.platformapi.internal.FieldLatenciesQuery import java.time.Instant @@ -32,45 +30,32 @@ object FieldInsights { percentile = percentile, ) ).execute() - val data = response.data - return when { - data == null -> { - val cause = when (val e = response.exception!!) { - is ApolloHttpException -> { - val body = e.body?.use { it.readUtf8() } ?: "" - Exception("Cannot fetch field latencies: (code: ${e.statusCode})\n$body", e) - } - - is ApolloGraphQLException -> { - Exception("Cannot fetch field latencies: ${e.errors.joinToString { it.message }}") - } - - else -> { - Exception("Cannot fetch field latencies: ${e.message}", e) - } - } - FieldLatenciesResult.Error(cause = cause) - } - data.service == null && response.hasErrors() -> { - FieldLatenciesResult.Error(cause = Exception("Cannot fetch field latencies: ${response.errors!!.joinToString { it.message }}")) - } + val data = response.data + if (data == null) { + return FieldLatenciesResult.Error(cause = response.toException("Cannot fetch field latencies")) + } - else -> { - FieldLatencies(fieldLatencies = data.service?.statsWindow?.fieldLatencies?.mapNotNull { - val parentType = it.groupBy.parentType ?: return@mapNotNull null - val fieldName = it.groupBy.fieldName ?: return@mapNotNull null - val durationMs = it.metrics.fieldHistogram.durationMs ?: return@mapNotNull null - FieldLatencies.FieldLatency( - parentType = parentType, - fieldName = fieldName, - durationMs = durationMs - ) - } ?: emptyList()) - } + val service = data.service + if (service == null) { + // As of Dec-2023, service doesn't return an error when it is null + // Better safe than sorry though, and we still display the GraphQL errors. + return FieldLatenciesResult.Error(cause = Exception("Cannot find service $serviceId: ${response.errors?.joinToString { it.message }}}")) } + + return FieldLatencies(fieldLatencies = service.statsWindow.fieldLatencies.mapNotNull { + val parentType = it.groupBy.parentType ?: return@mapNotNull null + val fieldName = it.groupBy.fieldName ?: return@mapNotNull null + val durationMs = it.metrics.fieldHistogram.durationMs ?: return@mapNotNull null + FieldLatencies.FieldLatency( + parentType = parentType, + fieldName = fieldName, + durationMs = durationMs + ) + }) } + @ApolloExperimental sealed interface FieldLatenciesResult { @ApolloExperimental diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/PersistedQueriesUploader.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/PersistedQueriesUploader.kt index 7e4116dd981..c671cd6fe1d 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/PersistedQueriesUploader.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/PersistedQueriesUploader.kt @@ -15,10 +15,18 @@ class PersistedQuery( ) sealed interface PublishOperationsResult -object GraphNotFound: PublishOperationsResult -class PermissionError(val message: String): PublishOperationsResult -class CannotModifyOperationBody(val message: String): PublishOperationsResult -class PublishOperationsSuccess(val added: Int, val removed: Int, val identical: Int, val updated: Int, val unaffected: Int, val name: String, val revision: Int) : PublishOperationsResult +object GraphNotFound : PublishOperationsResult +class PermissionError(val message: String) : PublishOperationsResult +class CannotModifyOperationBody(val message: String) : PublishOperationsResult +class PublishOperationsSuccess( + val added: Int, + val removed: Int, + val identical: Int, + val updated: Int, + val unaffected: Int, + val name: String, + val revision: Int, +) : PublishOperationsResult @ApolloExperimental fun publishOperations( @@ -38,11 +46,15 @@ fun publishOperations( .execute() } - val graph1 = response.dataOrThrow().graph + val data = response.data + if (data == null) { + throw response.toException("Cannot publish operations") + } + + val graph1 = data.graph if (graph1 == null) { return GraphNotFound } - val ops = graph1.persistedQueryList.publishOperations return when { ops.onPublishOperationsResult != null -> { @@ -57,12 +69,16 @@ fun publishOperations( ops.onPublishOperationsResult.build.revision ) } + ops.onPermissionError != null -> { PermissionError(ops.onPermissionError.message) } + ops.onCannotModifyOperationBodyError != null -> { CannotModifyOperationBody(ops.onCannotModifyOperationBodyError.message) } - else -> error("") + + else -> error("Unknown ops: ${ops.__typename}") } + } \ No newline at end of file diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt index 87f39a9ef64..665c5e41596 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/RegisterOperations.kt @@ -1,5 +1,6 @@ package com.apollographql.apollo3.tooling +import com.apollographql.apollo3.annotations.ApolloDeprecatedSince import com.apollographql.apollo3.annotations.ApolloExperimental import com.apollographql.apollo3.api.Optional import com.apollographql.apollo3.ast.GQLArgument @@ -26,8 +27,6 @@ import com.apollographql.apollo3.ast.transform import com.apollographql.apollo3.compiler.APOLLO_VERSION import com.apollographql.apollo3.compiler.OperationIdGenerator import com.apollographql.apollo3.compiler.operationoutput.OperationOutput -import com.apollographql.apollo3.exception.ApolloGraphQLException -import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.tooling.platformapi.internal.RegisterOperationsMutation import com.apollographql.apollo3.tooling.platformapi.internal.type.RegisteredClientIdentityInput import com.apollographql.apollo3.tooling.platformapi.internal.type.RegisteredOperationInput @@ -225,6 +224,8 @@ object RegisterOperations { return OperationIdGenerator.Sha256.apply(normalize(), "") } + @Deprecated("Use persisted queries and publishOperations instead. See https://www.apollographql.com/docs/graphos/operations/persisted-queries/") + @ApolloDeprecatedSince(ApolloDeprecatedSince.Version.v4_0_0) fun registerOperations( key: String, graphID: String, @@ -256,29 +257,21 @@ object RegisterOperations { val response = runBlocking { call.execute() } val data = response.data if (data == null) { - when (val e = response.exception!!) { - is ApolloHttpException -> { - val body = e.body?.use { it.readUtf8() } ?: "" - throw Exception("Cannot push operations: (code: ${e.statusCode})\n$body", e) - } + throw response.toException("Cannot push operations") + } - is ApolloGraphQLException -> { - throw Exception("Cannot push operations: ${e.errors.joinToString { it.message }}") - } + val service = data.service + if (service == null) { + throw Exception("Cannot push operations: cannot find service '$graphID': ${response.errors?.joinToString { it.message }}") + } - else -> { - throw Exception("Cannot push operations: ${e.message}", e) - } - } - } else { - val errors = data.service?.registerOperationsWithResponse?.invalidOperations?.flatMap { - it.errors ?: emptyList() - } ?: emptyList() + val errors = data.service.registerOperationsWithResponse.invalidOperations.flatMap { + it.errors ?: emptyList() + } - check(errors.isEmpty()) { - "Cannot push operations:\n${errors.joinToString("\n")}" - } - println("Operations pushed successfully") + if (errors.isNotEmpty()) { + throw Exception("Cannot push operations:\n${errors.joinToString("\n")}") } + println("Operations pushed successfully") } } diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaDownloader.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaDownloader.kt index 0146e0dff74..6da2a943d2d 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaDownloader.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaDownloader.kt @@ -13,6 +13,7 @@ import com.apollographql.apollo3.ast.toFullSchemaGQLDocument import com.apollographql.apollo3.ast.toGQLDocument import com.apollographql.apollo3.ast.toSDL import com.apollographql.apollo3.ast.toUtf8 +import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.network.okHttpClient import com.apollographql.apollo3.tooling.SchemaHelper.reworkFullTypeFragment import com.apollographql.apollo3.tooling.SchemaHelper.reworkInputValueFragment @@ -166,21 +167,27 @@ object SchemaDownloader { val apolloClient = ApolloClient.Builder() .serverUrl(endpoint) .okHttpClient(SchemaHelper.newOkHttpClient(insecure)) + .httpExposeErrorBody(true) .build() val response = runBlocking { apolloClient.query(DownloadSchemaQuery(graphID = graph, variant = variant)) .httpHeaders(headers.map { HttpHeader(it.key, it.value) } + HttpHeader("x-api-key", key)) .execute() } - if (response.exception != null) throw response.exception!! - if (response.errors?.isNotEmpty() == true) { - throw Exception("Cannot retrieve document from $endpoint: ${response.errors!!.joinToString { it.message }}\nCheck graph id and variant") + val data = response.data + + if (data == null) { + throw response.toException("Cannot download schema") + } + + if (data.graph == null) { + throw Exception("Cannot retrieve graph '$graph': ${response.errors?.joinToString { it.message }}") } - val document = response.data?.graph?.variant?.latestPublication?.schema?.document - check(document != null) { - "Cannot retrieve document from $endpoint\nCheck graph id and variant" + + if (data.graph.variant == null) { + throw Exception("Cannot retrieve variant '$variant': ${response.errors?.joinToString { it.message }}") } - return document + return data.graph.variant.latestPublication.schema.document } inline fun Any?.cast() = this as? T diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaHelper.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaHelper.kt index 169a8eb1d58..901644845e5 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaHelper.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaHelper.kt @@ -14,7 +14,6 @@ import com.apollographql.apollo3.ast.GQLDefinition import com.apollographql.apollo3.ast.GQLField import com.apollographql.apollo3.ast.GQLFragmentDefinition import com.apollographql.apollo3.ast.GQLOperationDefinition -import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.network.http.DefaultHttpEngine import com.apollographql.apollo3.network.okHttpClient import com.apollographql.apollo3.tooling.GraphQLFeature.* @@ -82,17 +81,12 @@ internal object SchemaHelper { .httpHeaders(headers.map { HttpHeader(it.key, it.value) }) .execute() } - response.exception?.let { e -> - if (e is ApolloHttpException) { - val body = e.body?.use { it.readUtf8() } ?: "" - throw Exception("Cannot execute pre-introspection query from $endpoint: (code: ${e.statusCode})\n$body", e) - } - throw e - } - if (response.errors?.isNotEmpty() == true) { - throw Exception("Cannot execute pre-introspection query from $endpoint: ${response.errors!!.joinToString { it.message }}") + val data = response.data + if (data == null) { + throw response.toException("Cannot execute pre-introspection query from $endpoint") } - return response.data!! + + return data } internal fun executeIntrospectionQuery( diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt index 73ccfb7abcf..f8b746f24e2 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/SchemaUploader.kt @@ -3,8 +3,6 @@ package com.apollographql.apollo3.tooling import com.apollographql.apollo3.ApolloClient import com.apollographql.apollo3.annotations.ApolloExperimental import com.apollographql.apollo3.api.http.HttpHeader -import com.apollographql.apollo3.exception.ApolloGraphQLException -import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.tooling.platformapi.public.PublishMonolithSchemaMutation import com.apollographql.apollo3.tooling.platformapi.public.PublishSubgraphSchemaMutation import kotlinx.coroutines.runBlocking @@ -68,27 +66,18 @@ object SchemaUploader { val response = runBlocking { call.execute() } val data = response.data if (data == null) { - when (val e = response.exception!!) { - is ApolloHttpException -> { - val body = e.body?.use { it.readUtf8() } ?: "" - throw Exception("Cannot upload schema: (code: ${e.statusCode})\n$body", e) - } + throw response.toException("Cannot upload schema") + } - is ApolloGraphQLException -> { - throw Exception("Cannot upload schema: ${e.errors.joinToString { it.message }}") - } + if (data.graph == null) { + throw Exception("Cannot retrieve graph '$graphID': ${response.errors?.joinToString { it.message }}") + } - else -> { - throw Exception("Cannot upload schema: ${e.message}", e) - } - } - } else { - val code = data.graph?.uploadSchema?.code - val message = data.graph?.uploadSchema?.message - val success = data.graph?.uploadSchema?.success - check(success == true) { - "Cannot upload schema (code: $code): $message" - } + val code = data.graph.uploadSchema.code + val message = data.graph.uploadSchema.message + val success = data.graph.uploadSchema.success + if (!success) { + throw Exception("Cannot upload schema (code: $code): $message") } } @@ -114,25 +103,14 @@ object SchemaUploader { val response = runBlocking { call.execute() } val data = response.data if (data == null) { - when (val e = response.exception!!) { - is ApolloHttpException -> { - val body = e.body?.use { it.readUtf8() } ?: "" - throw Exception("Cannot upload schema: (code: ${e.statusCode})\n$body", e) - } - - is ApolloGraphQLException -> { - throw Exception("Cannot upload schema: ${e.errors.joinToString { it.message }}") - } - - else -> { - throw Exception("Cannot upload schema: ${e.message}", e) - } - } - } else { - val errors = data.graph?.publishSubgraph?.errors?.filterNotNull()?.joinToString("\n") { it.code + ": " + it.message } - check(errors.isNullOrEmpty()) { - "Cannot upload schema:\n$errors" - } + throw response.toException("Cannot upload schema") + } + if (data.graph == null) { + throw Exception("Cannot find graph '$graphID': ${response.errors?.joinToString { it.message }}") + } + val errors = data.graph.publishSubgraph.errors.filterNotNull().joinToString("\n") { it.code + ": " + it.message } + if (errors.isNotEmpty()) { + throw Exception("Cannot upload schema: $errors") } } } diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/Telemetry.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/Telemetry.kt index 31b6bac28f6..8303a37f706 100644 --- a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/Telemetry.kt +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/Telemetry.kt @@ -2,8 +2,6 @@ package com.apollographql.apollo3.tooling import com.apollographql.apollo3.annotations.ApolloInternal import com.apollographql.apollo3.api.Optional -import com.apollographql.apollo3.exception.ApolloGraphQLException -import com.apollographql.apollo3.exception.ApolloHttpException import com.apollographql.apollo3.tooling.platformapi.internal.TrackApolloKotlinUsageMutation import com.apollographql.apollo3.tooling.platformapi.internal.type.ApolloKotlinUsageEventInput import com.apollographql.apollo3.tooling.platformapi.internal.type.ApolloKotlinUsagePropertyInput @@ -19,7 +17,7 @@ object Telemetry { instanceId: String, properties: List, events: List, - ): Result { + ) { val apolloClient = newInternalPlatformApiApolloClient(serverUrl = serverUrl ?: INTERNAL_PLATFORM_API_URL) val response = apolloClient.mutation( TrackApolloKotlinUsageMutation( @@ -39,23 +37,9 @@ object Telemetry { }, ) ).execute() - return when (val e = response.exception) { - null -> { - Result.success(Unit) - } - is ApolloHttpException -> { - val body = e.body?.use { it.readUtf8() } ?: "" - Result.failure(Exception("Cannot track Apollo Kotlin usage: (code: ${e.statusCode})\n$body", e)) - } - - is ApolloGraphQLException -> { - Result.failure(Exception("Cannot track Apollo Kotlin usage: ${e.errors.joinToString { it.message }}")) - } - - else -> { - Result.failure(Exception("Cannot track Apollo Kotlin usage: ${e.message}", e)) - } + if (response.data == null) { + throw response.toException("Cannot track Apollo Kotlin usage") } } diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/VoidAdapter.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/VoidAdapter.kt new file mode 100644 index 00000000000..6edace43b88 --- /dev/null +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/VoidAdapter.kt @@ -0,0 +1,16 @@ +package com.apollographql.apollo3.tooling + +import com.apollographql.apollo3.api.Adapter +import com.apollographql.apollo3.api.CustomScalarAdapters +import com.apollographql.apollo3.api.json.JsonReader +import com.apollographql.apollo3.api.json.JsonWriter + +internal object VoidAdapter : Adapter { + override fun fromJson(reader: JsonReader, customScalarAdapters: CustomScalarAdapters): Unit { + return Unit + } + + override fun toJson(writer: JsonWriter, customScalarAdapters: CustomScalarAdapters, value: Unit) { + writer.nullValue() + } +} diff --git a/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/exceptions.kt b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/exceptions.kt new file mode 100644 index 00000000000..df0f623bbb2 --- /dev/null +++ b/libraries/apollo-tooling/src/main/kotlin/com/apollographql/apollo3/tooling/exceptions.kt @@ -0,0 +1,29 @@ +package com.apollographql.apollo3.tooling + +import com.apollographql.apollo3.api.ApolloResponse +import com.apollographql.apollo3.api.Operation +import com.apollographql.apollo3.exception.ApolloHttpException + +/** + * returns an [Exception] that represents [ApolloResponse] + * + * Handles: + * - the case where [ApolloResponse.exception] is null and [ApolloResponse.data] is also null + * - reading the HTTP body if [com.apollographql.apollo3.ApolloClient.Builder.httpExposeErrorBody] is true + */ +internal fun ApolloResponse.toException(context: String): Exception { + return when (val e = exception) { + is ApolloHttpException -> { + val body = e.body?.use { it.readUtf8() } ?: "" + Exception("$context: (code: ${e.statusCode})\n$body", e) + } + + null -> { + Exception("$context: ${errors?.joinToString { it.message }}") + } + + else -> { + Exception("$context: ${e.message}", e) + } + } +} \ No newline at end of file