Merge pull request #1854 from vitorpamplona/claude/kotlin-serialization-mapper-4WYOP
Add Kotlin Serialization support for Nostr protocol types
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.AuthCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CloseCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CountCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip42RelayAuth.RelayAuthEvent
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object CommandKSerializer : KSerializer<Command> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Command")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Command,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonArray {
|
||||
add(JsonPrimitive(value.label()))
|
||||
when (value) {
|
||||
is ReqCmd -> {
|
||||
add(JsonPrimitive(value.subId))
|
||||
for (filter in value.filters) {
|
||||
add(FilterKSerializer.serializeToElement(filter))
|
||||
}
|
||||
}
|
||||
is EventCmd -> {
|
||||
add(EventKSerializer.serializeToElement(value.event))
|
||||
}
|
||||
is CloseCmd -> {
|
||||
add(JsonPrimitive(value.subId))
|
||||
}
|
||||
is AuthCmd -> {
|
||||
add(EventKSerializer.serializeToElement(value.event))
|
||||
}
|
||||
is CountCmd -> {
|
||||
add(JsonPrimitive(value.queryId))
|
||||
for (filter in value.filters) {
|
||||
add(FilterKSerializer.serializeToElement(filter))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Command {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val array = jsonDecoder.decodeJsonElement().jsonArray
|
||||
val type = array[0].jsonPrimitive.content
|
||||
|
||||
return when (type) {
|
||||
ReqCmd.LABEL -> {
|
||||
val subId = array[1].jsonPrimitive.content
|
||||
val filters =
|
||||
(2 until array.size).map { i ->
|
||||
FilterKSerializer.deserializeFromElement(array[i].jsonObject)
|
||||
}
|
||||
ReqCmd(subId, filters)
|
||||
}
|
||||
CountCmd.LABEL -> {
|
||||
val queryId = array[1].jsonPrimitive.content
|
||||
val filters =
|
||||
(2 until array.size).map { i ->
|
||||
FilterKSerializer.deserializeFromElement(array[i].jsonObject)
|
||||
}
|
||||
CountCmd(queryId, filters)
|
||||
}
|
||||
EventCmd.LABEL -> {
|
||||
EventCmd(EventKSerializer.deserializeFromElement(array[1].jsonObject))
|
||||
}
|
||||
CloseCmd.LABEL -> {
|
||||
CloseCmd(array[1].jsonPrimitive.content)
|
||||
}
|
||||
AuthCmd.LABEL -> {
|
||||
AuthCmd(EventKSerializer.deserializeFromElement(array[1].jsonObject) as RelayAuthEvent)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Message $type is not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountResult
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object CountResultKSerializer : KSerializer<CountResult> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("CountResult") {
|
||||
element<Int>("count")
|
||||
element<Boolean>("pubkey")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: CountResult,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
jsonEncoder.encodeJsonElement(serializeToElement(value))
|
||||
}
|
||||
|
||||
fun serializeToElement(value: CountResult): JsonObject =
|
||||
buildJsonObject {
|
||||
put("count", value.count)
|
||||
// Matches Jackson's CountResultSerializer which writes "pubkey" for approximate
|
||||
put("pubkey", value.approximate)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): CountResult {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
return deserializeFromElement(jsonDecoder.decodeJsonElement().jsonObject)
|
||||
}
|
||||
|
||||
fun deserializeFromElement(jsonObject: JsonObject): CountResult =
|
||||
CountResult(
|
||||
count = jsonObject["count"]!!.jsonPrimitive.int,
|
||||
approximate = jsonObject["approximate"]?.jsonPrimitive?.boolean ?: false,
|
||||
)
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.utils.EventFactory
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object EventKSerializer : KSerializer<Event> {
|
||||
private val emptyTagArray = emptyArray<Array<String>>()
|
||||
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Event") {
|
||||
element<String>("id")
|
||||
element<String>("pubkey")
|
||||
element<Long>("created_at")
|
||||
element<Int>("kind")
|
||||
element("tags", TagArrayKSerializer.descriptor)
|
||||
element<String>("content")
|
||||
element<String>("sig")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Event,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
jsonEncoder.encodeJsonElement(serializeToElement(value))
|
||||
}
|
||||
|
||||
fun serializeToElement(event: Event): JsonObject =
|
||||
buildJsonObject {
|
||||
put("id", event.id)
|
||||
put("pubkey", event.pubKey)
|
||||
put("created_at", event.createdAt)
|
||||
put("kind", event.kind)
|
||||
put("tags", TagArrayKSerializer.serializeToElement(event.tags))
|
||||
put("content", event.content)
|
||||
put("sig", event.sig)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Event {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
return deserializeFromElement(jsonDecoder.decodeJsonElement().jsonObject)
|
||||
}
|
||||
|
||||
fun deserializeFromElement(jsonObject: JsonObject): Event {
|
||||
var id: HexKey = ""
|
||||
var pubKey: HexKey = ""
|
||||
var createdAt: Long = 0
|
||||
var kind: Kind = 0
|
||||
var tags: TagArray = emptyTagArray
|
||||
var content: String = ""
|
||||
var sig: HexKey = ""
|
||||
|
||||
for ((key, value) in jsonObject) {
|
||||
when (key) {
|
||||
"id" -> id = value.jsonPrimitive.content
|
||||
"pubkey" -> pubKey = value.jsonPrimitive.content
|
||||
"created_at" -> createdAt = value.jsonPrimitive.long
|
||||
"kind" -> kind = value.jsonPrimitive.int
|
||||
"tags" -> tags = TagArrayKSerializer.deserializeFromElement(value)
|
||||
"content" -> content = value.jsonPrimitive.content
|
||||
"sig" -> sig = value.jsonPrimitive.content
|
||||
}
|
||||
}
|
||||
|
||||
if (pubKey.isEmpty()) {
|
||||
throw IllegalArgumentException("Event not found")
|
||||
}
|
||||
|
||||
return EventFactory.create(id, pubKey, createdAt, kind, tags, content, sig)
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object EventTemplateKSerializer : KSerializer<EventTemplate<Event>> {
|
||||
private val emptyTagArray = emptyArray<Array<String>>()
|
||||
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("EventTemplate") {
|
||||
element<Long>("created_at")
|
||||
element<Int>("kind")
|
||||
element("tags", TagArrayKSerializer.descriptor)
|
||||
element<String>("content")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: EventTemplate<Event>,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonObject {
|
||||
put("created_at", value.createdAt)
|
||||
put("kind", value.kind)
|
||||
put("tags", TagArrayKSerializer.serializeToElement(value.tags))
|
||||
put("content", value.content)
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): EventTemplate<Event> {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
|
||||
var createdAt = 0L
|
||||
var kind = 0
|
||||
var tags: TagArray = emptyTagArray
|
||||
var content = ""
|
||||
|
||||
for ((key, value) in jsonObject) {
|
||||
when (key) {
|
||||
"created_at" -> createdAt = value.jsonPrimitive.long
|
||||
"kind" -> kind = value.jsonPrimitive.int
|
||||
"tags" -> tags = TagArrayKSerializer.deserializeFromElement(value)
|
||||
"content" -> content = value.jsonPrimitive.content
|
||||
}
|
||||
}
|
||||
|
||||
return EventTemplate(createdAt, kind, tags, content)
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object FilterKSerializer : KSerializer<Filter> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Filter")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Filter,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
jsonEncoder.encodeJsonElement(serializeToElement(value))
|
||||
}
|
||||
|
||||
fun serializeToElement(filter: Filter): JsonObject =
|
||||
buildJsonObject {
|
||||
filter.kinds?.let { kinds ->
|
||||
put(
|
||||
"kinds",
|
||||
buildJsonArray {
|
||||
for (k in kinds) add(JsonPrimitive(k))
|
||||
},
|
||||
)
|
||||
}
|
||||
filter.ids?.let { ids ->
|
||||
put(
|
||||
"ids",
|
||||
buildJsonArray {
|
||||
for (id in ids) add(JsonPrimitive(id))
|
||||
},
|
||||
)
|
||||
}
|
||||
filter.authors?.let { authors ->
|
||||
put(
|
||||
"authors",
|
||||
buildJsonArray {
|
||||
for (a in authors) add(JsonPrimitive(a))
|
||||
},
|
||||
)
|
||||
}
|
||||
filter.tags?.let { tags ->
|
||||
for ((key, values) in tags) {
|
||||
put(
|
||||
"#$key",
|
||||
buildJsonArray {
|
||||
for (v in values) add(JsonPrimitive(v))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
filter.tagsAll?.let { tagsAll ->
|
||||
for ((key, values) in tagsAll) {
|
||||
put(
|
||||
"&$key",
|
||||
buildJsonArray {
|
||||
for (v in values) add(JsonPrimitive(v))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
filter.since?.let { put("since", it) }
|
||||
filter.until?.let { put("until", it) }
|
||||
filter.limit?.let { put("limit", it) }
|
||||
filter.search?.let { put("search", it) }
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Filter {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
return deserializeFromElement(jsonDecoder.decodeJsonElement().jsonObject)
|
||||
}
|
||||
|
||||
fun deserializeFromElement(jsonObject: JsonObject): Filter {
|
||||
val tags = mutableMapOf<String, List<String>>()
|
||||
val tagsAll = mutableMapOf<String, List<String>>()
|
||||
|
||||
for ((key, value) in jsonObject) {
|
||||
when {
|
||||
key.startsWith("#") -> {
|
||||
tags[key.substring(1)] = value.jsonArray.mapNotNull { it.jsonPrimitive.content }
|
||||
}
|
||||
key.startsWith("&") -> {
|
||||
tagsAll[key.substring(1)] = value.jsonArray.mapNotNull { it.jsonPrimitive.content }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Filter(
|
||||
ids = jsonObject["ids"]?.jsonArray?.mapNotNull { it.jsonPrimitive.content },
|
||||
authors = jsonObject["authors"]?.jsonArray?.mapNotNull { it.jsonPrimitive.content },
|
||||
kinds = jsonObject["kinds"]?.jsonArray?.mapNotNull { it.jsonPrimitive.intOrNull },
|
||||
tags = tags.ifEmpty { null },
|
||||
tagsAll = tagsAll.ifEmpty { null },
|
||||
since = jsonObject["since"]?.jsonPrimitive?.longOrNull,
|
||||
until = jsonObject["until"]?.jsonPrimitive?.longOrNull,
|
||||
limit = jsonObject["limit"]?.jsonPrimitive?.intOrNull,
|
||||
search = jsonObject["search"]?.jsonPrimitive?.content,
|
||||
)
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.OptimizedSerializable
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.Command
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization.BunkerMessageKSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization.BunkerRequestKSerializer
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization.BunkerResponseKSerializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Notification
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization.Nip47NotificationKSerializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization.Nip47RequestKSerializer
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization.Nip47ResponseKSerializer
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.kotlinSerialization.RumorKSerializer
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class KotlinSerializationMapper {
|
||||
companion object {
|
||||
val json =
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
isLenient = true
|
||||
encodeDefaults = false
|
||||
}
|
||||
|
||||
fun fromJson(jsonStr: String): Event = json.decodeFromString(EventKSerializer, jsonStr)
|
||||
|
||||
fun fromJsonToMessage(jsonStr: String): Message = json.decodeFromString(MessageKSerializer, jsonStr)
|
||||
|
||||
fun fromJsonToCommand(jsonStr: String): Command = json.decodeFromString(CommandKSerializer, jsonStr)
|
||||
|
||||
fun fromJsonToTagArray(jsonStr: String): TagArray = json.decodeFromString(TagArrayKSerializer, jsonStr)
|
||||
|
||||
fun fromJsonToRumor(jsonStr: String): Rumor = json.decodeFromString(RumorKSerializer, jsonStr)
|
||||
|
||||
fun fromJsonToEventTemplate(jsonStr: String): EventTemplate<Event> = json.decodeFromString(EventTemplateKSerializer, jsonStr)
|
||||
|
||||
fun toJson(event: Event): String = json.encodeToString(EventKSerializer, event)
|
||||
|
||||
fun toJson(tags: TagArray): String = json.encodeToString(TagArrayKSerializer, tags)
|
||||
|
||||
fun toJson(value: OptimizedSerializable): String =
|
||||
when (value) {
|
||||
is Event -> json.encodeToString(EventKSerializer, value)
|
||||
is Filter -> json.encodeToString(FilterKSerializer, value)
|
||||
is Rumor -> json.encodeToString(RumorKSerializer, value)
|
||||
is EventTemplate<*> -> {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
json.encodeToString(EventTemplateKSerializer, value as EventTemplate<Event>)
|
||||
}
|
||||
is Message -> json.encodeToString(MessageKSerializer, value)
|
||||
is Command -> json.encodeToString(CommandKSerializer, value)
|
||||
is BunkerRequest -> json.encodeToString(BunkerRequestKSerializer, value)
|
||||
is BunkerResponse -> json.encodeToString(BunkerResponseKSerializer, value)
|
||||
is BunkerMessage -> json.encodeToString(BunkerMessageKSerializer, value)
|
||||
else -> throw IllegalArgumentException("Unsupported type: ${value::class}")
|
||||
}
|
||||
|
||||
inline fun <reified T : OptimizedSerializable> fromJsonTo(jsonStr: String): T {
|
||||
val result: Any =
|
||||
when (T::class) {
|
||||
Event::class -> fromJson(jsonStr)
|
||||
Filter::class -> json.decodeFromString(FilterKSerializer, jsonStr)
|
||||
Rumor::class -> fromJsonToRumor(jsonStr)
|
||||
EventTemplate::class -> fromJsonToEventTemplate(jsonStr)
|
||||
Message::class -> fromJsonToMessage(jsonStr)
|
||||
Command::class -> fromJsonToCommand(jsonStr)
|
||||
BunkerRequest::class -> json.decodeFromString(BunkerRequestKSerializer, jsonStr)
|
||||
BunkerResponse::class -> json.decodeFromString(BunkerResponseKSerializer, jsonStr)
|
||||
BunkerMessage::class -> json.decodeFromString(BunkerMessageKSerializer, jsonStr)
|
||||
Response::class -> json.decodeFromString(Nip47ResponseKSerializer, jsonStr)
|
||||
Request::class -> json.decodeFromString(Nip47RequestKSerializer, jsonStr)
|
||||
Notification::class -> json.decodeFromString(Nip47NotificationKSerializer, jsonStr)
|
||||
else -> throw IllegalArgumentException("Unsupported type: ${T::class}")
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return result as T
|
||||
}
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.AuthMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.ClosedMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EoseMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.Message
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.NoticeMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.NotifyMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.OkMessage
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.boolean
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object MessageKSerializer : KSerializer<Message> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Message")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Message,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonArray {
|
||||
add(JsonPrimitive(value.label()))
|
||||
when (value) {
|
||||
is EventMessage -> {
|
||||
add(JsonPrimitive(value.subId))
|
||||
add(EventKSerializer.serializeToElement(value.event))
|
||||
}
|
||||
is NoticeMessage -> {
|
||||
add(JsonPrimitive(value.message))
|
||||
}
|
||||
is OkMessage -> {
|
||||
add(JsonPrimitive(value.eventId))
|
||||
// Jackson writes success as a string, not boolean
|
||||
add(JsonPrimitive(value.success.toString()))
|
||||
if (value.message.isNotBlank()) {
|
||||
add(JsonPrimitive(value.message))
|
||||
}
|
||||
}
|
||||
is AuthMessage -> {
|
||||
add(JsonPrimitive(value.challenge))
|
||||
}
|
||||
is NotifyMessage -> {
|
||||
add(JsonPrimitive(value.message))
|
||||
}
|
||||
is ClosedMessage -> {
|
||||
add(JsonPrimitive(value.subId))
|
||||
add(JsonPrimitive(value.message))
|
||||
}
|
||||
is CountMessage -> {
|
||||
add(CountResultKSerializer.serializeToElement(value.result))
|
||||
}
|
||||
}
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Message {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val array = jsonDecoder.decodeJsonElement().jsonArray
|
||||
val type = array[0].jsonPrimitive.content
|
||||
|
||||
return when (type) {
|
||||
EventMessage.LABEL -> {
|
||||
val subId = array[1].jsonPrimitive.content
|
||||
val event = EventKSerializer.deserializeFromElement(array[2].jsonObject)
|
||||
EventMessage(subId, event)
|
||||
}
|
||||
EoseMessage.LABEL -> {
|
||||
EoseMessage(array[1].jsonPrimitive.content)
|
||||
}
|
||||
NoticeMessage.LABEL -> {
|
||||
NoticeMessage(array[1].jsonPrimitive.content)
|
||||
}
|
||||
OkMessage.LABEL -> {
|
||||
OkMessage(
|
||||
eventId = array[1].jsonPrimitive.content,
|
||||
success = array[2].jsonPrimitive.boolean,
|
||||
message = if (array.size > 3) array[3].jsonPrimitive.content else "",
|
||||
)
|
||||
}
|
||||
AuthMessage.LABEL -> {
|
||||
AuthMessage(array[1].jsonPrimitive.content)
|
||||
}
|
||||
NotifyMessage.LABEL -> {
|
||||
NotifyMessage(array[1].jsonPrimitive.content)
|
||||
}
|
||||
ClosedMessage.LABEL -> {
|
||||
ClosedMessage(
|
||||
subId = array[1].jsonPrimitive.content,
|
||||
message = if (array.size > 2) array[2].jsonPrimitive.content else "",
|
||||
)
|
||||
}
|
||||
CountMessage.LABEL -> {
|
||||
val queryId = array[1].jsonPrimitive.content
|
||||
val result = CountResultKSerializer.deserializeFromElement(array[2].jsonObject)
|
||||
CountMessage(queryId, result)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Message $type is not supported")
|
||||
}
|
||||
}
|
||||
}
|
||||
+101
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.builtins.ListSerializer
|
||||
import kotlinx.serialization.builtins.serializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object TagArrayKSerializer : KSerializer<TagArray> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
ListSerializer(ListSerializer(String.serializer())).descriptor
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: TagArray,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonArray {
|
||||
for (tag in value) {
|
||||
add(
|
||||
buildJsonArray {
|
||||
for (s in tag) {
|
||||
add(JsonPrimitive(s))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): TagArray {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
return deserializeFromElement(jsonDecoder.decodeJsonElement())
|
||||
}
|
||||
|
||||
fun deserializeFromElement(element: JsonElement): TagArray {
|
||||
val array = element.jsonArray
|
||||
val outerList = ArrayList<Array<String>>(array.size)
|
||||
for (inner in array) {
|
||||
val innerArray = inner.jsonArray
|
||||
val innerList = ArrayList<String>(innerArray.size.coerceAtLeast(5))
|
||||
for (s in innerArray) {
|
||||
if (s is JsonNull) {
|
||||
innerList.add("")
|
||||
} else {
|
||||
innerList.add(s.jsonPrimitive.content)
|
||||
}
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
outerList.add(innerList.toArray(arrayOfNulls<String>(innerList.size)) as Array<String>)
|
||||
}
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return outerList.toArray(arrayOfNulls<Array<String>>(outerList.size)) as TagArray
|
||||
}
|
||||
|
||||
fun serializeToElement(value: TagArray): JsonArray =
|
||||
buildJsonArray {
|
||||
for (tag in value) {
|
||||
add(
|
||||
buildJsonArray {
|
||||
for (s in tag) {
|
||||
add(JsonPrimitive(s))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerMessage
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object BunkerMessageKSerializer : KSerializer<BunkerMessage> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("BunkerMessage")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: BunkerMessage,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
when (value) {
|
||||
is BunkerRequest -> BunkerRequestKSerializer.serialize(jsonEncoder, value)
|
||||
is BunkerResponse -> BunkerResponseKSerializer.serialize(jsonEncoder, value)
|
||||
else -> throw IllegalArgumentException("Unknown BunkerMessage type")
|
||||
}
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): BunkerMessage {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
val isRequest = jsonObject.containsKey("method")
|
||||
|
||||
return if (isRequest) {
|
||||
val id = jsonObject["id"]!!.jsonPrimitive.content
|
||||
val method = jsonObject["method"]!!.jsonPrimitive.content
|
||||
val params =
|
||||
jsonObject["params"]?.jsonArray?.map { it.jsonPrimitive.content }?.toTypedArray()
|
||||
?: emptyArray()
|
||||
dispatchBunkerRequest(id, method, params)
|
||||
} else {
|
||||
BunkerResponseKSerializer.deserializeFromElement(jsonObject)
|
||||
}
|
||||
}
|
||||
|
||||
private fun dispatchBunkerRequest(
|
||||
id: String,
|
||||
method: String,
|
||||
params: Array<String>,
|
||||
): BunkerRequest {
|
||||
return when (method) {
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetPublicKey.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetPublicKey.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetRelays.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetRelays.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Decrypt.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Decrypt.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Encrypt.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Encrypt.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing.parse(id, params)
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign.METHOD_NAME ->
|
||||
com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign.parse(id, params)
|
||||
else -> BunkerRequest(id, method, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestConnect
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetPublicKey
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestGetRelays
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Decrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip04Encrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Decrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestNip44Encrypt
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestPing
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequestSign
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object BunkerRequestKSerializer : KSerializer<BunkerRequest> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("BunkerRequest") {
|
||||
element<String>("id")
|
||||
element<String>("method")
|
||||
element<List<String>>("params")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: BunkerRequest,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonObject {
|
||||
put("id", value.id)
|
||||
put("method", value.method)
|
||||
put(
|
||||
"params",
|
||||
buildJsonArray {
|
||||
for (p in value.params) {
|
||||
add(JsonPrimitive(p))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): BunkerRequest {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
val id = jsonObject["id"]!!.jsonPrimitive.content
|
||||
val method = jsonObject["method"]!!.jsonPrimitive.content
|
||||
val params =
|
||||
jsonObject["params"]?.jsonArray?.map { it.jsonPrimitive.content }?.toTypedArray()
|
||||
?: emptyArray()
|
||||
|
||||
return when (method) {
|
||||
BunkerRequestConnect.METHOD_NAME -> BunkerRequestConnect.parse(id, params)
|
||||
BunkerRequestGetPublicKey.METHOD_NAME -> BunkerRequestGetPublicKey.parse(id, params)
|
||||
BunkerRequestGetRelays.METHOD_NAME -> BunkerRequestGetRelays.parse(id, params)
|
||||
BunkerRequestNip04Decrypt.METHOD_NAME -> BunkerRequestNip04Decrypt.parse(id, params)
|
||||
BunkerRequestNip04Encrypt.METHOD_NAME -> BunkerRequestNip04Encrypt.parse(id, params)
|
||||
BunkerRequestNip44Decrypt.METHOD_NAME -> BunkerRequestNip44Decrypt.parse(id, params)
|
||||
BunkerRequestNip44Encrypt.METHOD_NAME -> BunkerRequestNip44Encrypt.parse(id, params)
|
||||
BunkerRequestPing.METHOD_NAME -> BunkerRequestPing.parse(id, params)
|
||||
BunkerRequestSign.METHOD_NAME -> BunkerRequestSign.parse(id, params)
|
||||
else -> BunkerRequest(id, method, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip46RemoteSigner.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseAck
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseError
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseEvent
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponseGetRelays
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePong
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponsePublicKey
|
||||
import com.vitorpamplona.quartz.utils.Hex
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object BunkerResponseKSerializer : KSerializer<BunkerResponse> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("BunkerResponse") {
|
||||
element<String>("id")
|
||||
element<String?>("result")
|
||||
element<String?>("error")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: BunkerResponse,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonObject {
|
||||
put("id", value.id)
|
||||
value.result?.let { put("result", it) }
|
||||
value.error?.let { put("error", it) }
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): BunkerResponse {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
return deserializeFromElement(jsonDecoder.decodeJsonElement().jsonObject)
|
||||
}
|
||||
|
||||
fun deserializeFromElement(jsonObject: JsonObject): BunkerResponse {
|
||||
val id = jsonObject["id"]!!.jsonPrimitive.content
|
||||
val result = jsonObject["result"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }
|
||||
val error = jsonObject["error"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }
|
||||
|
||||
if (error != null) {
|
||||
return BunkerResponseError.parse(id, result, error)
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
when (result) {
|
||||
BunkerResponseAck.RESULT -> return BunkerResponseAck.parse(id, result, error)
|
||||
BunkerResponsePong.RESULT -> return BunkerResponsePong.parse(id, result, error)
|
||||
else -> {
|
||||
if (result.length == 64 && Hex.isHex(result)) {
|
||||
return BunkerResponsePublicKey.parse(id, result)
|
||||
}
|
||||
|
||||
if (result.isNotEmpty() && result[0] == '{') {
|
||||
try {
|
||||
return BunkerResponseEvent.parse(id, result)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
|
||||
try {
|
||||
return BunkerResponseGetRelays.parse(id, result)
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
}
|
||||
|
||||
return BunkerResponse(id, result, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return BunkerResponse(id, result, error)
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.HoldInvoiceAcceptedData
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.HoldInvoiceAcceptedNotification
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Notification
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcNotificationType
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PaymentReceivedNotification
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PaymentSentNotification
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
|
||||
object Nip47NotificationKSerializer : KSerializer<Notification> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Nip47Notification")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Notification,
|
||||
) {
|
||||
throw UnsupportedOperationException("NIP-47 Notification serialization not supported")
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Notification {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
val notificationType =
|
||||
jsonObject["notification_type"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }
|
||||
|
||||
return when (notificationType) {
|
||||
NwcNotificationType.PAYMENT_RECEIVED -> {
|
||||
PaymentReceivedNotification(
|
||||
notification =
|
||||
Nip47ResponseKSerializer.parseTransaction(
|
||||
jsonObject["notification"]?.jsonObject,
|
||||
),
|
||||
)
|
||||
}
|
||||
NwcNotificationType.PAYMENT_SENT -> {
|
||||
PaymentSentNotification(
|
||||
notification =
|
||||
Nip47ResponseKSerializer.parseTransaction(
|
||||
jsonObject["notification"]?.jsonObject,
|
||||
),
|
||||
)
|
||||
}
|
||||
NwcNotificationType.HOLD_INVOICE_ACCEPTED -> {
|
||||
val notifObj = jsonObject["notification"]?.jsonObject
|
||||
HoldInvoiceAcceptedNotification(
|
||||
notification =
|
||||
notifObj?.let {
|
||||
HoldInvoiceAcceptedData(
|
||||
type = it["type"]?.jsonPrimitive?.content,
|
||||
invoice = it["invoice"]?.jsonPrimitive?.content,
|
||||
payment_hash = it["payment_hash"]?.jsonPrimitive?.content,
|
||||
amount = it["amount"]?.jsonPrimitive?.longOrNull,
|
||||
created_at = it["created_at"]?.jsonPrimitive?.longOrNull,
|
||||
expires_at = it["expires_at"]?.jsonPrimitive?.longOrNull,
|
||||
settle_deadline = it["settle_deadline"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unknown notification type: $notificationType")
|
||||
}
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CancelHoldInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CancelHoldInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CreateConnectionMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CreateConnectionParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetBalanceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetBudgetMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetInfoMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.ListTransactionsMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.ListTransactionsParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LookupInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LookupInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeHoldInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeHoldInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayKeysendMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayKeysendParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SettleHoldInvoiceMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SettleHoldInvoiceParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SignMessageMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SignMessageParams
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.TlvRecord
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.booleanOrNull
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
|
||||
object Nip47RequestKSerializer : KSerializer<Request> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Nip47Request")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Request,
|
||||
) {
|
||||
throw UnsupportedOperationException("NIP-47 Request serialization not supported")
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Request {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
val method = jsonObject["method"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }
|
||||
|
||||
return when (method) {
|
||||
NwcMethod.PAY_INVOICE -> parsePayInvoice(jsonObject)
|
||||
NwcMethod.PAY_KEYSEND -> parsePayKeysend(jsonObject)
|
||||
NwcMethod.MAKE_INVOICE -> parseMakeInvoice(jsonObject)
|
||||
NwcMethod.LOOKUP_INVOICE -> parseLookupInvoice(jsonObject)
|
||||
NwcMethod.LIST_TRANSACTIONS -> parseListTransactions(jsonObject)
|
||||
NwcMethod.GET_BALANCE -> GetBalanceMethod()
|
||||
NwcMethod.GET_INFO -> GetInfoMethod()
|
||||
NwcMethod.GET_BUDGET -> GetBudgetMethod()
|
||||
NwcMethod.SIGN_MESSAGE -> parseSignMessage(jsonObject)
|
||||
NwcMethod.CREATE_CONNECTION -> parseCreateConnection(jsonObject)
|
||||
NwcMethod.MAKE_HOLD_INVOICE -> parseMakeHoldInvoice(jsonObject)
|
||||
NwcMethod.CANCEL_HOLD_INVOICE -> parseCancelHoldInvoice(jsonObject)
|
||||
NwcMethod.SETTLE_HOLD_INVOICE -> parseSettleHoldInvoice(jsonObject)
|
||||
else -> throw IllegalArgumentException("Unknown NWC method: $method")
|
||||
}
|
||||
}
|
||||
|
||||
private fun parsePayInvoice(json: JsonObject): PayInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return PayInvoiceMethod(
|
||||
params?.let {
|
||||
PayInvoiceParams(
|
||||
invoice = it["invoice"]?.jsonPrimitive?.content,
|
||||
amount = it["amount"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parsePayKeysend(json: JsonObject): PayKeysendMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return PayKeysendMethod(
|
||||
params?.let {
|
||||
PayKeysendParams(
|
||||
amount = it["amount"]?.jsonPrimitive?.longOrNull,
|
||||
pubkey = it["pubkey"]?.jsonPrimitive?.content,
|
||||
preimage = it["preimage"]?.jsonPrimitive?.content,
|
||||
tlv_records =
|
||||
it["tlv_records"]?.jsonArray?.map { record ->
|
||||
val obj = record.jsonObject
|
||||
TlvRecord(
|
||||
type = obj["type"]?.jsonPrimitive?.longOrNull,
|
||||
value = obj["value"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseMakeInvoice(json: JsonObject): MakeInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return MakeInvoiceMethod(
|
||||
params?.let {
|
||||
MakeInvoiceParams(
|
||||
amount = it["amount"]?.jsonPrimitive?.longOrNull,
|
||||
description = it["description"]?.jsonPrimitive?.content,
|
||||
description_hash = it["description_hash"]?.jsonPrimitive?.content,
|
||||
expiry = it["expiry"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseLookupInvoice(json: JsonObject): LookupInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return LookupInvoiceMethod(
|
||||
params?.let {
|
||||
LookupInvoiceParams(
|
||||
payment_hash = it["payment_hash"]?.jsonPrimitive?.content,
|
||||
invoice = it["invoice"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseListTransactions(json: JsonObject): ListTransactionsMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return ListTransactionsMethod(
|
||||
params?.let {
|
||||
ListTransactionsParams(
|
||||
from = it["from"]?.jsonPrimitive?.longOrNull,
|
||||
until = it["until"]?.jsonPrimitive?.longOrNull,
|
||||
limit = it["limit"]?.jsonPrimitive?.intOrNull,
|
||||
offset = it["offset"]?.jsonPrimitive?.intOrNull,
|
||||
unpaid = it["unpaid"]?.jsonPrimitive?.booleanOrNull,
|
||||
unpaid_outgoing = it["unpaid_outgoing"]?.jsonPrimitive?.booleanOrNull,
|
||||
unpaid_incoming = it["unpaid_incoming"]?.jsonPrimitive?.booleanOrNull,
|
||||
type = it["type"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseSignMessage(json: JsonObject): SignMessageMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return SignMessageMethod(
|
||||
params?.let {
|
||||
SignMessageParams(
|
||||
message = it["message"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseCreateConnection(json: JsonObject): CreateConnectionMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return CreateConnectionMethod(
|
||||
params?.let {
|
||||
CreateConnectionParams(
|
||||
pubkey = it["pubkey"]?.jsonPrimitive?.content,
|
||||
name = it["name"]?.jsonPrimitive?.content,
|
||||
request_methods = it["request_methods"]?.jsonArray?.map { m -> m.jsonPrimitive.content },
|
||||
notification_types = it["notification_types"]?.jsonArray?.map { n -> n.jsonPrimitive.content },
|
||||
max_amount = it["max_amount"]?.jsonPrimitive?.longOrNull,
|
||||
budget_renewal = it["budget_renewal"]?.jsonPrimitive?.content,
|
||||
expires_at = it["expires_at"]?.jsonPrimitive?.longOrNull,
|
||||
isolated = it["isolated"]?.jsonPrimitive?.booleanOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseMakeHoldInvoice(json: JsonObject): MakeHoldInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return MakeHoldInvoiceMethod(
|
||||
params?.let {
|
||||
MakeHoldInvoiceParams(
|
||||
amount = it["amount"]?.jsonPrimitive?.longOrNull,
|
||||
description = it["description"]?.jsonPrimitive?.content,
|
||||
description_hash = it["description_hash"]?.jsonPrimitive?.content,
|
||||
expiry = it["expiry"]?.jsonPrimitive?.longOrNull,
|
||||
payment_hash = it["payment_hash"]?.jsonPrimitive?.content,
|
||||
min_cltv_expiry_delta = it["min_cltv_expiry_delta"]?.jsonPrimitive?.intOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseCancelHoldInvoice(json: JsonObject): CancelHoldInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return CancelHoldInvoiceMethod(
|
||||
params?.let {
|
||||
CancelHoldInvoiceParams(
|
||||
payment_hash = it["payment_hash"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseSettleHoldInvoice(json: JsonObject): SettleHoldInvoiceMethod {
|
||||
val params = json["params"]?.jsonObject
|
||||
return SettleHoldInvoiceMethod(
|
||||
params?.let {
|
||||
SettleHoldInvoiceParams(
|
||||
preimage = it["preimage"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip47WalletConnect.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CancelHoldInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.CreateConnectionSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetBalanceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetBudgetSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.GetInfoSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.ListTransactionsSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.LookupInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeHoldInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.MakeInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcError
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcErrorCode
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcErrorResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcMethod
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.NwcTransaction
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceErrorResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.PayKeysendSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SettleHoldInvoiceSuccessResponse
|
||||
import com.vitorpamplona.quartz.nip47WalletConnect.SignMessageSuccessResponse
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.longOrNull
|
||||
|
||||
object Nip47ResponseKSerializer : KSerializer<Response> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Nip47Response")
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Response,
|
||||
) {
|
||||
throw UnsupportedOperationException("NIP-47 Response serialization not supported")
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Response {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
val resultType = jsonObject["result_type"]?.let { if (it is JsonNull) null else it.jsonPrimitive.content }
|
||||
val hasError = jsonObject["error"]?.let { it !is JsonNull } ?: false
|
||||
val hasResult = jsonObject["result"]?.let { it !is JsonNull } ?: false
|
||||
|
||||
if (hasError) {
|
||||
return when (resultType) {
|
||||
NwcMethod.PAY_INVOICE -> parsePayInvoiceError(jsonObject)
|
||||
else -> {
|
||||
val error = jsonObject["error"]?.jsonObject?.let { parseNwcError(it) }
|
||||
NwcErrorResponse(resultType ?: "", error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasResult || resultType != null) {
|
||||
return when (resultType) {
|
||||
NwcMethod.PAY_INVOICE -> parsePayInvoiceSuccess(jsonObject)
|
||||
NwcMethod.PAY_KEYSEND -> parsePayKeysendSuccess(jsonObject)
|
||||
NwcMethod.MAKE_INVOICE -> MakeInvoiceSuccessResponse(parseTransaction(jsonObject["result"]?.jsonObject))
|
||||
NwcMethod.LOOKUP_INVOICE -> LookupInvoiceSuccessResponse(parseTransaction(jsonObject["result"]?.jsonObject))
|
||||
NwcMethod.LIST_TRANSACTIONS -> parseListTransactionsSuccess(jsonObject)
|
||||
NwcMethod.GET_BALANCE -> parseGetBalanceSuccess(jsonObject)
|
||||
NwcMethod.GET_INFO -> parseGetInfoSuccess(jsonObject)
|
||||
NwcMethod.GET_BUDGET -> parseGetBudgetSuccess(jsonObject)
|
||||
NwcMethod.SIGN_MESSAGE -> parseSignMessageSuccess(jsonObject)
|
||||
NwcMethod.CREATE_CONNECTION -> parseCreateConnectionSuccess(jsonObject)
|
||||
NwcMethod.MAKE_HOLD_INVOICE -> MakeHoldInvoiceSuccessResponse(parseTransaction(jsonObject["result"]?.jsonObject))
|
||||
NwcMethod.CANCEL_HOLD_INVOICE -> CancelHoldInvoiceSuccessResponse()
|
||||
NwcMethod.SETTLE_HOLD_INVOICE -> SettleHoldInvoiceSuccessResponse()
|
||||
else -> {
|
||||
// backward compatibility: guess by result content
|
||||
val resultObj = jsonObject["result"]?.jsonObject
|
||||
if (resultObj?.containsKey("preimage") == true) {
|
||||
return parsePayInvoiceSuccess(jsonObject)
|
||||
}
|
||||
throw IllegalArgumentException("Unknown NWC response type: $resultType")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("NWC response has neither result nor error")
|
||||
}
|
||||
|
||||
private fun parseNwcError(obj: JsonObject): NwcError {
|
||||
val code = obj["code"]?.jsonPrimitive?.content?.let { codeName ->
|
||||
try {
|
||||
NwcErrorCode.valueOf(codeName)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
return NwcError(code, obj["message"]?.jsonPrimitive?.content)
|
||||
}
|
||||
|
||||
fun parseTransaction(obj: JsonObject?): NwcTransaction? {
|
||||
if (obj == null) return null
|
||||
return NwcTransaction(
|
||||
type = obj["type"]?.jsonPrimitive?.content,
|
||||
state = obj["state"]?.jsonPrimitive?.content,
|
||||
invoice = obj["invoice"]?.jsonPrimitive?.content,
|
||||
description = obj["description"]?.jsonPrimitive?.content,
|
||||
description_hash = obj["description_hash"]?.jsonPrimitive?.content,
|
||||
preimage = obj["preimage"]?.jsonPrimitive?.content,
|
||||
payment_hash = obj["payment_hash"]?.jsonPrimitive?.content,
|
||||
amount = obj["amount"]?.jsonPrimitive?.longOrNull,
|
||||
fees_paid = obj["fees_paid"]?.jsonPrimitive?.longOrNull,
|
||||
created_at = obj["created_at"]?.jsonPrimitive?.longOrNull,
|
||||
expires_at = obj["expires_at"]?.jsonPrimitive?.longOrNull,
|
||||
settled_at = obj["settled_at"]?.jsonPrimitive?.longOrNull,
|
||||
settle_deadline = obj["settle_deadline"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
}
|
||||
|
||||
private fun parsePayInvoiceSuccess(json: JsonObject): PayInvoiceSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return PayInvoiceSuccessResponse(
|
||||
result?.let {
|
||||
PayInvoiceSuccessResponse.PayInvoiceResultParams(
|
||||
preimage = it["preimage"]?.jsonPrimitive?.content,
|
||||
fees_paid = it["fees_paid"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parsePayInvoiceError(json: JsonObject): PayInvoiceErrorResponse {
|
||||
val error = json["error"]?.jsonObject
|
||||
return PayInvoiceErrorResponse(
|
||||
error?.let {
|
||||
PayInvoiceErrorResponse.PayInvoiceErrorParams(
|
||||
code = it["code"]?.jsonPrimitive?.content?.let { codeName ->
|
||||
try {
|
||||
NwcErrorCode.valueOf(codeName)
|
||||
} catch (_: Exception) {
|
||||
null
|
||||
}
|
||||
},
|
||||
message = it["message"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parsePayKeysendSuccess(json: JsonObject): PayKeysendSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return PayKeysendSuccessResponse(
|
||||
result?.let {
|
||||
PayKeysendSuccessResponse.PayKeysendResult(
|
||||
preimage = it["preimage"]?.jsonPrimitive?.content,
|
||||
fees_paid = it["fees_paid"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseListTransactionsSuccess(json: JsonObject): ListTransactionsSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return ListTransactionsSuccessResponse(
|
||||
result?.let {
|
||||
ListTransactionsSuccessResponse.ListTransactionsResult(
|
||||
transactions = it["transactions"]?.jsonArray?.map { t -> parseTransaction(t.jsonObject) },
|
||||
total_count = it["total_count"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseGetBalanceSuccess(json: JsonObject): GetBalanceSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return GetBalanceSuccessResponse(
|
||||
result?.let {
|
||||
GetBalanceSuccessResponse.GetBalanceResult(
|
||||
balance = it["balance"]?.jsonPrimitive?.longOrNull,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseGetInfoSuccess(json: JsonObject): GetInfoSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return GetInfoSuccessResponse(
|
||||
result?.let {
|
||||
GetInfoSuccessResponse.GetInfoResult(
|
||||
alias = it["alias"]?.jsonPrimitive?.content,
|
||||
color = it["color"]?.jsonPrimitive?.content,
|
||||
pubkey = it["pubkey"]?.jsonPrimitive?.content,
|
||||
network = it["network"]?.jsonPrimitive?.content,
|
||||
block_height = it["block_height"]?.jsonPrimitive?.longOrNull,
|
||||
block_hash = it["block_hash"]?.jsonPrimitive?.content,
|
||||
methods = it["methods"]?.jsonArray?.map { m -> m.jsonPrimitive.content },
|
||||
notifications = it["notifications"]?.jsonArray?.map { n -> n.jsonPrimitive.content },
|
||||
lud16 = it["lud16"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseGetBudgetSuccess(json: JsonObject): GetBudgetSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return GetBudgetSuccessResponse(
|
||||
result?.let {
|
||||
GetBudgetSuccessResponse.GetBudgetResult(
|
||||
used_budget = it["used_budget"]?.jsonPrimitive?.longOrNull,
|
||||
total_budget = it["total_budget"]?.jsonPrimitive?.longOrNull,
|
||||
renews_at = it["renews_at"]?.jsonPrimitive?.longOrNull,
|
||||
renewal_period = it["renewal_period"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseSignMessageSuccess(json: JsonObject): SignMessageSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return SignMessageSuccessResponse(
|
||||
result?.let {
|
||||
SignMessageSuccessResponse.SignMessageResult(
|
||||
message = it["message"]?.jsonPrimitive?.content,
|
||||
signature = it["signature"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun parseCreateConnectionSuccess(json: JsonObject): CreateConnectionSuccessResponse {
|
||||
val result = json["result"]?.jsonObject
|
||||
return CreateConnectionSuccessResponse(
|
||||
result?.let {
|
||||
CreateConnectionSuccessResponse.CreateConnectionResult(
|
||||
wallet_pubkey = it["wallet_pubkey"]?.jsonPrimitive?.content,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip59Giftwrap.rumors.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.kotlinSerialization.TagArrayKSerializer
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.TagArray
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.element
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.JsonDecoder
|
||||
import kotlinx.serialization.json.JsonEncoder
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import kotlinx.serialization.json.long
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
object RumorKSerializer : KSerializer<Rumor> {
|
||||
override val descriptor: SerialDescriptor =
|
||||
buildClassSerialDescriptor("Rumor") {
|
||||
element<String>("id")
|
||||
element<String>("pubkey")
|
||||
element<Long>("created_at")
|
||||
element<Int>("kind")
|
||||
element("tags", TagArrayKSerializer.descriptor)
|
||||
element<String>("content")
|
||||
}
|
||||
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: Rumor,
|
||||
) {
|
||||
val jsonEncoder = encoder as JsonEncoder
|
||||
val element =
|
||||
buildJsonObject {
|
||||
value.id?.let { put("id", it) }
|
||||
value.pubKey?.let { put("pubkey", it) }
|
||||
value.createdAt?.let { put("created_at", it) }
|
||||
value.kind?.let { put("kind", it) }
|
||||
value.tags?.let { put("tags", TagArrayKSerializer.serializeToElement(it)) }
|
||||
value.content?.let { put("content", it) }
|
||||
}
|
||||
jsonEncoder.encodeJsonElement(element)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Rumor {
|
||||
val jsonDecoder = decoder as JsonDecoder
|
||||
val jsonObject = jsonDecoder.decodeJsonElement().jsonObject
|
||||
|
||||
var id: HexKey? = null
|
||||
var pubKey: HexKey? = null
|
||||
var createdAt: Long? = null
|
||||
var kind: Int? = null
|
||||
var tags: TagArray? = null
|
||||
var content: String? = null
|
||||
|
||||
for ((key, value) in jsonObject) {
|
||||
when (key) {
|
||||
"id" -> id = value.jsonPrimitive.content
|
||||
"pubkey" -> pubKey = value.jsonPrimitive.content
|
||||
"created_at" -> createdAt = value.jsonPrimitive.long
|
||||
"kind" -> kind = value.jsonPrimitive.int
|
||||
"tags" -> tags = TagArrayKSerializer.deserializeFromElement(value)
|
||||
"content" -> content = value.jsonPrimitive.content
|
||||
}
|
||||
}
|
||||
|
||||
return Rumor(id, pubKey, createdAt, kind, tags, content)
|
||||
}
|
||||
}
|
||||
+47
-10
@@ -20,29 +20,66 @@
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.core
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.kotlinSerialization.KotlinSerializationMapper
|
||||
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.SerializationException
|
||||
|
||||
actual object OptimizedJsonMapper {
|
||||
actual fun fromJson(json: String): Event = TODO("Not yet implemented")
|
||||
actual fun fromJson(json: String): Event =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJson(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun toJson(event: Event): String = TODO("Not yet implemented")
|
||||
actual fun toJson(event: Event): String = KotlinSerializationMapper.toJson(event)
|
||||
|
||||
actual fun fromJsonToMessage(json: String): Message = TODO("Not yet implemented")
|
||||
actual fun fromJsonToMessage(json: String): Message =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonToMessage(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun fromJsonToCommand(json: String): Command = TODO("Not yet implemented")
|
||||
actual fun fromJsonToCommand(json: String): Command =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonToCommand(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun fromJsonToTagArray(json: String): Array<Array<String>> = TODO("Not yet implemented")
|
||||
actual fun fromJsonToTagArray(json: String): Array<Array<String>> =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun fromJsonToEventTemplate(json: String): EventTemplate<Event> = TODO("Not yet implemented")
|
||||
actual fun fromJsonToEventTemplate(json: String): EventTemplate<Event> =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonToEventTemplate(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun fromJsonToRumor(json: String): Rumor = TODO("Not yet implemented")
|
||||
actual fun fromJsonToRumor(json: String): Rumor =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonToRumor(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun toJson(tags: Array<Array<String>>): String = TODO("Not yet implemented")
|
||||
actual fun toJson(tags: Array<Array<String>>): String = KotlinSerializationMapper.toJson(tags)
|
||||
|
||||
actual inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T = TODO("Not yet implemented")
|
||||
actual inline fun <reified T : OptimizedSerializable> fromJsonTo(json: String): T =
|
||||
try {
|
||||
KotlinSerializationMapper.fromJsonTo<T>(json)
|
||||
} catch (e: SerializationException) {
|
||||
throw IllegalArgumentException(e.message, e)
|
||||
}
|
||||
|
||||
actual fun toJson(value: OptimizedSerializable): String = TODO("Not yet implemented")
|
||||
actual fun toJson(value: OptimizedSerializable): String = KotlinSerializationMapper.toJson(value)
|
||||
}
|
||||
|
||||
+740
@@ -0,0 +1,740 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.quartz.nip01Core.kotlinSerialization
|
||||
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.jackson.JacksonMapper
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.AuthMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.ClosedMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.CountResult
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EoseMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.EventMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.NoticeMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.NotifyMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toClient.OkMessage
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.CloseCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.EventCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.commands.toRelay.ReqCmd
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerRequest
|
||||
import com.vitorpamplona.quartz.nip46RemoteSigner.BunkerResponse
|
||||
import com.vitorpamplona.quartz.nip51Lists.followList.FollowListEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class KotlinSerializationMapperTest {
|
||||
val tags =
|
||||
arrayOf(
|
||||
arrayOf("title", "Retro Computer Fans"),
|
||||
arrayOf("d", "xmbspe8rddsq"),
|
||||
arrayOf("image", "https://blog.johnnovak.net/2022/04/15/achieving-period-correct-graphics-in-personal-computer-emulators-part-1-the-amiga/img/dream-setup.jpg"),
|
||||
arrayOf("p", "3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da"),
|
||||
arrayOf("p", "9a9a4aa0e43e57873380ab22e8a3df12f3c4cf5bb3a804c6e3fed0069a6e2740"),
|
||||
arrayOf("p", "4f5dd82517b11088ce00f23d99f06fe8f3e2e45ecf47bc9c2f90f34d5c6f7382"),
|
||||
arrayOf("p", "ac92102a2ecb873c488e0125354ef5a97075a16198668c360eda050007ed42cd"),
|
||||
arrayOf("p", "47f54409a4620eb35208a3bc1b53555bf3d0656b246bf0471a93208e20672f6f"),
|
||||
arrayOf("p", "2624911545afb7a2b440cf10f5c69308afa33aae26fca664d8c94623dc0f1baf"),
|
||||
arrayOf("p", "6641f26f5c59f7010dbe3e42e4593398e27c087497cb7d20e0e7633a17e48a94"),
|
||||
arrayOf("description", "Retro computer fans and enthusiasts "),
|
||||
)
|
||||
|
||||
val followCard =
|
||||
FollowListEvent(
|
||||
id = "eca31634fce7c9068b56fa8db9f387da70bdcceb3986a77ca1a9844f3128eb5f",
|
||||
pubKey = "3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da",
|
||||
createdAt = 1761736286,
|
||||
tags = tags,
|
||||
content = "",
|
||||
sig = "3aa388edafad151e81cb0228fe04e115dbbcaa851c666bfe3c8740b6cd99575f0fc3ba2d47acda86f7626564a05e9dbc05ef452a7bd0ac00f828dbad0e1bae6c",
|
||||
)
|
||||
|
||||
val followCardRumor =
|
||||
Rumor(
|
||||
id = followCard.id,
|
||||
pubKey = followCard.pubKey,
|
||||
createdAt = followCard.createdAt,
|
||||
kind = followCard.kind,
|
||||
tags = followCard.tags,
|
||||
content = followCard.content,
|
||||
)
|
||||
|
||||
val followCardTemplate =
|
||||
EventTemplate<Event>(
|
||||
createdAt = followCard.createdAt,
|
||||
kind = followCard.kind,
|
||||
tags = followCard.tags,
|
||||
content = followCard.content,
|
||||
)
|
||||
|
||||
// =========================================================================
|
||||
// TagArray Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeTagArray_matchesJackson() {
|
||||
val jacksonJson = JacksonMapper.toJson(tags)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(tags)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeTagArray_matchesJackson() {
|
||||
val json = JacksonMapper.toJson(tags)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
tags.forEachIndexed { index, tag ->
|
||||
assertContentEquals(tag, deserialized[index])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tagArrayRoundTrip() {
|
||||
val json = KotlinSerializationMapper.toJson(tags)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
assertEquals(tags.size, deserialized.size)
|
||||
tags.forEachIndexed { index, tag ->
|
||||
assertContentEquals(tag, deserialized[index])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun tagArrayWithNullValues() {
|
||||
val json = """[["key",null,"value"]]"""
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
assertEquals(1, deserialized.size)
|
||||
assertEquals("key", deserialized[0][0])
|
||||
assertEquals("", deserialized[0][1]) // null -> ""
|
||||
assertEquals("value", deserialized[0][2])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyTagArray() {
|
||||
val json = "[]"
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
assertEquals(0, deserialized.size)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Event Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeEvent_matchesJackson() {
|
||||
val jacksonJson = JacksonMapper.toJson(followCard)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCard)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEvent_matchesJackson() {
|
||||
val json = JacksonMapper.toJson(followCard)
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
|
||||
assertEquals(followCard.id, deserialized.id)
|
||||
assertEquals(followCard.pubKey, deserialized.pubKey)
|
||||
assertEquals(followCard.createdAt, deserialized.createdAt)
|
||||
assertEquals(followCard.kind, deserialized.kind)
|
||||
assertEquals(followCard.content, deserialized.content)
|
||||
assertEquals(followCard.sig, deserialized.sig)
|
||||
tags.forEachIndexed { index, tag ->
|
||||
assertContentEquals(tag, deserialized.tags[index])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun eventRoundTrip() {
|
||||
val json = KotlinSerializationMapper.toJson(followCard)
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
|
||||
assertEquals(followCard.id, deserialized.id)
|
||||
assertEquals(followCard.kind, deserialized.kind)
|
||||
assertEquals(followCard.createdAt, deserialized.createdAt)
|
||||
assertEquals(followCard.pubKey, deserialized.pubKey)
|
||||
assertEquals(followCard.content, deserialized.content)
|
||||
assertEquals(followCard.sig, deserialized.sig)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEventWithUnknownFields() {
|
||||
val json =
|
||||
"""{"id":"abc123","pubkey":"def456","created_at":12345,"kind":1,"tags":[],"content":"test","sig":"sig123","unknown_field":"ignored"}"""
|
||||
// Should not throw with unknown fields, should be ignored
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
assertEquals("abc123", deserialized.id)
|
||||
assertEquals("def456", deserialized.pubKey)
|
||||
assertEquals("test", deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEventWithSpecialCharactersInContent() {
|
||||
val content = "Hello \"world\" \n\ttab\\backslash"
|
||||
val event =
|
||||
FollowListEvent(
|
||||
id = "abc",
|
||||
pubKey = "def",
|
||||
createdAt = 1000,
|
||||
tags = emptyArray(),
|
||||
content = content,
|
||||
sig = "sig",
|
||||
)
|
||||
val json = KotlinSerializationMapper.toJson(event)
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
assertEquals(content, deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossDeserializationEvent() {
|
||||
// Serialize with Jackson, deserialize with Kotlin Serialization
|
||||
val jacksonJson = JacksonMapper.toJson(followCard)
|
||||
val kotlinDeserialized = KotlinSerializationMapper.fromJson(jacksonJson)
|
||||
|
||||
assertEquals(followCard.id, kotlinDeserialized.id)
|
||||
assertEquals(followCard.pubKey, kotlinDeserialized.pubKey)
|
||||
assertEquals(followCard.kind, kotlinDeserialized.kind)
|
||||
|
||||
// Serialize with Kotlin Serialization, deserialize with Jackson
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCard)
|
||||
val jacksonDeserialized = JacksonMapper.fromJson(kotlinJson)
|
||||
|
||||
assertEquals(followCard.id, jacksonDeserialized.id)
|
||||
assertEquals(followCard.pubKey, jacksonDeserialized.pubKey)
|
||||
assertEquals(followCard.kind, jacksonDeserialized.kind)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Rumor Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeRumor_matchesJackson() {
|
||||
val jacksonJson = JacksonMapper.toJson(followCardRumor)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCardRumor)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeRumor_matchesJackson() {
|
||||
val json = JacksonMapper.toJson(followCardRumor)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToRumor(json)
|
||||
|
||||
assertEquals(followCardRumor.id, deserialized.id)
|
||||
assertEquals(followCardRumor.pubKey, deserialized.pubKey)
|
||||
assertEquals(followCardRumor.createdAt, deserialized.createdAt)
|
||||
assertEquals(followCardRumor.kind, deserialized.kind)
|
||||
assertEquals(followCardRumor.content, deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rumorRoundTrip() {
|
||||
val json = KotlinSerializationMapper.toJson(followCardRumor)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToRumor(json)
|
||||
|
||||
assertEquals(followCardRumor.id, deserialized.id)
|
||||
assertEquals(followCardRumor.kind, deserialized.kind)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rumorWithNullFields() {
|
||||
val rumor = Rumor(null, null, null, null, null, null)
|
||||
val json = KotlinSerializationMapper.toJson(rumor)
|
||||
assertEquals("{}", json)
|
||||
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToRumor(json)
|
||||
assertNull(deserialized.id)
|
||||
assertNull(deserialized.pubKey)
|
||||
assertNull(deserialized.createdAt)
|
||||
assertNull(deserialized.kind)
|
||||
assertNull(deserialized.tags)
|
||||
assertNull(deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rumorPartialFields() {
|
||||
val rumor = Rumor("abc", null, 1000, 1, null, "content")
|
||||
val json = KotlinSerializationMapper.toJson(rumor)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToRumor(json)
|
||||
|
||||
assertEquals("abc", deserialized.id)
|
||||
assertNull(deserialized.pubKey)
|
||||
assertEquals(1000L, deserialized.createdAt)
|
||||
assertEquals(1, deserialized.kind)
|
||||
assertNull(deserialized.tags)
|
||||
assertEquals("content", deserialized.content)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// EventTemplate Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeTemplate_matchesJackson() {
|
||||
val jacksonJson = JacksonMapper.toJson(followCardTemplate)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCardTemplate)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeTemplate_matchesJackson() {
|
||||
val json = JacksonMapper.toJson(followCardTemplate)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToEventTemplate(json)
|
||||
|
||||
assertEquals(followCardTemplate.kind, deserialized.kind)
|
||||
assertEquals(followCardTemplate.createdAt, deserialized.createdAt)
|
||||
assertEquals(followCardTemplate.content, deserialized.content)
|
||||
tags.forEachIndexed { index, tag ->
|
||||
assertContentEquals(tag, deserialized.tags[index])
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun templateRoundTrip() {
|
||||
val json = KotlinSerializationMapper.toJson(followCardTemplate)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToEventTemplate(json)
|
||||
|
||||
assertEquals(followCardTemplate.kind, deserialized.kind)
|
||||
assertEquals(followCardTemplate.createdAt, deserialized.createdAt)
|
||||
assertEquals(followCardTemplate.content, deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun templateWithDifferentFieldOrder() {
|
||||
// Fields in different order than expected
|
||||
val json = """{"kind":1,"content":"test","created_at":1234,"tags":[]}"""
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToEventTemplate(json)
|
||||
assertEquals(1, deserialized.kind)
|
||||
assertEquals("test", deserialized.content)
|
||||
assertEquals(1234L, deserialized.createdAt)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Filter Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun emptyFilter_matchesJackson() {
|
||||
val filter = Filter()
|
||||
val jacksonJson = JacksonMapper.toJson(filter)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(filter)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterWithAllFields_matchesJackson() {
|
||||
val filter =
|
||||
Filter(
|
||||
ids = listOf("abc123" + "0".repeat(58)),
|
||||
authors = listOf("def456" + "0".repeat(58)),
|
||||
kinds = listOf(1, 2, 3),
|
||||
tags = mapOf("p" to listOf("3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da")),
|
||||
tagsAll = mapOf("p" to listOf("3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da")),
|
||||
since = 1000L,
|
||||
until = 2000L,
|
||||
limit = 50,
|
||||
search = "hello",
|
||||
)
|
||||
val jacksonJson = JacksonMapper.toJson(filter)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(filter)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterRoundTrip() {
|
||||
val expectedTagValue = "3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da"
|
||||
val filter =
|
||||
Filter(
|
||||
tags = mapOf("p" to listOf(expectedTagValue)),
|
||||
tagsAll = mapOf("p" to listOf(expectedTagValue)),
|
||||
)
|
||||
val json = KotlinSerializationMapper.toJson(filter)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonTo<Filter>(json)
|
||||
|
||||
assertEquals(true, deserialized.tags?.keys?.contains("p"))
|
||||
assertEquals(listOf(expectedTagValue), deserialized.tags?.get("p"))
|
||||
assertEquals(true, deserialized.tagsAll?.keys?.contains("p"))
|
||||
assertEquals(listOf(expectedTagValue), deserialized.tagsAll?.get("p"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEmptyFilter() {
|
||||
val json = Filter().toJson()
|
||||
val deserialized = KotlinSerializationMapper.fromJsonTo<Filter>(json)
|
||||
assertNull(deserialized.ids)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossDeserializationFilter() {
|
||||
val expectedTagValue = "3c39a7b53dec9ac85acf08b267637a9841e6df7b7b0f5e2ac56a8cf107de37da"
|
||||
val filter =
|
||||
Filter(
|
||||
tags = mapOf("p" to listOf(expectedTagValue)),
|
||||
tagsAll = mapOf("p" to listOf(expectedTagValue)),
|
||||
)
|
||||
|
||||
// Jackson serialized -> Kotlin deserialized
|
||||
val jacksonJson = JacksonMapper.toJson(filter)
|
||||
val kotlinDeserialized = KotlinSerializationMapper.fromJsonTo<Filter>(jacksonJson)
|
||||
assertEquals(listOf(expectedTagValue), kotlinDeserialized.tags?.get("p"))
|
||||
assertEquals(listOf(expectedTagValue), kotlinDeserialized.tagsAll?.get("p"))
|
||||
|
||||
// Kotlin serialized -> Jackson deserialized
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(filter)
|
||||
val jacksonDeserialized = JacksonMapper.fromJsonTo<Filter>(kotlinJson)
|
||||
assertEquals(listOf(expectedTagValue), jacksonDeserialized.tags?.get("p"))
|
||||
assertEquals(listOf(expectedTagValue), jacksonDeserialized.tagsAll?.get("p"))
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Message Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeEventMessage_matchesJackson() {
|
||||
val msg = EventMessage("sub1", followCard)
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEventMessage() {
|
||||
val msg = EventMessage("sub1", followCard)
|
||||
val json = KotlinSerializationMapper.toJson(msg)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToMessage(json)
|
||||
|
||||
assertTrue(deserialized is EventMessage)
|
||||
assertEquals("sub1", deserialized.subId)
|
||||
assertEquals(followCard.id, deserialized.event.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeNoticeMessage_matchesJackson() {
|
||||
val msg = NoticeMessage("something went wrong")
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeOkMessage_matchesJackson() {
|
||||
val msg = OkMessage("abc123", true, "success")
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeOkMessage() {
|
||||
val msg = OkMessage("abc123", false, "rate limited")
|
||||
val json = KotlinSerializationMapper.toJson(msg)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToMessage(json)
|
||||
|
||||
assertTrue(deserialized is OkMessage)
|
||||
assertEquals("abc123", deserialized.eventId)
|
||||
assertEquals(false, deserialized.success)
|
||||
assertEquals("rate limited", deserialized.message)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeAuthMessage_matchesJackson() {
|
||||
val msg = AuthMessage("challenge123")
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeNotifyMessage_matchesJackson() {
|
||||
val msg = NotifyMessage("notification text")
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeClosedMessage_matchesJackson() {
|
||||
val msg = ClosedMessage("sub1", "subscription closed")
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeEoseMessage() {
|
||||
val json = """["EOSE","sub123"]"""
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToMessage(json)
|
||||
assertTrue(deserialized is EoseMessage)
|
||||
assertEquals("sub123", (deserialized as EoseMessage).subId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeCountMessage_matchesJackson() {
|
||||
val msg = CountMessage("q1", CountResult(42, false))
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossDeserializationMessages() {
|
||||
val messages =
|
||||
listOf(
|
||||
NoticeMessage("test"),
|
||||
AuthMessage("challenge"),
|
||||
NotifyMessage("notify"),
|
||||
ClosedMessage("sub1", "reason"),
|
||||
)
|
||||
|
||||
for (msg in messages) {
|
||||
val jacksonJson = JacksonMapper.toJson(msg)
|
||||
val kotlinDeserialized = KotlinSerializationMapper.fromJsonToMessage(jacksonJson)
|
||||
assertEquals(msg.label(), kotlinDeserialized.label())
|
||||
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(msg)
|
||||
val jacksonDeserialized = JacksonMapper.fromJsonToMessage(kotlinJson)
|
||||
assertEquals(msg.label(), jacksonDeserialized.label())
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Command Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeReqCmd_matchesJackson() {
|
||||
val filter =
|
||||
Filter(
|
||||
kinds = listOf(1),
|
||||
limit = 10,
|
||||
)
|
||||
val cmd = ReqCmd("sub1", listOf(filter))
|
||||
val jacksonJson = JacksonMapper.toJson(cmd)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(cmd)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeReqCmd() {
|
||||
val filter = Filter(kinds = listOf(1), limit = 10)
|
||||
val cmd = ReqCmd("sub1", listOf(filter))
|
||||
val json = KotlinSerializationMapper.toJson(cmd)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToCommand(json)
|
||||
|
||||
assertTrue(deserialized is ReqCmd)
|
||||
assertEquals("sub1", deserialized.subId)
|
||||
assertEquals(1, deserialized.filters.size)
|
||||
assertEquals(listOf(1), deserialized.filters[0].kinds)
|
||||
assertEquals(10, deserialized.filters[0].limit)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeEventCmd_matchesJackson() {
|
||||
val cmd = EventCmd(followCard)
|
||||
val jacksonJson = JacksonMapper.toJson(cmd)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(cmd)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeCloseCmd_matchesJackson() {
|
||||
val cmd = CloseCmd("sub1")
|
||||
val jacksonJson = JacksonMapper.toJson(cmd)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(cmd)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossDeserializationCommands() {
|
||||
val cmd = CloseCmd("sub1")
|
||||
|
||||
val jacksonJson = JacksonMapper.toJson(cmd)
|
||||
val kotlinDeserialized = KotlinSerializationMapper.fromJsonToCommand(jacksonJson)
|
||||
assertTrue(kotlinDeserialized is CloseCmd)
|
||||
assertEquals("sub1", (kotlinDeserialized as CloseCmd).subId)
|
||||
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(cmd)
|
||||
val jacksonDeserialized = JacksonMapper.fromJsonToCommand(kotlinJson)
|
||||
assertTrue(jacksonDeserialized is CloseCmd)
|
||||
assertEquals("sub1", (jacksonDeserialized as CloseCmd).subId)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// BunkerRequest Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeBunkerRequest_matchesJackson() {
|
||||
val req = BunkerRequest("id1", "connect", arrayOf("pubkey", "secret"))
|
||||
val jacksonJson = JacksonMapper.toJson(req)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(req)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeBunkerRequest() {
|
||||
val json = """{"id":"id1","method":"ping","params":[]}"""
|
||||
val deserialized = KotlinSerializationMapper.fromJsonTo<BunkerRequest>(json)
|
||||
assertEquals("id1", deserialized.id)
|
||||
assertEquals("ping", deserialized.method)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun crossDeserializationBunkerRequest() {
|
||||
val req = BunkerRequest("id1", "sign_event", arrayOf("eventJson"))
|
||||
val jacksonJson = JacksonMapper.toJson(req)
|
||||
val kotlinDeserialized = KotlinSerializationMapper.fromJsonTo<BunkerRequest>(jacksonJson)
|
||||
assertEquals(req.id, kotlinDeserialized.id)
|
||||
assertEquals(req.method, kotlinDeserialized.method)
|
||||
assertContentEquals(req.params, kotlinDeserialized.params)
|
||||
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(req)
|
||||
val jacksonDeserialized = JacksonMapper.fromJsonTo<BunkerRequest>(kotlinJson)
|
||||
assertEquals(req.id, jacksonDeserialized.id)
|
||||
assertEquals(req.method, jacksonDeserialized.method)
|
||||
assertContentEquals(req.params, jacksonDeserialized.params)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// BunkerResponse Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun serializeBunkerResponse_matchesJackson() {
|
||||
val resp = BunkerResponse("id1", "ok", null)
|
||||
val jacksonJson = JacksonMapper.toJson(resp)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(resp)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun serializeBunkerResponseWithError_matchesJackson() {
|
||||
val resp = BunkerResponse("id1", null, "something went wrong")
|
||||
val jacksonJson = JacksonMapper.toJson(resp)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(resp)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun deserializeBunkerResponse() {
|
||||
val json = """{"id":"id1","result":"pong"}"""
|
||||
val deserialized = KotlinSerializationMapper.fromJsonTo<BunkerResponse>(json)
|
||||
assertEquals("id1", deserialized.id)
|
||||
assertNotNull(deserialized.result)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// OptimizedSerializable toJson dispatch Tests
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun toJsonDispatchForFilter() {
|
||||
val filter = Filter(kinds = listOf(1))
|
||||
val jacksonJson = JacksonMapper.toJson(filter)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(filter)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toJsonDispatchForRumor() {
|
||||
val jacksonJson = JacksonMapper.toJson(followCardRumor)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCardRumor)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun toJsonDispatchForEventTemplate() {
|
||||
val jacksonJson = JacksonMapper.toJson(followCardTemplate)
|
||||
val kotlinJson = KotlinSerializationMapper.toJson(followCardTemplate)
|
||||
assertEquals(jacksonJson, kotlinJson)
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Edge Cases
|
||||
// =========================================================================
|
||||
|
||||
@Test
|
||||
fun emptyContentEvent() {
|
||||
val event =
|
||||
FollowListEvent(
|
||||
id = "a".repeat(64),
|
||||
pubKey = "b".repeat(64),
|
||||
createdAt = 0,
|
||||
tags = emptyArray(),
|
||||
content = "",
|
||||
sig = "c".repeat(64),
|
||||
)
|
||||
val json = KotlinSerializationMapper.toJson(event)
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
assertEquals("", deserialized.content)
|
||||
assertEquals(0, deserialized.tags.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun largeTagArray() {
|
||||
val largeTags = Array(100) { i -> arrayOf("p", "key$i") }
|
||||
val json = KotlinSerializationMapper.toJson(largeTags)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonToTagArray(json)
|
||||
assertEquals(100, deserialized.size)
|
||||
assertEquals("key99", deserialized[99][1])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun eventWithUnicodeContent() {
|
||||
val content = "Hello \uD83D\uDE00 world \u00E9\u00E8\u00EA"
|
||||
val event =
|
||||
FollowListEvent(
|
||||
id = "a".repeat(64),
|
||||
pubKey = "b".repeat(64),
|
||||
createdAt = 1000,
|
||||
tags = emptyArray(),
|
||||
content = content,
|
||||
sig = "c".repeat(64),
|
||||
)
|
||||
val json = KotlinSerializationMapper.toJson(event)
|
||||
val deserialized = KotlinSerializationMapper.fromJson(json)
|
||||
assertEquals(content, deserialized.content)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun filterWithMultipleTagTypes() {
|
||||
val filter =
|
||||
Filter(
|
||||
tags =
|
||||
mapOf(
|
||||
"p" to listOf("pubkey1", "pubkey2"),
|
||||
"e" to listOf("eventid1"),
|
||||
"t" to listOf("nostr", "bitcoin"),
|
||||
),
|
||||
)
|
||||
val json = KotlinSerializationMapper.toJson(filter)
|
||||
val deserialized = KotlinSerializationMapper.fromJsonTo<Filter>(json)
|
||||
|
||||
assertEquals(3, deserialized.tags?.size)
|
||||
assertEquals(listOf("pubkey1", "pubkey2"), deserialized.tags?.get("p"))
|
||||
assertEquals(listOf("eventid1"), deserialized.tags?.get("e"))
|
||||
assertEquals(listOf("nostr", "bitcoin"), deserialized.tags?.get("t"))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user