Adds faster parsers for Messages and Commands

Unwraps inline runCatching because it seems faster for some reason.
This commit is contained in:
Vitor Pamplona
2025-12-22 14:52:02 -05:00
parent 2de4d49fd8
commit 79d6745ee6
9 changed files with 73 additions and 26 deletions
@@ -29,7 +29,6 @@ import com.vitorpamplona.quartz.nip01Core.crypto.verifyId
import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
import com.vitorpamplona.quartz.utils.EventFactory
import com.vitorpamplona.quartz.utils.TimeUtils
@@ -49,36 +48,36 @@ class EventBenchmark {
@get:Rule val benchmarkRule = BenchmarkRule()
@Test
fun parseComplete() {
fun parseVerify() {
benchmarkRule.measureRepeated {
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event
assertTrue(event.verify())
}
}
@Test
fun parseREQString() {
fun parseREQTree() {
benchmarkRule.measureRepeated { JacksonMapper.mapper.readTree(reqResponseEvent) }
}
@Test
fun parseREQString2() {
fun parseREQTree2() {
benchmarkRule.measureRepeated { JacksonMapper.mapper.readTree(reqResponseEvent2) }
}
@Test
fun parseREQStringComplete() {
benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) }
fun parseREQString() {
benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) }
}
@Test
fun parseREQStringComplete2() {
benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent2) }
fun parseREQString2() {
benchmarkRule.measureRepeated { OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent2) }
}
@Test
fun checkId() {
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event
benchmarkRule.measureRepeated {
// Should pass
assertTrue(event.verifyId())
@@ -87,7 +86,7 @@ class EventBenchmark {
@Test
fun checkSignature() {
val event = (OptimizedJsonMapper.fromJsonTo<Message>(reqResponseEvent) as EventMessage).event
val event = (OptimizedJsonMapper.fromJsonToMessage(reqResponseEvent) as EventMessage).event
benchmarkRule.measureRepeated {
// Should pass
assertTrue(event.verifySignature())
@@ -27,7 +27,6 @@ import com.vitorpamplona.quartz.nip01Core.crypto.checkSignature
import com.vitorpamplona.quartz.nip01Core.crypto.verifyId
import com.vitorpamplona.quartz.nip01Core.crypto.verifySignature
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip25Reactions.ReactionEvent
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
import com.vitorpamplona.quartz.nip99Classifieds.ClassifiedsEvent
@@ -64,7 +63,7 @@ class EventSigCheck {
@Test
fun testUnicode2028and2029ShouldNotBeEscaped() {
val msg = OptimizedJsonMapper.fromJsonTo<Message>(payload1) as EventMessage
val msg = OptimizedJsonMapper.fromJsonToMessage(payload1) as EventMessage
// Should pass
msg.event.checkSignature()
@@ -20,6 +20,8 @@
*/
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
import kotlinx.serialization.json.Json
@@ -31,6 +33,10 @@ expect object OptimizedJsonMapper {
fun toJson(event: Event): String
fun fromJsonToMessage(json: String): Message
fun fromJsonToCommand(json: String): Command
fun fromJsonToTagArray(json: String): Array<Array<String>>
fun fromJsonToEventTemplate(json: String): EventTemplate<Event>
@@ -24,7 +24,6 @@ import com.vitorpamplona.quartz.nip01Core.core.OptimizedJsonMapper
import com.vitorpamplona.quartz.nip01Core.relay.client.listeners.IRelayClientListener
import com.vitorpamplona.quartz.nip01Core.relay.client.single.IRelayClient
import com.vitorpamplona.quartz.nip01Core.relay.client.single.basic.BasicRelayClient.Companion.DELAY_TO_RECONNECT_IN_SECS
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.sockets.WebSocket
@@ -124,7 +123,7 @@ open class BasicRelayClient(
override fun onMessage(text: String) {
try {
val msg = OptimizedJsonMapper.fromJsonTo<Message>(text)
val msg = OptimizedJsonMapper.fromJsonToMessage(text)
listener.onIncomingMessage(this@BasicRelayClient, text, msg)
} catch (e: Throwable) {
if (e is CancellationException) throw e
@@ -49,7 +49,7 @@ class Rumor(
}
companion object {
fun fromJson(json: String): Rumor = OptimizedJsonMapper.fromJsonTo<Rumor>(json)
fun fromJson(json: String): Rumor = OptimizedJsonMapper.fromJsonToRumor(json)
fun toJson(event: Rumor): String = OptimizedJsonMapper.toJson(event)
@@ -20,6 +20,8 @@
*/
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
@@ -28,6 +30,10 @@ actual object OptimizedJsonMapper {
actual fun toJson(event: Event): String = TODO("Not yet implemented")
actual fun fromJsonToMessage(json: String): Message = TODO("Not yet implemented")
actual fun fromJsonToCommand(json: String): Command = TODO("Not yet implemented")
actual fun fromJsonToTagArray(json: String): Array<Array<String>> = TODO("Not yet implemented")
actual fun fromJsonToEventTemplate(json: String): EventTemplate<Event> = TODO("Not yet implemented")
@@ -21,31 +21,64 @@
package com.vitorpamplona.quartz.nip01Core.core
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
actual object OptimizedJsonMapper {
inline fun <T> runCatching(parsingAction: () -> T): T =
actual fun fromJson(json: String): Event =
try {
parsingAction()
// wraps jackson errors
JacksonMapper.fromJson(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun fromJson(json: String): Event = runCatching { JacksonMapper.fromJson(json) }
actual fun toJson(event: Event) = JacksonMapper.toJson(event)
actual fun fromJsonToTagArray(json: String): Array<Array<String>> = runCatching { JacksonMapper.fromJsonToTagArray(json) }
actual fun fromJsonToMessage(json: String): Message =
try {
JacksonMapper.fromJsonToMessage(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun fromJsonToRumor(json: String): Rumor = runCatching { JacksonMapper.fromJsonToRumor(json) }
actual fun fromJsonToCommand(json: String): Command =
try {
JacksonMapper.fromJsonToCommand(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun fromJsonToEventTemplate(json: String): EventTemplate<Event> = runCatching { JacksonMapper.fromJsonToEventTemplate(json) }
actual fun fromJsonToTagArray(json: String): Array<Array<String>> =
try {
JacksonMapper.fromJsonToTagArray(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun fromJsonToRumor(json: String): Rumor =
try {
JacksonMapper.fromJsonToRumor(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun fromJsonToEventTemplate(json: String): EventTemplate<Event> =
try {
JacksonMapper.fromJsonToEventTemplate(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun toJson(tags: Array<Array<String>>): String = JacksonMapper.toJson(tags)
actual inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T = runCatching { JacksonMapper.fromJsonTo<T>(json) }
actual inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T =
try {
JacksonMapper.fromJsonTo<T>(json)
} catch (e: com.fasterxml.jackson.core.JsonParseException) {
throw IllegalArgumentException(e.message, e)
}
actual fun toJson(value: OptimizedSerializable): String = JacksonMapper.toJson(value)
}
@@ -63,7 +63,6 @@ class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
}
}
// NPE on purpose. If the object isn't fully filled, it should throw.
return EventFactory.create(id, pubKey, createdAt, kind, tags, content, sig)
}
}
@@ -109,9 +109,15 @@ class JacksonMapper {
val tagArrayTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<TagArray>())
val rumorTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Rumor>())
val eventTemplateTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<EventTemplate<Event>>())
val messageTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Message>())
val commandTypeInstance: JavaType = mapper.typeFactory.constructType(jacksonTypeRef<Command>())
fun fromJson(json: String): Event = mapper.readValue(json, eventTypeInstance)
fun fromJsonToMessage(json: String): Message = mapper.readValue(json, messageTypeInstance)
fun fromJsonToCommand(json: String): Command = mapper.readValue(json, commandTypeInstance)
fun fromJsonToTagArray(json: String): TagArray = mapper.readValue(json, tagArrayTypeInstance)
fun fromJsonToRumor(json: String): Rumor = mapper.readValue(json, rumorTypeInstance)