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

com.apollographql.apollo3.ast.check_key_fields.kt Maven / Gradle / Ivy

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

private class CheckKeyFieldsScope(
    val schema: Schema,
    val allFragmentDefinitions: Map,
)

fun checkKeyFields(operation: GQLOperationDefinition, schema: Schema, allFragmentDefinitions: Map) {
  val parentType = operation.rootTypeDefinition(schema)!!.name
  CheckKeyFieldsScope(schema, allFragmentDefinitions).checkField("Operation(${operation.name})", operation.selectionSet.selections, parentType)
}

fun checkKeyFields(fragmentDefinition: GQLFragmentDefinition, schema: Schema, allFragmentDefinitions: Map) {
  CheckKeyFieldsScope(schema, allFragmentDefinitions).checkField("Fragment(${fragmentDefinition.name})", fragmentDefinition.selectionSet.selections, fragmentDefinition.typeCondition.name)
}

private fun CheckKeyFieldsScope.checkField(
    path: String,
    selections: List,
    parentType: String,
) {
  schema.typeDefinitions.values.filterIsInstance().forEach {
    checkFieldSet(path, selections, parentType, it.name)
  }
}

private fun CheckKeyFieldsScope.checkFieldSet(path: String, selections: List, parentType: String, possibleType: String) {
  val implementedTypes = schema.implementedTypes(possibleType)

  val mergedFields = collectFields(selections, parentType, implementedTypes).groupBy {
    it.field.name
  }.values

  if (implementedTypes.contains(parentType)) {
    // only check types that are actually possible
    val fieldNames = mergedFields.map { it.first().field }
        .filter { it.alias == null }
        .map { it.name }.toSet()
    val keyFieldNames = schema.keyFields(possibleType)

    val missingFieldNames = keyFieldNames.subtract(fieldNames)
    check(missingFieldNames.isEmpty()) {
      "Key Field(s) '$missingFieldNames' are not queried on $possibleType at $path"
    }
  }

  mergedFields.forEach {
    val first = it.first()
    val rawTypeName = first.field.definitionFromScope(schema, first.parentType)!!.type.leafType().name
    checkField(path + "." + first.field.name, it.flatMap { it.field.selectionSet?.selections ?: emptyList() }, rawTypeName)
  }
}

private class FieldWithParent(val field: GQLField, val parentType: String)

private fun CheckKeyFieldsScope.collectFields(
    selections: List,
    parentType: String,
    implementedTypes: Set,
): List {
  if (!implementedTypes.contains(parentType)) {
    return emptyList()
  }
  return selections.flatMap {
    when (it) {
      is GQLField -> {
        if (it.directives.hasCondition()) {
          return@flatMap emptyList()
        }

        listOf(FieldWithParent(it, parentType))
      }
      is GQLInlineFragment -> {
        if (it.directives.hasCondition()) {
          return@flatMap emptyList()
        }

        collectFields(it.selectionSet.selections, it.typeCondition.name, implementedTypes)
      }
      is GQLFragmentSpread -> {
        if (it.directives.hasCondition()) {
          return@flatMap emptyList()
        }

        val fragmentDefinition = allFragmentDefinitions[it.name]!!
        collectFields(fragmentDefinition.selectionSet.selections, fragmentDefinition.typeCondition.name, implementedTypes)
      }
    }
  }
}

private fun List?.hasCondition(): Boolean {
  return this?.any {
    it.name == "skip" && (it.arguments!!.arguments.first().value as? GQLStringValue)?.value != "false"
        || it.name == "include" && (it.arguments!!.arguments.first().value as? GQLStringValue)?.value != "true"
  } ?: false
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy