feat: Add kotlinx.serialization KSerializers mirroring all Jackson custom serializers
Add 15 KSerializer implementations in commonMain that replicate the exact behavior of all 21 Jackson serializer/deserializer registrations: - EventKSerializer, TagArrayKSerializer, FilterKSerializer - EventTemplateKSerializer, RumorKSerializer, CountResultKSerializer - MessageKSerializer (8 message types), CommandKSerializer (5 command types) - BunkerRequest/Response/MessageKSerializer (NIP-46) - Nip47Request/Response/NotificationKSerializer (NIP-47) - KotlinSerializationMapper as the main entry point Uses JsonElement-based approach for performance. All serializers produce identical JSON output to Jackson. Includes comprehensive test suite (35+ tests) verifying exact output parity and cross-deserialization between Jackson and kotlinx.serialization. https://claude.ai/code/session_01QBc4Pb7a6m4TfLZSJKVdjw
This commit is contained in:
+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.nip01Core.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.nip01Core.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.nip01Core.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* 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.nip47WalletConnect.Notification
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.Request
|
||||||
|
import com.vitorpamplona.quartz.nip47WalletConnect.Response
|
||||||
|
import com.vitorpamplona.quartz.nip59Giftwrap.rumors.Rumor
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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.nip01Core.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.nip01Core.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.nip01Core.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,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+93
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+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))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+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