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

commonMain.com.apollographql.execution.internal.coerceArgumentValues.kt Maven / Gradle / Ivy

The newest version!
package com.apollographql.execution.internal

import com.apollographql.apollo.ast.GQLEnumTypeDefinition
import com.apollographql.apollo.ast.GQLEnumValue
import com.apollographql.apollo.ast.GQLField
import com.apollographql.apollo.ast.GQLInputObjectTypeDefinition
import com.apollographql.apollo.ast.GQLInterfaceTypeDefinition
import com.apollographql.apollo.ast.GQLListType
import com.apollographql.apollo.ast.GQLListValue
import com.apollographql.apollo.ast.GQLNamedType
import com.apollographql.apollo.ast.GQLNonNullType
import com.apollographql.apollo.ast.GQLNullValue
import com.apollographql.apollo.ast.GQLObjectTypeDefinition
import com.apollographql.apollo.ast.GQLObjectValue
import com.apollographql.apollo.ast.GQLScalarTypeDefinition
import com.apollographql.apollo.ast.GQLType
import com.apollographql.apollo.ast.GQLUnionTypeDefinition
import com.apollographql.apollo.ast.GQLValue
import com.apollographql.apollo.ast.GQLVariableValue
import com.apollographql.apollo.ast.Schema
import com.apollographql.apollo.ast.definitionFromScope
import com.apollographql.execution.Coercing
import com.apollographql.execution.scalarCoercingParseLiteral

internal fun coerceArgumentValues(
    schema: Schema,
    typename: String,
    field: GQLField,
    coercings: Map>,
    coercedVariables: Map,
): Map {
  val coercedValues = mutableMapOf()
  val argumentValues = field.arguments.associate { it.name to it.value }
  val argumentDefinitions = field.definitionFromScope(schema, typename)!!.arguments

  argumentDefinitions.forEach { argumentDefinition ->
    val argumentName = argumentDefinition.name
    val argumentType = argumentDefinition.type
    val defaultValue = argumentDefinition.defaultValue
    var hasValue = argumentValues.containsKey(argumentName)
    val argumentValue = argumentValues.get(argumentName)
    var value: Any?
    if (argumentValue is GQLVariableValue) {
      val variableName = argumentValue.name
      hasValue = coercedVariables.containsKey(variableName)
      value = coercedVariables.get(variableName)
    } else {
      value = argumentValue
    }
    if (!hasValue && defaultValue != null) {
      hasValue = true
      value = defaultValue
    } else {
      if (argumentType is GQLNonNullType) {
        if (!hasValue) {
          error("No value passed for required argument: '$argumentName'")
        }
        if (value == null) {
          error("'null' found in non-null position for argument: '$argumentName'")
        }
      }
    }

    if (hasValue) {
      val value2 = if (value == null) {
        null
      } else if (argumentValue is GQLVariableValue) {
        value
      } else if (value is GQLValue) {
        coerceLiteralToInternal(schema, value, argumentType, coercings, coercedVariables)
      } else {
        error("Cannot coerce '$value'")
      }
      coercedValues.put(argumentName, value2)
    }
  }

  return coercedValues
}

/**
 *
 */
internal fun coerceLiteralToInternal(schema: Schema, value: GQLValue, type: GQLType, coercings: Map>, coercedVariables: Map): InternalValue {
  if (value is GQLNullValue) {
    check(type !is GQLNonNullType) {
      error("'null' found in non-null position")
    }
    return null
  }
  if (value is GQLVariableValue) {
    /**
     * The absence of a variable MUST be checked at the callsite
     *
     * This is done for:
     * - top level argument values
     * - input objects
     * - lists
     */
    return coercedVariables.get(value.name)
  }

  return when (type) {
    is GQLNonNullType -> {
      coerceLiteralToInternal(schema, value, type.type, coercings, coercedVariables)
    }

    is GQLListType -> {
      if (value is GQLListValue) {
        value.values.map {
          if (it is GQLVariableValue) {
            if (coercedVariables.containsKey(it.name)) {
              coercedVariables.get(it.name)
            } else {
              // In lists, absent variables are coerced to null
              null
            }
          } else {
            coerceLiteralToInternal(schema, it, type.type, coercings, coercedVariables)
          }
        }
      } else {
        if (value is GQLVariableValue) {
          if (coercedVariables.containsKey(value.name)) {
            coercedVariables.get(value.name)
          } else {
            null
          }
        } else {
          // Single items are mapped to a list of 1
          listOf(coerceLiteralToInternal(schema, value, type.type, coercings, coercedVariables))
        }
      }
    }

    is GQLNamedType -> {
      val definition = schema.typeDefinition(type.name)
      when (definition) {
        is GQLEnumTypeDefinition -> {
          coerceEnumLiteralToInternal(value, coercings, definition)
        }

        is GQLInputObjectTypeDefinition -> {
          coerceInputObject(schema, definition, value, coercings, coercedVariables)
        }

        is GQLInterfaceTypeDefinition,
        is GQLObjectTypeDefinition,
        is GQLUnionTypeDefinition,
        -> {
          error("Output type '${definition.name}' cannot be used in input position")
        }

        is GQLScalarTypeDefinition -> {
          scalarCoercingParseLiteral(value, coercings, definition.name)
        }
      }
    }
  }
}

internal fun coerceEnumLiteralToInternal(value: GQLValue, coercings: Map>, definition: GQLEnumTypeDefinition): InternalValue {
  check(value is GQLEnumValue) {
    error("Don't know how to coerce '$value' to a '${definition.name}' enum value")
  }

  val coercing = coercings.get(definition.name)

  return if (coercing == null) {
    check(definition.enumValues.any { it.name == value.value }) {
      val possibleValues = definition.enumValues.map { it.name }.toSet()
      "'$value' cannot be coerced to a '${definition.name}' enum value. Possible values are: '$possibleValues'"
    }
    value.value
  } else {
    coercing.parseLiteral(value)
  }
}

private fun coerceInputObject(schema: Schema, definition: GQLInputObjectTypeDefinition, literalValue: GQLValue, coercings: Map>, coercedVariables: Map): InternalValue {
  if (literalValue !is GQLObjectValue) {
    error("Don't know how to coerce '$literalValue' to a '${definition.name}' input object")
  }

  val fields = literalValue.fields.associate { it.name to it.value }

  val map = definition.inputFields.mapNotNull { inputValueDefinition ->
    val inputFieldType = inputValueDefinition.type

    if (!fields.containsKey(inputValueDefinition.name)) {
      if (inputValueDefinition.defaultValue != null) {
        inputValueDefinition.name to inputValueDefinition.defaultValue!!.toInternalValue()
      } else {
        if (inputFieldType is GQLNonNullType) {
          error("Missing input field '${inputValueDefinition.name}")
        }
        // Skip this field
        null
      }
    } else {
      val inputFieldValue = fields.get(inputValueDefinition.name)!!
      inputValueDefinition.name to coerceLiteralToInternal(schema, inputFieldValue, inputValueDefinition.type, coercings, coercedVariables)
    }
  }.toMap()

  val coercing = coercings.get(definition.name)
  return if (coercing != null) {
    coercing.deserialize(map)
  } else {
    map
  }
}

internal fun coerceInputMaps(value: Any?, schema: Schema, expectedType: GQLType , coercings: Map>): InternalValue {
  if (value == null) {
    return null
  }

  return when (expectedType) {
    is GQLNonNullType -> {
      coerceInputMaps(value, schema, expectedType.type, coercings)
    }
    is GQLListType -> {
      check(value is List<*>)
      value.map { coerceInputMaps(it, schema, expectedType.type, coercings) }
    }
    is GQLNamedType -> {
      val definition = schema.typeDefinition(expectedType.name)
      if (definition is GQLInputObjectTypeDefinition) {
        val coercing = coercings.get(definition.name)
        return if (coercing != null) {
          coercing.deserialize(value)
        } else {
          value
        }
      } else {
        value
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy