-
Notifications
You must be signed in to change notification settings - Fork 2
Bridging legacy api through armadillo
Kasper Kondzielski edited this page Nov 26, 2022
·
1 revision
Assuming that your legacy api is no the following form:
jsonRpcRouter: JsonRpcRequest => IO[JsonRpcResponse]
you can wire your legacy api through armadillo using following interceptor:
import cats.effect.IO
import io.iohk.armadillo.server.JsonSupport.Json
import io.iohk.armadillo.server.ServerInterpreter.{ResponseHandlingStatus, ServerResponse}
import io.iohk.armadillo.server.{EndpointInterceptor, JsonSupport, MethodHandler, MethodInterceptor, Responder}
import io.iohk.armadillo.{JsonRpcId, JsonRpcResponse => ArmadilloJsonRpcResponse}
import legacy.api.{JsonRpcError, JsonRpcRequest, JsonRpcResponse}
import org.json4s.JsonAST.{JArray, JValue}
import sttp.monad.MonadError
class LegacyGlueRequestInterceptor(jsonRpcRouter: JsonRpcRequest => IO[JsonRpcResponse])
extends MethodInterceptor[IO, JValue] {
private val jsonRpcErrorCodes: List[Int] =
List(JsonRpcError.InvalidRequest.code, JsonRpcError.ParseError.code, JsonRpcError.InvalidParams().code)
override def apply(
responder: Responder[IO, JValue],
jsonSupport: JsonSupport[JValue],
methodHandler: EndpointInterceptor[IO, JValue] => MethodHandler[IO, JValue]
): MethodHandler[IO, JValue] = {
val next = methodHandler(EndpointInterceptor.noop)
new MethodHandler[IO, JValue] {
override def onDecodeSuccess[I](ctx: MethodHandler.DecodeSuccessContext[IO, JValue])(implicit
monad: MonadError[IO]
): IO[ResponseHandlingStatus[JValue]] =
next.onDecodeSuccess(ctx).flatMap {
case ResponseHandlingStatus.Unhandled =>
ctx.request.id match {
case Some(_) =>
ctx.request.params match {
case Some(Json.JsonArray(params)) => callLegacyEndpoints(jsonSupport, ctx, params)
case None => callLegacyEndpoints(jsonSupport, ctx, Vector.empty)
case _ => monad.unit(ResponseHandlingStatus.Unhandled)
}
case None => monad.unit(ResponseHandlingStatus.Unhandled)
}
case actionTaken => monad.unit(actionTaken)
}
override def onDecodeFailure(ctx: MethodHandler.DecodeFailureContext[IO, JValue])(implicit
monad: MonadError[IO]
): IO[ResponseHandlingStatus[JValue]] =
next.onDecodeFailure(ctx)
}
}
private def callLegacyEndpoints(
jsonSupport: JsonSupport[JValue],
ctx: MethodHandler.DecodeSuccessContext[IO, JValue],
params: Vector[JValue]
): IO[ResponseHandlingStatus.Handled[JValue]] = {
val legacyResponse = jsonRpcRouter(
JsonRpcRequest(
ctx.request.jsonrpc,
ctx.request.method,
if (params.isEmpty) None else Some(JArray(params.toList)),
ctx.request.id.flatMap {
case JsonRpcId.IntId(value) => JsonRpcRequest.toId(value)
case JsonRpcId.StringId(value) => JsonRpcRequest.toId(value)
}
)
)
convertResponse(legacyResponse, jsonSupport)
}
private def convertResponse(
legacyResponse: IO[domain.JsonRpcResponse],
jsonSupport: JsonSupport[JValue]
): IO[ResponseHandlingStatus.Handled[JValue]] =
legacyResponse
.map { response =>
(response.result, response.error) match {
case (Some(result), None) =>
ServerResponse.Success(
jsonSupport.encodeResponse(
ArmadilloJsonRpcResponse.v2(
result,
response.id
.map {
case Left(value) => JsonRpcId.IntId(value)
case Right(value) => JsonRpcId.StringId(value)
}
.getOrElse(throw new IllegalStateException("cannot happen"))
)
)
)
case (None, Some(error)) =>
val json = jsonSupport.encodeResponse(
ArmadilloJsonRpcResponse.error_v2(
JsonRpcError.jsonRpcErrorEncoder.encodeJson(error),
response.id
.map {
case Left(value) => JsonRpcId.IntId(value)
case Right(value) => JsonRpcId.StringId(value)
}
)
)
if (jsonRpcErrorCodes.contains(error.code)) {
ServerResponse.Failure(json)
} else {
ServerResponse.Success(json)
}
case _ => throw new IllegalStateException("cannot happen")
}
}
.map(serverResponse => ResponseHandlingStatus.Handled(Some(serverResponse)))
}
Thanks to that trick your legacy endpoints will work together with new armadillo endpoints (also when called from batch requests!)