Coverage Summary for Class: GhostReactiveEncoder (com.ghost.serialization.spring)

Class Class, % Method, % Branch, % Line, % Instruction, %
GhostReactiveEncoder 100% (1/1) 100% (6/6) 81.2% (13/16) 100% (22/22) 98.6% (137/139)


 package com.ghost.serialization.spring
 
 import com.ghost.serialization.Ghost
 import com.ghost.serialization.annotations.GhostSerialization
 import org.reactivestreams.Publisher
 import org.springframework.core.ResolvableType
 import org.springframework.core.codec.AbstractEncoder
 import org.springframework.core.io.buffer.DataBuffer
 import org.springframework.core.io.buffer.DataBufferFactory
 import org.springframework.util.MimeType
 import org.springframework.util.MimeTypeUtils
 import reactor.core.publisher.Flux
 import kotlin.reflect.KClass
 
 private const val NDJSON_NEWLINE: Byte = '\n'.code.toByte()
 
 /**
  * Reactive Encoder for Ghost Serialization.
  *
  * Serializes through the pooled [com.ghost.serialization.writer.GhostJsonFlatWriter]
  * to a [ByteArray] and wraps it with [DataBufferFactory.wrap], producing a
  * zero-copy [DataBuffer] backed by the serialized bytes directly.
  */
 class GhostReactiveEncoder : AbstractEncoder<Any>(
     MimeTypeUtils.APPLICATION_JSON,
     MimeType("application", "x-ndjson")
 ) {
     override fun canEncode(elementType: ResolvableType, mimeType: MimeType?): Boolean {
         val clazz = elementType.toClass()
         return super.canEncode(elementType, mimeType) &&
                 (clazz.isAnnotationPresent(GhostSerialization::class.java) ||
                         Ghost.getSerializer(clazz.kotlin) != null)
     }
 
     override fun encode(
         inputStream: Publisher<out Any>,
         bufferFactory: DataBufferFactory,
         elementType: ResolvableType,
         mimeType: MimeType?,
         hints: MutableMap<String, Any>?
     ): Flux<DataBuffer> {
         val isNdJson = isNdJson(mimeType)
 
         return Flux.from(inputStream).map { value ->
             encodeValue(value, bufferFactory, isNdJson)
         }
     }
 
     private fun encodeValue(
         value: Any,
         bufferFactory: DataBufferFactory,
         isNdJson: Boolean
     ): DataBuffer {
         @Suppress("UNCHECKED_CAST")
         val kClass = value::class as KClass<Any>
         val serializer = Ghost.getSerializer(kClass)
             ?: throw IllegalArgumentException(
                 "${Ghost.NOT_FOUND} ${kClass.simpleName}. ${Ghost.MISSING_ANN}"
             )
 
         val encoded = Ghost.encodeToBytes(serializer, value)
 
         if (!isNdJson) return bufferFactory.wrap(encoded)
 
         val bytes = ByteArray(encoded.size + 1)
         encoded.copyInto(bytes)
         bytes[encoded.size] = NDJSON_NEWLINE
         return bufferFactory.wrap(bytes)
     }
 
     private fun isNdJson(mimeType: MimeType?): Boolean {
         return mimeType?.subtype?.contains("ndjson") == true
     }
 }