Skip to content

Instantly share code, notes, and snippets.

@1gravity
Created April 12, 2021 15:24
Show Gist options
  • Save 1gravity/be9a071006a86704a130270bc5f5ee25 to your computer and use it in GitHub Desktop.
Save 1gravity/be9a071006a86704a130270bc5f5ee25 to your computer and use it in GitHub Desktop.
Moshi as Ktor ContentNegotiation feature
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.JsonEncodingException
import com.squareup.moshi.Moshi
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.request.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import io.ktor.utils.io.jvm.javaio.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.buffer
import okio.source
// we can suppress BlockingMethodInNonBlockingContext, it's a compiler not a code problem
@Suppress("BlockingMethodInNonBlockingContext")
class MoshiContentConverter(private val moshi: Moshi = Moshi.Builder().build()) : ContentConverter {
override suspend fun convertForReceive(context: PipelineContext<ApplicationReceiveRequest, ApplicationCall>): Any? {
val result = runCatching {
val request = context.subject
val channel = request.value as? ByteReadChannel ?: return null
return withContext(Dispatchers.IO) {
val source = channel.toInputStream().source().buffer()
val type = request.type
moshi.adapter(type.javaObjectType).fromJson(source)
}
}
return if (result.isFailure) {
when (val ex = result.exceptionOrNull()) {
is JsonDataException -> throw IllegalArgumentException("Incorrect json format: ${ex.message}")
is JsonEncodingException -> throw IllegalArgumentException("Invalid json format: ${ex.message}")
else -> throw IllegalArgumentException(ex?.message ?: "")
}
} else {
result.getOrNull()
}
}
override suspend fun convertForSend(context: PipelineContext<Any, ApplicationCall>, contentType: ContentType, value: Any): Any {
return TextContent(moshi.adapter(value.javaClass).toJson(value), contentType.withCharset(context.call.suitableCharset()))
}
}
/**
* Creates a new Moshi instance and registers it as a content converter for
* `application/json` data. The supplied block is used to configure the builder.
*/
fun ContentNegotiation.Configuration.moshi(block: Moshi.Builder.() -> Unit) {
val builder = Moshi.Builder().apply(block).build()
val converter = MoshiContentConverter(builder)
register(ContentType.Application.Json, converter)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment