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

com.apollographql.execution.processor.codegen.CoercingsBuilder.kt Maven / Gradle / Ivy

The newest version!
package com.apollographql.execution.processor.codegen

import com.apollographql.execution.processor.codegen.KotlinSymbols.AstEnumValue
import com.apollographql.execution.processor.codegen.KotlinSymbols.AstValue
import com.apollographql.execution.processor.sir.*
import com.apollographql.execution.processor.sir.SirDefinition
import com.apollographql.execution.processor.sir.SirEnumDefinition
import com.apollographql.execution.processor.sir.SirInputObjectDefinition
import com.apollographql.execution.processor.sir.SirNonNullType
import com.apollographql.execution.processor.sir.asKotlinPoet
import com.google.devtools.ksp.processing.KSPLogger
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.MemberName
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeVariableName
import java.util.Locale

internal fun String.capitalizeFirstLetter(): String {
  return this.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() }
}

internal fun String.decapitalizeFirstLetter(): String {
  return this.replaceFirstChar { if (it.isUpperCase()) it.lowercase(Locale.ROOT) else it.toString() }
}

internal class CoercingsBuilder(
  private val context: KotlinExecutableSchemaContext,
  serviceName: String,
  sirDefinitions: List,
  private val logger: KSPLogger,
) : CgFileBuilder {
  private val sirEnumDefinitions = sirDefinitions.filterIsInstance()
  private val sirInputObjectDefinitions = sirDefinitions.filterIsInstance()
  val fileName = "${serviceName}Coercings".capitalizeFirstLetter()
  override fun prepare() {
    sirEnumDefinitions.forEach {
      context.coercings.put(it.name, MemberName(context.packageName, it.name.coercingName()))
    }
    sirInputObjectDefinitions.forEach {
      context.coercings.put(it.name, MemberName(context.packageName, it.name.coercingName()))
    }
  }

  private fun String.coercingName() = "${this}Coercing".decapitalizeFirstLetter()

  override fun build(): FileSpec {

    val funSpecs = if (sirEnumDefinitions.isNotEmpty() || sirInputObjectDefinitions.isNotEmpty()) {
      listOf(getInputFunSpec(), getRequiredInputFunSpec())
    } else {
      emptyList()
    }
    return FileSpec.builder(packageName = context.packageName, fileName = fileName)
      .apply {
        funSpecs.forEach {
          addFunction(it)
        }
        sirEnumDefinitions.map {
          it.propertySpec()
        }.forEach {
          addProperty(it)
        }
        sirInputObjectDefinitions.map {
          it.propertySpec()
        }.forEach {
          addProperty(it)
        }
      }
      .build()
  }

  private fun inputFunSpecInternal(name: String, block: CodeBlock.Builder.() -> Unit): FunSpec {
    return FunSpec.builder(name)
      .addAnnotation(AnnotationSpec.builder(KotlinSymbols.Suppress).addMember("\"UNCHECKED_CAST\"").build())
      .addTypeVariable(TypeVariableName("T"))
      .receiver(KotlinSymbols.Any.copy(nullable = true))
      .addModifiers(KModifier.PRIVATE)
      .addParameter(ParameterSpec("key", KotlinSymbols.String))
      .returns(TypeVariableName("T"))
      .addCode(
        buildCode {
          add("if (this == null) return null as T\n")
          add("check(this is %T<*,*>)\n", KotlinSymbols.Map)
          block()
        }
      )
      .build()
  }

  private fun getInputFunSpec(): FunSpec {
    return inputFunSpecInternal("getInput") {
      add("return if(containsKey(key)) {\n")
      indent {
        add("%T(get(key))\n", KotlinSymbols.Present)
      }
      add("} else {\n")
      indent {
        add("%T\n", KotlinSymbols.Absent)
      }
      add("} as T\n")
    }
  }

  private fun getRequiredInputFunSpec(): FunSpec {
    return inputFunSpecInternal("getRequiredInput") {
      add("return get(key) as T\n")
    }
  }

  private fun SirEnumDefinition.propertySpec(): PropertySpec {
    return PropertySpec.builder(
      name.coercingName(),
      KotlinSymbols.Coercing.parameterizedBy(targetClassName.asKotlinPoet())
    )
      .addModifiers(KModifier.INTERNAL)
      .initializer(
        buildCode {
          add("object: %T<%T> {\n", KotlinSymbols.Coercing, targetClassName.asKotlinPoet())
          indent {
            add("override fun serialize(internalValue: %T): Any?{\n", targetClassName.asKotlinPoet())
            indent {
              add("return internalValue.name\n")
            }
            add("}\n")
            add("override fun deserialize(value: Any?): %T {\n", targetClassName.asKotlinPoet())
            indent {
              add("return %T.valueOf(value.toString())\n", targetClassName.asKotlinPoet())
            }
            add("}\n")
            add("override fun parseLiteral(gqlValue: %T): %T {\n", AstValue, targetClassName.asKotlinPoet())
            indent {
              add("return %T.valueOf((gqlValue as %T).value)\n", targetClassName.asKotlinPoet(), AstEnumValue)
            }
            add("}\n")
          }
          add("}\n")
        }
      )
      .build()
  }

  private fun SirInputObjectDefinition.propertySpec(): PropertySpec {
    return PropertySpec.builder(
      name.coercingName(),
      KotlinSymbols.Coercing.parameterizedBy(targetClassName.asKotlinPoet())
    )
      .initializer(
        buildCode {
          add("object: %T<%T> {\n", KotlinSymbols.Coercing, targetClassName.asKotlinPoet())
          indent {
            add("override fun serialize(internalValue: %T): Any?{\n", targetClassName.asKotlinPoet())
            indent {
              add("error(\"Input objects cannot be serialized\")")
            }
            add("}\n")
            add("override fun deserialize(value: Any?): %T {\n", targetClassName.asKotlinPoet())
            indent {
              add("return %T(\n", targetClassName.asKotlinPoet())
              indent {
                inputFields.forEach {
                  val getInput = if (it.defaultValue == null && it.type !is SirNonNullType) {
                    "getInput"
                  } else {
                    "getRequiredInput"
                  }
                  add("%L = value.$getInput(%S),\n", it.name, it.name)
                }
              }
              add(")\n")
            }
            add("}\n")
            add("override fun parseLiteral(gqlValue: %T): %T {\n", AstValue, targetClassName.asKotlinPoet())
            indent {
              add("error(\"Input objects cannot be parsed from literals\")")
            }
            add("}\n")
          }
          add("}\n")
        }
      )
      .build()
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy