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

org.http4k.jsonrpc.JsonRpcService.kt Maven / Gradle / Ivy

package org.http4k.jsonrpc

import org.http4k.core.ContentType.Companion.APPLICATION_JSON
import org.http4k.core.Filter
import org.http4k.core.HttpHandler
import org.http4k.core.Method.POST
import org.http4k.core.Request
import org.http4k.core.Response
import org.http4k.core.Status.Companion.METHOD_NOT_ALLOWED
import org.http4k.core.Status.Companion.NO_CONTENT
import org.http4k.core.Status.Companion.OK
import org.http4k.core.then
import org.http4k.core.with
import org.http4k.filter.ServerFilters.CatchLensFailure
import org.http4k.format.Json
import org.http4k.format.JsonType
import org.http4k.format.JsonType.Array
import org.http4k.format.JsonType.Object
import org.http4k.jsonrpc.ErrorMessage.Companion.InternalError
import org.http4k.jsonrpc.ErrorMessage.Companion.InvalidParams
import org.http4k.jsonrpc.ErrorMessage.Companion.InvalidRequest
import org.http4k.jsonrpc.ErrorMessage.Companion.MethodNotFound
import org.http4k.jsonrpc.ErrorMessage.Companion.ParseError
import org.http4k.lens.ContentNegotiation.Companion.StrictNoDirective
import org.http4k.lens.Failure.Type.Invalid
import org.http4k.lens.Header.CONTENT_TYPE
import org.http4k.lens.LensFailure

data class JsonRpcService(
    private val json: Json,
    private val errorHandler: ErrorHandler,
    private val bindings: Iterable>
) : HttpHandler {

    private val jsonLens = json.body("JSON-RPC request", StrictNoDirective).toLens()
    private val methods = bindings.map { it.name to it.handler }.toMap()

    private val handler = CatchLensFailure { _ -> Response(OK).with(jsonLens of renderError(ParseError)) }
        .then(Filter { next -> { if (it.method == POST) next(it) else Response(METHOD_NOT_ALLOWED) } })
        .then {
            when (val responseJson = process(jsonLens(it))) {
                null -> Response(NO_CONTENT).with(CONTENT_TYPE of APPLICATION_JSON)
                else -> Response(OK).with(jsonLens of responseJson)
            }
        }

    override fun invoke(request: Request): Response = handler(request)

    private fun process(requestJson: NODE): NODE? = when (json.typeOf(requestJson)) {
        Object -> processSingleRequest(json.fields(requestJson).toMap())
        Array -> processBatchRequest(json.elements(requestJson).toList())
        else -> renderError(InvalidRequest)
    }

    private fun processSingleRequest(fields: Map) =
        JsonRpcRequest(json, fields).mapIfValid { request ->
            try {
                when (val method = methods[request.method]) {
                    null -> renderError(MethodNotFound, request.id)
                    else -> with(method(request.params ?: json.nullNode())) {
                        request.id?.let { renderResult(this, it) }
                    }
                }
            } catch (e: Exception) {
                when (e) {
                    is LensFailure -> {
                        val errorMessage = errorHandler(e.cause ?: e)
                            ?: if (e.overall() == Invalid) InvalidParams else InternalError
                        renderError(errorMessage, request.id)
                    }
                    else -> renderError(errorHandler(e) ?: InternalError, request.id)
                }
            }
        }

    private fun JsonRpcRequest.mapIfValid(block: (JsonRpcRequest) -> NODE?) = when {
        valid() -> block(this)
        else -> renderError(InvalidRequest, id)
    }

    private fun processBatchRequest(elements: List) = with(elements) {
        if (isNotEmpty()) processEachAsSingleRequest() else renderError(InvalidRequest)
    }

    private fun List.processEachAsSingleRequest() = json {
        mapNotNull {
            processSingleRequest(if (typeOf(it) == Object) fields(it).toMap() else emptyMap())
        }.takeIf { it.isNotEmpty() }?.let { array(it) }
    }

    private fun renderResult(result: NODE, id: NODE): NODE = json {
        obj(
            "jsonrpc" to string(jsonRpcVersion),
            "result" to result,
            "id" to id
        )
    }

    private fun renderError(errorMessage: ErrorMessage, id: NODE? = null) = json {
        obj(
            "jsonrpc" to string(jsonRpcVersion),
            "error" to errorMessage(this),
            "id" to (id ?: nullNode())
        )
    }
}

data class JsonRpcMethodBinding(val name: String, val handler: JsonRpcHandler)

typealias ErrorHandler = (Throwable) -> ErrorMessage?

private const val jsonRpcVersion: String = "2.0"

private class JsonRpcRequest(json: Json, fields: Map) {
    private var valid = (fields["jsonrpc"] ?: json.nullNode()).let {
        json.typeOf(it) == JsonType.String && jsonRpcVersion == json.text(it)
    }

    val method: String = (fields["method"] ?: json.nullNode()).let {
        if (json.typeOf(it) == JsonType.String) {
            json.text(it)
        } else {
            valid = false
            ""
        }
    }
    val params: NODE? = fields["params"]?.also {
        if (!setOf(Object, Array).contains(json.typeOf(it))) valid = false
    }

    val id: NODE? = fields["id"]?.let {
        if (!setOf(JsonType.String, JsonType.Number, JsonType.Integer, JsonType.Null).contains(json.typeOf(it))) {
            valid = false
            json.nullNode()
        } else it
    }

    fun valid() = valid
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy