Merge branch 'main' of https://github.com/vitorpamplona/amethyst
* 'main' of https://github.com/vitorpamplona/amethyst: fix lint warnings add custom serializer that accepts both single integers and arrays add tests New Crowdin translations by GitHub Action updated cz, de, pt, sv
This commit is contained in:
+90
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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.nip11RelayInfo
|
||||
|
||||
import com.vitorpamplona.quartz.utils.Log
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
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.JsonNull
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.int
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
object FlexibleIntListSerializer : KSerializer<List<Int>?> {
|
||||
private val listSerializer = ListSerializer(Int.serializer())
|
||||
|
||||
override val descriptor: SerialDescriptor = listSerializer.descriptor
|
||||
|
||||
override fun deserialize(decoder: Decoder): List<Int>? {
|
||||
require(decoder is JsonDecoder) { "This serializer can only be used with Json format" }
|
||||
|
||||
return when (val element = decoder.decodeJsonElement()) {
|
||||
// Handle JSON null
|
||||
is JsonNull -> null
|
||||
|
||||
// Handle array format (spec-compliant): [1, 2, 3]
|
||||
is JsonArray -> {
|
||||
element.mapNotNull { arrayElement ->
|
||||
try {
|
||||
arrayElement.jsonPrimitive.int
|
||||
} catch (e: Exception) {
|
||||
// Skip elements that aren't valid integers (strings, booleans, floats, etc.)
|
||||
Log.w("FlexibleIntListSerializer", "Invalid element in array: $arrayElement", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle single integer format (malformed but found in the wild): 1
|
||||
is JsonPrimitive if !element.isString -> {
|
||||
try {
|
||||
listOf(element.int)
|
||||
} catch (e: Exception) {
|
||||
// Can't parse as integer (e.g., float, boolean), treat as missing data
|
||||
Log.w("FlexibleIntListSerializer", "Invalid primitive: $element", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// Unsupported format (strings, objects, etc.)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
override fun serialize(
|
||||
encoder: Encoder,
|
||||
value: List<Int>?,
|
||||
) {
|
||||
if (value == null) {
|
||||
encoder.encodeNull()
|
||||
} else {
|
||||
listSerializer.serialize(encoder, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -32,6 +32,7 @@ class Nip11RelayInformation(
|
||||
val icon: String? = null,
|
||||
val pubkey: String? = null,
|
||||
val contact: String? = null,
|
||||
@Serializable(with = FlexibleIntListSerializer::class)
|
||||
val supported_nips: List<Int>? = null,
|
||||
val supported_nip_extensions: List<String>? = null,
|
||||
val software: String? = null,
|
||||
|
||||
+209
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* 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.nip11RelayInfo
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
class Nip11RelayInformationTest {
|
||||
// Test case for properly formatted relay info with array of supported NIPs
|
||||
val standardFormatJson =
|
||||
"""
|
||||
{
|
||||
"contact":"_@f7z.io",
|
||||
"description":"Nostr's Purple Pages",
|
||||
"limitation":{
|
||||
"max_limit":5000000,
|
||||
"max_message_length":131072,
|
||||
"max_subscriptions":50000
|
||||
},
|
||||
"name":"purplepag.es",
|
||||
"negentropy":1,
|
||||
"pubkey":"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
|
||||
"software":"git+https://github.com/hoytech/strfry.git",
|
||||
"supported_nips":[1,2,9,11],
|
||||
"version":"1.0.4"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
// Test case for malformed relay info where supported_nips is a single integer instead of an array
|
||||
val malformedSingleIntegerJson =
|
||||
"""
|
||||
{
|
||||
"contact":"_@f7z.io",
|
||||
"description":"Nostr's Purple Pages",
|
||||
"limitation":{
|
||||
"max_limit":5000000,
|
||||
"max_message_length":131072,
|
||||
"max_subscriptions":50000
|
||||
},
|
||||
"name":"purplepag.es",
|
||||
"negentropy":1,
|
||||
"pubkey":"fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
|
||||
"software":"git+https://github.com/hoytech/strfry.git",
|
||||
"supported_nips":1,
|
||||
"version":"1.0.4"
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
@Test
|
||||
fun `Parse standard format`() {
|
||||
val info = Nip11RelayInformation.fromJson(standardFormatJson)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("purplepag.es", info.name)
|
||||
assertEquals("Nostr's Purple Pages", info.description)
|
||||
assertEquals("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", info.pubkey)
|
||||
assertEquals(listOf(1, 2, 9, 11), info.supported_nips)
|
||||
assertEquals("1.0.4", info.version)
|
||||
assertEquals("git+https://github.com/hoytech/strfry.git", info.software)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse malformed single integer`() {
|
||||
val info = Nip11RelayInformation.fromJson(malformedSingleIntegerJson)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("purplepag.es", info.name)
|
||||
assertEquals("Nostr's Purple Pages", info.description)
|
||||
assertEquals("fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52", info.pubkey)
|
||||
// Single integer should be converted to a list with one element
|
||||
assertEquals(listOf(1), info.supported_nips)
|
||||
assertEquals("1.0.4", info.version)
|
||||
assertEquals("git+https://github.com/hoytech/strfry.git", info.software)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse null value`() {
|
||||
val json = """{"name":"test relay","supported_nips":null}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
assertNull(info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse missing field`() {
|
||||
val json = """{"name":"test relay","version":"1.0"}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
assertNull(info.supported_nips) // Field is optional with default null
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse empty array`() {
|
||||
val json = """{"name":"test relay","supported_nips":[]}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
assertEquals(emptyList(), info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse malformed array With invalid elements`() {
|
||||
// Array contains valid integers mixed with invalid elements (strings, floats, booleans)
|
||||
val json = """{"name":"test relay","supported_nips":[1,"invalid",3,4.5,true,11]}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// Should skip invalid elements and keep only valid integers
|
||||
assertEquals(listOf(1, 3, 11), info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse malformed array with all invalid elements`() {
|
||||
// Array contains only invalid elements
|
||||
val json = """{"name":"test relay","supported_nips":["one","two","three"]}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// All elements invalid, should result in empty list
|
||||
assertEquals(emptyList(), info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse invalid primitive float`() {
|
||||
// Float instead of int
|
||||
val json = """{"name":"test relay","supported_nips":1.5}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// Cannot parse float as integer, should be null
|
||||
assertNull(info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse invalid primitive boolean`() {
|
||||
// Boolean instead of int/array
|
||||
val json = """{"name":"test relay","supported_nips":true}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// Cannot parse boolean as integer, should be null
|
||||
assertNull(info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse invalid primitive string`() {
|
||||
// String instead of int/array
|
||||
val json = """{"name":"test relay","supported_nips":"1"}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// Strings are explicitly excluded (element.isString check), should be null
|
||||
assertNull(info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse invalid object`() {
|
||||
// Object instead of int/array
|
||||
val json = """{"name":"test relay","supported_nips":{"nip":1}}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
// Objects are unsupported format, should be null
|
||||
assertNull(info.supported_nips)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Parse large array`() {
|
||||
// Test with a large array to ensure no performance issues
|
||||
val nips = (1..100).joinToString(",")
|
||||
val json = """{"name":"test relay","supported_nips":[$nips]}"""
|
||||
val info = Nip11RelayInformation.fromJson(json)
|
||||
|
||||
assertNotNull(info)
|
||||
assertEquals("test relay", info.name)
|
||||
assertEquals((1..100).toList(), info.supported_nips)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user