Coverage Summary for Class: EnvelopeRouterEmitterKt (com.ghost.serialization.compiler)
| Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
| EnvelopeRouterEmitterKt |
100%
(1/1)
|
100%
(1/1)
|
|
100%
(3/3)
|
100%
(51/51)
|
package com.ghost.serialization.compiler
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.ghost.serialization.compiler.GhostEmitterConstants as C
private val nullableAnyType = ClassName(C.PKG_KOTLIN, C.STR_TYPE_ANY).copy(nullable = true)
private val rawJsonType = ClassName(C.PKG_TYPES, C.STR_RAW_JSON_TYPE).copy(nullable = true)
private val ghostSerializerType = ClassName(C.PKG_CONTRACT, C.STR_GHOST_SERIALIZER)
/**
* Emits zero-copy envelope routing helpers on generated serializer companions.
*/
internal class EnvelopeRouterEmitter(
private val envelope: GhostEnvelopeModel,
private val originalClassName: ClassName,
private val flatReaderClass: ClassName
) {
private val typedMappings = envelope.payloadMappings.filter { it.targetType != null }
fun emit(typeSpecBuilder: TypeSpec.Builder) {
emitCachedTargetSerializers(typeSpecBuilder)
typeSpecBuilder.addFunction(buildRoutePayloadFunction())
typeSpecBuilder.addFunction(buildParsePayloadFunction())
if (typedMappings.isNotEmpty()) {
typeSpecBuilder.addFunction(buildRouteTypedFunction())
typeSpecBuilder.addFunction(buildParseTypedFunction())
}
}
private fun emitCachedTargetSerializers(typeSpecBuilder: TypeSpec.Builder) {
val ghostClass = ClassName(C.PKG_GHOST, C.STR_GHOST)
typedMappings.forEach { mapping ->
val targetType = mapping.targetType ?: return@forEach
typeSpecBuilder.addProperty(
PropertySpec.builder(targetSerializerPropertyName(mapping), ghostSerializerType.parameterizedBy(targetType))
.addModifiers(KModifier.PRIVATE)
.initializer(C.TEMPLATE_ENVELOPE_CACHED_SERIALIZER, ghostClass, targetType)
.build()
)
}
}
private fun buildRoutePayloadFunction(): FunSpec {
val builder = FunSpec.builder(C.STR_FUN_ROUTE_PAYLOAD)
.addKdoc(C.STR_KDOC_ROUTE_PAYLOAD, originalClassName)
.addParameter(C.STR_PARAM_ENVELOPE, originalClassName)
.returns(rawJsonType)
if (envelope.isGenericMode && envelope.payloadMappings.isEmpty()) {
builder.addStatement(C.STR_ENVELOPE_RETURN_FIELD, envelope.genericDataKotlinName!!)
} else {
builder.addCode(buildRouteWhenBlock(typed = false))
}
return builder.build()
}
private fun buildParsePayloadFunction(): FunSpec {
return FunSpec.builder(C.STR_FUN_PARSE_PAYLOAD)
.addKdoc(C.STR_KDOC_PARSE_PAYLOAD, originalClassName)
.addParameter(C.STR_PARAM_BYTES, ByteArray::class)
.returns(rawJsonType)
.addCode(
CodeBlock.of(
C.STR_ENVELOPE_PARSE_BYTES_ROUTE,
flatReaderClass,
C.STR_PARAM_BYTES,
C.STR_FUN_ROUTE_PAYLOAD
)
)
.build()
}
private fun buildRouteTypedFunction(): FunSpec {
val builder = FunSpec.builder(C.STR_FUN_ROUTE_TYPED)
.addKdoc(C.STR_KDOC_ROUTE_TYPED, originalClassName)
.addParameter(C.STR_PARAM_ENVELOPE, originalClassName)
.returns(nullableAnyType)
if (envelope.isGenericMode && typedMappings.isEmpty()) {
builder.addStatement(C.STR_ENVELOPE_RETURN_FIELD, envelope.genericDataKotlinName!!)
} else {
builder.addCode(buildRouteWhenBlock(typed = true))
}
return builder.build()
}
private fun buildParseTypedFunction(): FunSpec {
return FunSpec.builder(C.STR_FUN_PARSE_TYPED)
.addKdoc(C.STR_KDOC_PARSE_TYPED, originalClassName)
.addParameter(C.STR_PARAM_BYTES, ByteArray::class)
.returns(nullableAnyType)
.addCode(
CodeBlock.of(
C.STR_ENVELOPE_PARSE_BYTES_ROUTE,
flatReaderClass,
C.STR_PARAM_BYTES,
C.STR_FUN_ROUTE_TYPED
)
)
.build()
}
private fun buildRouteWhenBlock(typed: Boolean): CodeBlock {
val builder = CodeBlock.builder()
.add(C.STR_ENVELOPE_ROUTE_WHEN_OPEN, envelope.discriminatorKotlinName)
if (envelope.isGenericMode) {
val dataField = envelope.genericDataKotlinName!!
envelope.payloadMappings.forEach { mapping ->
builder.add(
C.STR_ENVELOPE_BRANCH,
mapping.discriminatorValue,
payloadExpression(mapping, dataField, typed)
)
}
builder.add(C.STR_ENVELOPE_ELSE_GENERIC, dataField)
} else {
envelope.payloadMappings.forEach { mapping ->
builder.add(
C.STR_ENVELOPE_BRANCH,
mapping.discriminatorValue,
payloadExpression(mapping, mapping.kotlinName, typed)
)
}
val fallback = envelope.fallbackMapping
if (fallback != null) {
builder.add(C.STR_ENVELOPE_ELSE_FALLBACK, fallback.kotlinName)
} else {
builder.add(C.STR_ENVELOPE_ELSE_NULL)
}
}
builder.add(C.STR_ENVELOPE_ROUTE_CLOSE)
return builder.build()
}
private fun payloadExpression(
mapping: EnvelopePayloadMapping,
fieldName: String,
typed: Boolean
): CodeBlock {
if (!typed || mapping.targetType == null) {
return CodeBlock.of(C.TEMPLATE_ENVELOPE_FIELD_ACCESS, fieldName)
}
return if (mapping.isRawJson) {
CodeBlock.of(
C.TEMPLATE_ENVELOPE_TYPED_SERIALIZER,
fieldName,
ClassName(C.PKG_TYPES, C.STR_RAW_JSON_DECODE),
targetSerializerPropertyName(mapping)
)
} else {
CodeBlock.of(C.TEMPLATE_ENVELOPE_FIELD_ACCESS, fieldName)
}
}
private fun targetSerializerPropertyName(mapping: EnvelopePayloadMapping): String =
mapping.kotlinName + C.STR_ENVELOPE_TARGET_SERIALIZER_SUFFIX
}