All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.apollographql.apollo3.api.ApolloResponse.kt Maven / Gradle / Ivy

There is a newer version: 4.0.0-beta.7
Show newest version
package com.apollographql.apollo3.api

import com.apollographql.apollo3.exception.ApolloException
import com.apollographql.apollo3.exception.ApolloGraphQLException
import com.apollographql.apollo3.exception.DefaultApolloException
import com.apollographql.apollo3.exception.NoDataException
import com.benasher44.uuid.Uuid
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName

/**
 * Represents a GraphQL response. GraphQL responses can be partial responses, so it is valid to have both data != null and exception != null
 *
 * Valid states are:
 * - data != null && exception == null: complete data with no error
 * - data == null && exception != null: no data, a network error or operation error happened
 * - data != null && exception != null: partial data with field errors
 */
class ApolloResponse
private constructor(
    @JvmField
    val requestUuid: Uuid,

    /**
     * The GraphQL operation this response represents
     */
    @JvmField
    val operation: Operation,

    /**
     * Parsed response of GraphQL [operation] execution.
     *
     * See also [exception]
     */
    @JvmField
    val data: D?,

    /**
     * [GraphQL errors](https://spec.graphql.org/October2021/#sec-Errors) returned by the server to let the client know that something
     * has gone wrong.
     *
     * If no GraphQL error was raised, [errors] is null. Else it's a non-empty list of errors indicating where the error(s) happened.
     *
     * Note that because GraphQL allows partial data, it is possible to have both [data] non-null and [errors] non-null.
     *
     * See also [exception]
     */
    @JvmField
    val errors: List?,

    exception: ApolloException?,

    /**
     * Extensions of GraphQL protocol, arbitrary map of key [String] / value [Any] sent by server along with the response.
     */
    @JvmField
    val extensions: Map,

    /**
     * The context of GraphQL [operation] execution.
     * This can contain additional data contributed by interceptors.
     */
    @JvmField
    val executionContext: ExecutionContext,

    /**
     * Indicates that this [ApolloResponse] is the last [ApolloResponse] in a given [Flow] and that no
     * other items are expected.
     *
     * This is used as a hint by the watchers to make sure to subscribe before the last item is emitted.
     *
     * There can be false negatives where [isLast] is false if the producer does not know in advance if
     * other items are emitted. For an example, the CacheAndNetwork fetch policy doesn't emit the network
     * item if it fails.
     *
     * There must not be false positives. If [isLast] is true, no other items must follow.
     */
    @JvmField
    val isLast: Boolean,
) {

  /**
   * An [ApolloException] if a complete GraphQL response wasn't received, an instance of [ApolloGraphQLException] if GraphQL
   * errors were return or another instance of [ApolloException] if a network, parsing, caching or other error happened.
   *
   * For example, `exception` is non-null if there is a network failure or cache miss.
   *
   * See also [data]
   */
  @JvmField
  val exception: ApolloException? = when  {
    exception != null -> exception
    !errors.isNullOrEmpty() -> ApolloGraphQLException(errors)
    data == null -> DefaultApolloException("No data and no error was returned")
    else -> null
  }

  /**
   * A shorthand property to get a non-nullable `data` if handling partial data is **not** important
   *
   * Note: A future version could use [Definitely non nullable types](https://github.com/Kotlin/KEEP/pull/269)
   * to implement something like `ApolloResponse.assertNoErrors(): ApolloResponse`
   */
  @get:JvmName("dataAssertNoErrors")
  @Deprecated(message = "Use dataOrThrow instead", replaceWith = ReplaceWith("dataOrThrow()"))
  val dataAssertNoErrors: D
    get() {
      return dataOrThrow()
    }

  /**
   * Return [data] if not null or throws [exception] else
   */
  fun dataOrThrow(): D {
    return data ?: throw NoDataException(cause = exception)
  }

  fun hasErrors(): Boolean = !errors.isNullOrEmpty()

  fun newBuilder(): Builder {
    return Builder(operation, requestUuid, data, errors, extensions, exception)
        .addExecutionContext(executionContext)
        .isLast(isLast)
  }

  class Builder internal constructor(
      private val operation: Operation,
      private var requestUuid: Uuid,
      private var data: D?,
      private var errors: List?,
      private var extensions: Map?,
      private var exception: ApolloException?
  ) {
    private var executionContext: ExecutionContext = ExecutionContext.Empty
    private var isLast = false

    /**
     * Constructs a successful response with a valid data, no errors nor extensions
     */
    constructor(
        operation: Operation,
        requestUuid: Uuid,
        data: D,
    ): this(operation, requestUuid, data, null, null, null)

    /**
     * Constructs a response from data, errors and extensions
     *
     * If there are GraphQL errors, they will also be forwarded to [exception] so that the caller can do all the
     * checking in a single place
     */
    constructor(
        operation: Operation,
        requestUuid: Uuid,
        data: D?,
        errors: List?,
        extensions: Map?,
    ): this(operation, requestUuid, data, errors, extensions, null)

    /**
     * Constructs an exception response
     */
    constructor(
        operation: Operation,
        requestUuid: Uuid,
        exception: ApolloException
    ): this(operation, requestUuid, null, null, null, exception)

    fun addExecutionContext(executionContext: ExecutionContext) = apply {
      this.executionContext = this.executionContext + executionContext
    }

    fun data(data: D?) = apply {
      this.data = data
    }

    fun errors(errors: List?) = apply {
      this.errors = errors
    }

    fun exception(exception: ApolloException?) = apply {
      this.exception = exception
    }

    fun extensions(extensions: Map?) = apply {
      this.extensions = extensions
    }

    fun requestUuid(requestUuid: Uuid) = apply {
      this.requestUuid = requestUuid
    }

    fun isLast(isLast: Boolean) = apply {
      this.isLast = isLast
    }

    fun build(): ApolloResponse {
      return ApolloResponse(
          operation = operation,
          requestUuid = requestUuid,
          data = data,
          executionContext = executionContext,
          extensions = extensions ?: emptyMap(),
          errors = errors,
          exception = exception,
          isLast = isLast,
      )
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy