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

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

There is a newer version: 4.0.0-beta.7
Show newest version
@file:JvmName("CompiledGraphQL")

package com.apollographql.apollo3.api

import com.apollographql.apollo3.annotations.ApolloDeprecatedSince
import com.apollographql.apollo3.annotations.ApolloDeprecatedSince.Version.v4_0_0
import com.apollographql.apollo3.annotations.ApolloExperimental
import com.apollographql.apollo3.api.json.BufferedSinkJsonWriter
import com.apollographql.apollo3.api.json.writeAny
import okio.Buffer
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName

sealed class CompiledSelection

/**
 * A compiled field from a GraphQL operation
 */
class CompiledField internal constructor(
    val name: String,
    val type: CompiledType,
    val alias: String?,
    val condition: List,
    val arguments: List,
    val selections: List,
) : CompiledSelection() {
  val responseName: String
    get() = alias ?: name

  /**
   * Resolves field argument value by [name]. If the argument contains variables, replace them with their actual value
   */
  fun resolveArgument(
      name: String,
      variables: Executable.Variables,
  ): Any? {
    return resolveVariables(arguments.firstOrNull { it.name == name }?.value, variables)
  }

  /**
   * Returns a String containing the name of this field as well as encoded arguments. For an example:
   * `hero({"episode": "Jedi"})`
   * This is mostly used internally to compute records
   */
  fun nameWithArguments(variables: Executable.Variables): String {
    val filterOutPaginationArguments = arguments.any { it.isPagination }
    val arguments = if (filterOutPaginationArguments) {
      this.arguments.filterNot { it.isPagination }
    } else {
      this.arguments
    }
    if (arguments.isEmpty()) {
      return name
    }
    val map = arguments.associateBy { it.name }.mapValues { it.value.value }
    val resolvedArguments = resolveVariables(map, variables)
    return try {
      val buffer = Buffer()
      val jsonWriter = BufferedSinkJsonWriter(buffer)
      jsonWriter.writeAny(resolvedArguments)
      jsonWriter.close()
      "${name}(${buffer.readUtf8()})"
    } catch (e: Exception) {
      throw RuntimeException(e)
    }
  }

  fun newBuilder(): Builder = Builder(this)

  class Builder(val name: String, val type: CompiledType) {
    private var alias: String? = null
    private var condition: List = emptyList()
    private var arguments: List = emptyList()
    private var selections: List = emptyList()

    constructor(compiledField: CompiledField) : this(compiledField.name, compiledField.type) {
      this.alias = compiledField.alias
      this.condition = compiledField.condition
      this.arguments = compiledField.arguments
      this.selections = compiledField.selections
    }

    fun alias(alias: String?) = apply {
      this.alias = alias
    }

    fun condition(condition: List) = apply {
      this.condition = condition
    }

    fun arguments(arguments: List) = apply {
      this.arguments = arguments
    }

    fun selections(selections: List) = apply {
      this.selections = selections
    }

    fun build(): CompiledField = CompiledField(
        name = name,
        alias = alias,
        type = type,
        condition = condition,
        arguments = arguments,
        selections = selections
    )
  }
}

/**
 * A compiled inline fragment or fragment spread
 */
class CompiledFragment internal constructor(
    val typeCondition: String,
    val possibleTypes: List,
    val condition: List,
    val selections: List,
) : CompiledSelection() {

  class Builder(val typeCondition: String, val possibleTypes: List) {
    var condition: List = emptyList()
    var selections: List = emptyList()

    fun condition(condition: List) = apply {
      this.condition = condition
    }

    fun selections(selections: List) = apply {
      this.selections = selections
    }

    fun build() = CompiledFragment(typeCondition, possibleTypes, condition, selections)
  }
}


data class CompiledCondition(val name: String, val inverted: Boolean)

sealed class CompiledType {
  @Deprecated("Use rawType instead", ReplaceWith("rawType()"))
  abstract fun leafType(): CompiledNamedType
  abstract fun rawType(): CompiledNamedType
}

class CompiledNotNullType(val ofType: CompiledType) : CompiledType() {
  @Deprecated("Use rawType instead", ReplaceWith("rawType()"))
  override fun leafType() = ofType.rawType()
  override fun rawType() = ofType.rawType()
}

class CompiledListType(val ofType: CompiledType) : CompiledType() {
  @Deprecated("Use rawType instead", ReplaceWith("rawType()"))
  override fun leafType() = ofType.rawType()
  override fun rawType() = ofType.rawType()
}

sealed class CompiledNamedType(val name: String) : CompiledType() {
  @Deprecated("Use rawType instead", ReplaceWith("rawType()"))
  override fun leafType() = this
  override fun rawType() = this
}

/**
 * A GraphQL scalar type that is mapped to a Kotlin. This is named "Custom" for historical reasons
 * but is also used for builtin scalars
 *
 * TODO v4: rename this to ScalarType
 */
class CustomScalarType(
    /**
     * GraphQL schema custom scalar type name (e.g. `ID`, `URL`, `DateTime` etc.)
     */
    name: String,

    /**
     * Fully qualified class name this GraphQL scalar type is mapped to (e.g. `java.lang.String`, `java.net.URL`, `java.util.DateTime`)
     */
    val className: String,
) : CompiledNamedType(name)

class ObjectType internal constructor(
    name: String,
    keyFields: List,
    implements: List,
    embeddedFields: List,
) : CompiledNamedType(name) {
  val keyFields = keyFields
  val implements = implements
  val embeddedFields = embeddedFields

  fun newBuilder(): Builder = Builder(this)

  class Builder(internal val name: String) {
    private var keyFields: List = emptyList()
    private var implements: List = emptyList()
    private var embeddedFields: List = emptyList()

    constructor(objectType: ObjectType) : this(objectType.name) {
      this.keyFields = objectType.keyFields
      this.implements = objectType.implements
      this.embeddedFields = objectType.embeddedFields
    }

    fun keyFields(keyFields: List) = apply {
      this.keyFields = keyFields
    }

    // This method is named "interfaces" and not "implements" to avoid using a reserved Java keyword
    fun interfaces(implements: List) = apply {
      this.implements = implements
    }

    fun embeddedFields(embeddedFields: List) = apply {
      this.embeddedFields = embeddedFields
    }


    fun build(): ObjectType = ObjectType(
        name = name,
        keyFields = keyFields,
        implements = implements,
        embeddedFields = embeddedFields
    )
  }
}

class InterfaceType internal constructor(
    name: String,
    keyFields: List,
    implements: List,
    embeddedFields: List,
) : CompiledNamedType(name) {
  val keyFields = keyFields
  val implements = implements
  val embeddedFields = embeddedFields

  fun newBuilder(): Builder = Builder(this)

  class Builder(internal val name: String) {
    private var keyFields: List = emptyList()
    private var implements: List = emptyList()
    private var embeddedFields: List = emptyList()

    constructor(interfaceType: InterfaceType) : this(interfaceType.name) {
      this.keyFields = interfaceType.keyFields
      this.implements = interfaceType.implements
      this.embeddedFields = interfaceType.embeddedFields
    }

    fun keyFields(keyFields: List) = apply {
      this.keyFields = keyFields
    }

    // This method is named "interfaces" and not "implements" to avoid using a reserved Java keyword
    fun interfaces(implements: List) = apply {
      this.implements = implements
    }

    fun embeddedFields(embeddedFields: List) = apply {
      this.embeddedFields = embeddedFields
    }


    fun build(): InterfaceType = InterfaceType(
        name = name,
        keyFields = keyFields,
        implements = implements,
        embeddedFields = embeddedFields
    )
  }
}

class UnionType(
    name: String,
    vararg val members: ObjectType,
) : CompiledNamedType(name)

class InputObjectType(
    name: String,
) : CompiledNamedType(name)

class EnumType(
    name: String,
    val values: List
) : CompiledNamedType(name)

/**
 * TODO v4: remove (see also [CustomScalarType] above
 */
class ScalarType(
    name: String,
) : CompiledNamedType(name)


@JvmName("-notNull")
fun CompiledType.notNull() = CompiledNotNullType(this)

@JvmName("-list")
fun CompiledType.list() = CompiledListType(this)

/**
 * The Kotlin representation of a GraphQL variable value
 */
class CompiledVariable(val name: String)

/**
 * The Kotlin representation of a GraphQL argument
 *
 * value can be
 * - String, Int, Double, Boolean
 * - null
 * - Map
 * - List
 * - [CompiledVariable]
 *
 * Note: for now, enums are mapped to Strings
 */
class CompiledArgument private constructor(
    val name: String,
    val value: Any?,
    val isKey: Boolean = false,
    @ApolloExperimental
    val isPagination: Boolean = false,
) {
  class Builder(
      private val name: String,
      private val value: Any?,
  ) {
    private var isKey: Boolean = false
    private var isPagination: Boolean = false

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

    @ApolloExperimental
    fun isPagination(isPagination: Boolean) = apply {
      this.isPagination = isPagination
    }

    fun build(): CompiledArgument = CompiledArgument(
        name = name,
        value = value,
        isKey = isKey,
        isPagination = isPagination,
    )
  }
}

/**
 * Resolve all variables that may be contained inside `value`
 */
@Suppress("UNCHECKED_CAST")
fun resolveVariables(value: Any?, variables: Executable.Variables): Any? {
  return when (value) {
    null -> null
    is CompiledVariable -> {
      variables.valueMap[value.name]
    }
    is Map<*, *> -> {
      value as Map
      value.mapValues {
        resolveVariables(it.value, variables)
      }.toList()
          .sortedBy { it.first }
          .toMap()
    }
    is List<*> -> {
      value.map {
        resolveVariables(it, variables)
      }
    }
    else -> value
  }
}

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledSchemaType = ObjectType.Builder("__Schema").build()

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledTypeType = ObjectType.Builder("__Type").build()

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledFieldType = ObjectType.Builder("__Field").build()

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledInputValueType = ObjectType.Builder("__InputValue").build()

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledEnumValueType = ObjectType.Builder("__EnumValue").build()

@Deprecated("Introspection types are now generated like other types. Use the generated class instead.", level = DeprecationLevel.ERROR)
@ApolloDeprecatedSince(v4_0_0)
@JvmField
val CompiledDirectiveType = ObjectType.Builder("__Directive").build()

fun CompiledNamedType.isComposite(): Boolean {
  return when (this) {
    is UnionType,
    is InterfaceType,
    is ObjectType,
    -> true
    else
    -> false
  }
}


fun CompiledNamedType.keyFields(): List {
  return when (this) {
    is InterfaceType -> keyFields
    is ObjectType -> keyFields
    else -> emptyList()
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy