From 52b17fe75f0d259efa702c7b87e658aa5ce322a7 Mon Sep 17 00:00:00 2001 From: Andrea Date: Thu, 28 Mar 2019 17:54:56 +0100 Subject: [PATCH] Use nicer custom type hints when serializing to json (websocket) --- .../fr/acinq/eclair/api/JsonSerializers.scala | 13 ++++++++++++- .../scala/fr/acinq/eclair/api/Service.scala | 18 ++++++++++-------- .../fr/acinq/eclair/api/ApiServiceSpec.scala | 10 +++++----- .../acinq/eclair/api/JsonSerializersSpec.scala | 5 +++-- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala index 347702301..c40d73f47 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/JsonSerializers.scala @@ -34,7 +34,7 @@ import fr.acinq.eclair.transactions.Transactions.{InputInfo, TransactionWithInpu import fr.acinq.eclair.wire._ import fr.acinq.eclair.{ShortChannelId, UInt64} import org.json4s.JsonAST._ -import org.json4s.{CustomKeySerializer, CustomSerializer, jackson} +import org.json4s.{CustomKeySerializer, CustomSerializer, TypeHints, jackson} import scodec.bits.ByteVector /** @@ -186,4 +186,15 @@ object JsonSupport extends Json4sSupport { implicit val shouldWritePretty: ShouldWritePretty = ShouldWritePretty.True + case class CustomTypeHints(custom: Map[Class[_], String]) extends TypeHints { + val reverse: Map[String, Class[_]] = custom.map(_.swap) + + override val hints: List[Class[_]] = custom.keys.toList + override def hintFor(clazz: Class[_]): String = custom.getOrElse(clazz, { + throw new IllegalArgumentException(s"No type hint mapping found for $clazz") + }) + override def classFor(hint: String): Option[Class[_]] = reverse.get(hint) + } + + } \ No newline at end of file diff --git a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala index d88a794da..f547ae5a2 100644 --- a/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala +++ b/eclair-core/src/main/scala/fr/acinq/eclair/api/Service.scala @@ -31,13 +31,15 @@ import akka.http.scaladsl.model.ws.{Message, TextMessage} import akka.http.scaladsl.server.directives.{Credentials, LoggingMagnet} import akka.stream.{ActorMaterializer, OverflowStrategy} import akka.stream.scaladsl.{BroadcastHub, Flow, Keep, Source} +import fr.acinq.eclair.api.JsonSupport.CustomTypeHints import fr.acinq.eclair.io.NodeURI import fr.acinq.eclair.payment.PaymentLifecycle.PaymentFailed import fr.acinq.eclair.payment._ import grizzled.slf4j.Logging -import org.json4s.ShortTypeHints +import org.json4s.{ShortTypeHints, TypeHints} import org.json4s.jackson.Serialization import scodec.bits.ByteVector + import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ @@ -51,13 +53,13 @@ trait Service extends Directives with Logging { import JsonSupport.serialization // used to send typed messages over the websocket val formatsWithTypeHint = formats.withTypeHintFieldName("type") + - ShortTypeHints(List( - classOf[PaymentSent], - classOf[PaymentRelayed], - classOf[PaymentReceived], - classOf[PaymentSettlingOnChain], - classOf[PaymentFailed])) - + CustomTypeHints(Map( + classOf[PaymentSent] -> "payment-sent", + classOf[PaymentRelayed] -> "payment-relayed", + classOf[PaymentReceived] -> "payment-received", + classOf[PaymentSettlingOnChain] -> "payment-settling-onchain", + classOf[PaymentFailed] -> "payment-failed" + )) def password: String diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala index 942cf9ec4..2b926bfb2 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/ApiServiceSpec.scala @@ -270,31 +270,31 @@ class ApiServiceSpec extends FunSuite with ScalatestRouteTest { check { val pf = PaymentFailed(ByteVector32.Zeroes, failures = Seq.empty) - val expectedSerializedPf = """{"type":"PaymentLifecycle$PaymentFailed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[]}""" + val expectedSerializedPf = """{"type":"payment-failed","paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","failures":[]}""" Serialization.write(pf)(mockService.formatsWithTypeHint) === expectedSerializedPf system.eventStream.publish(pf) wsClient.expectMessage(expectedSerializedPf) val ps = PaymentSent(amount = MilliSatoshi(21), feesPaid = MilliSatoshi(1), paymentHash = ByteVector32.Zeroes, paymentPreimage = ByteVector32.One, toChannelId = ByteVector32.Zeroes, timestamp = 1553784337711L) - val expectedSerializedPs = """{"type":"PaymentSent","amount":21,"feesPaid":1,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784337711}""" + val expectedSerializedPs = """{"type":"payment-sent","amount":21,"feesPaid":1,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","paymentPreimage":"0100000000000000000000000000000000000000000000000000000000000000","toChannelId":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":1553784337711}""" Serialization.write(ps)(mockService.formatsWithTypeHint) === expectedSerializedPs system.eventStream.publish(ps) wsClient.expectMessage(expectedSerializedPs) val prel = PaymentRelayed(amountIn = MilliSatoshi(21), amountOut = MilliSatoshi(20), paymentHash = ByteVector32.Zeroes, fromChannelId = ByteVector32.Zeroes, ByteVector32.One, timestamp = 1553784963659L) - val expectedSerializedPrel = """{"type":"PaymentRelayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" + val expectedSerializedPrel = """{"type":"payment-relayed","amountIn":21,"amountOut":20,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0000000000000000000000000000000000000000000000000000000000000000","toChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" Serialization.write(prel)(mockService.formatsWithTypeHint) === expectedSerializedPrel system.eventStream.publish(prel) wsClient.expectMessage(expectedSerializedPrel) val precv = PaymentReceived(amount = MilliSatoshi(21), paymentHash = ByteVector32.Zeroes, fromChannelId = ByteVector32.One, timestamp = 1553784963659L) - val expectedSerializedPrecv = """{"type":"PaymentReceived","amount":21,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" + val expectedSerializedPrecv = """{"type":"payment-received","amount":21,"paymentHash":"0000000000000000000000000000000000000000000000000000000000000000","fromChannelId":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553784963659}""" Serialization.write(precv)(mockService.formatsWithTypeHint) === expectedSerializedPrecv system.eventStream.publish(precv) wsClient.expectMessage(expectedSerializedPrecv) val pset = PaymentSettlingOnChain(amount = MilliSatoshi(21), paymentHash = ByteVector32.One, timestamp = 1553785442676L) - val expectedSerializedPset = """{"type":"PaymentSettlingOnChain","amount":21,"paymentHash":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553785442676}""" + val expectedSerializedPset = """{"type":"payment-settling-onchain","amount":21,"paymentHash":"0100000000000000000000000000000000000000000000000000000000000000","timestamp":1553785442676}""" Serialization.write(pset)(mockService.formatsWithTypeHint) === expectedSerializedPset system.eventStream.publish(pset) wsClient.expectMessage(expectedSerializedPset) diff --git a/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala b/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala index 97e102662..a61acdba0 100644 --- a/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala +++ b/eclair-core/src/test/scala/fr/acinq/eclair/api/JsonSerializersSpec.scala @@ -22,6 +22,7 @@ import fr.acinq.bitcoin.{MilliSatoshi, OutPoint} import fr.acinq.eclair._ import fr.acinq.eclair.payment.{PaymentRequest, PaymentSettlingOnChain} import fr.acinq.bitcoin.{ByteVector32, OutPoint} +import fr.acinq.eclair.api.JsonSupport.CustomTypeHints import fr.acinq.eclair.payment.PaymentRequest import fr.acinq.eclair.transactions.{IN, OUT} import fr.acinq.eclair.wire.{NodeAddress, Tor2, Tor3} @@ -77,8 +78,8 @@ class JsonSerializersSpec extends FunSuite with Matchers { } test("type hints") { - implicit val formats = DefaultFormats.withTypeHintFieldName("type") + ShortTypeHints(List(classOf[PaymentSettlingOnChain])) + new MilliSatoshiSerializer + implicit val formats = DefaultFormats.withTypeHintFieldName("type") + CustomTypeHints(Map(classOf[PaymentSettlingOnChain] -> "payment-settling-onchain")) + new MilliSatoshiSerializer val e1 = PaymentSettlingOnChain(MilliSatoshi(42), randomBytes32) - assert(Serialization.writePretty(e1).contains("\"type\" : \"PaymentSettlingOnChain\"")) + assert(Serialization.writePretty(e1).contains("\"type\" : \"payment-settling-onchain\"")) } }