NIP-30: use Address type for emoji-set reference instead of raw string
Makes the API safer: callers can't accidentally pass an unparsed or malformed identifier. EmojiUrlTag.emojiSet is now Address?, serialized via toValue() and parsed via Address.parse() (which rejects anything that isn't a well-formed kind:pubkey:dTag).
This commit is contained in:
+4
-3
@@ -21,12 +21,13 @@
|
|||||||
package com.vitorpamplona.quartz.nip30CustomEmoji
|
package com.vitorpamplona.quartz.nip30CustomEmoji
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class EmojiUrlTag(
|
data class EmojiUrlTag(
|
||||||
val code: String,
|
val code: String,
|
||||||
val url: String,
|
val url: String,
|
||||||
val emojiSet: String? = null,
|
val emojiSet: Address? = null,
|
||||||
) {
|
) {
|
||||||
fun encode(): String = ":$code:$url"
|
fun encode(): String = ":$code:$url"
|
||||||
|
|
||||||
@@ -34,7 +35,7 @@ data class EmojiUrlTag(
|
|||||||
|
|
||||||
fun toTagArray() =
|
fun toTagArray() =
|
||||||
if (emojiSet != null) {
|
if (emojiSet != null) {
|
||||||
arrayOf(TAG_NAME, code, url, emojiSet)
|
arrayOf(TAG_NAME, code, url, emojiSet.toValue())
|
||||||
} else {
|
} else {
|
||||||
arrayOf(TAG_NAME, code, url)
|
arrayOf(TAG_NAME, code, url)
|
||||||
}
|
}
|
||||||
@@ -60,7 +61,7 @@ data class EmojiUrlTag(
|
|||||||
|
|
||||||
fun parse(tag: Array<String>): EmojiUrlTag? =
|
fun parse(tag: Array<String>): EmojiUrlTag? =
|
||||||
if (tag.size > 2 && tag[0] == TAG_NAME) {
|
if (tag.size > 2 && tag[0] == TAG_NAME) {
|
||||||
EmojiUrlTag(tag[1], tag[2], tag.getOrNull(3))
|
EmojiUrlTag(tag[1], tag[2], tag.getOrNull(3)?.let(Address::parse))
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip30CustomEmoji
|
package com.vitorpamplona.quartz.nip30CustomEmoji
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder
|
||||||
|
|
||||||
@@ -28,7 +29,7 @@ fun <T : Event> TagArrayBuilder<T>.emoji(tag: EmojiUrlTag) = add(tag.toTagArray(
|
|||||||
fun <T : Event> TagArrayBuilder<T>.emoji(
|
fun <T : Event> TagArrayBuilder<T>.emoji(
|
||||||
code: String,
|
code: String,
|
||||||
url: String,
|
url: String,
|
||||||
emojiSet: String? = null,
|
emojiSet: Address? = null,
|
||||||
) = add(EmojiUrlTag(code, url, emojiSet).toTagArray())
|
) = add(EmojiUrlTag(code, url, emojiSet).toTagArray())
|
||||||
|
|
||||||
fun <T : Event> TagArrayBuilder<T>.emojis(tags: List<EmojiUrlTag>) = addAll(tags.map { it.toTagArray() })
|
fun <T : Event> TagArrayBuilder<T>.emojis(tags: List<EmojiUrlTag>) = addAll(tags.map { it.toTagArray() })
|
||||||
|
|||||||
+17
-4
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip30CustomEmoji
|
package com.vitorpamplona.quartz.nip30CustomEmoji
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertContentEquals
|
import kotlin.test.assertContentEquals
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@@ -28,6 +29,10 @@ import kotlin.test.assertNull
|
|||||||
import kotlin.test.assertTrue
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class Nip30Test {
|
class Nip30Test {
|
||||||
|
companion object {
|
||||||
|
private const val TEST_PUBKEY = "0000000000000000000000000000000000000000000000000000000000000abc"
|
||||||
|
}
|
||||||
|
|
||||||
@Test()
|
@Test()
|
||||||
fun parseEmoji() {
|
fun parseEmoji() {
|
||||||
val tags = mapOf(":soapbox:" to "http://soapbox")
|
val tags = mapOf(":soapbox:" to "http://soapbox")
|
||||||
@@ -177,17 +182,25 @@ class Nip30Test {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun emojiTagWithEmojiSetRoundtrip() {
|
fun emojiTagWithEmojiSetRoundtrip() {
|
||||||
val setAddress = "30030:abc123:emojis-fall"
|
val setAddress = Address(30030, TEST_PUBKEY, "emojis-fall")
|
||||||
val tag = EmojiUrlTag("soapbox", "http://soapbox", setAddress)
|
val tag = EmojiUrlTag("soapbox", "http://soapbox", setAddress)
|
||||||
assertContentEquals(arrayOf("emoji", "soapbox", "http://soapbox", setAddress), tag.toTagArray())
|
assertContentEquals(arrayOf("emoji", "soapbox", "http://soapbox", setAddress.toValue()), tag.toTagArray())
|
||||||
assertEquals(tag, EmojiUrlTag.parse(tag.toTagArray()))
|
assertEquals(tag, EmojiUrlTag.parse(tag.toTagArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parseIgnoresTrailingFields() {
|
fun parseIgnoresTrailingFields() {
|
||||||
val tag = arrayOf("emoji", "soapbox", "http://soapbox", "30030:abc123:pack", "extra")
|
val setAddress = Address(30030, TEST_PUBKEY, "pack")
|
||||||
|
val tag = arrayOf("emoji", "soapbox", "http://soapbox", setAddress.toValue(), "extra")
|
||||||
val parsed = EmojiUrlTag.parse(tag)
|
val parsed = EmojiUrlTag.parse(tag)
|
||||||
assertEquals(EmojiUrlTag("soapbox", "http://soapbox", "30030:abc123:pack"), parsed)
|
assertEquals(EmojiUrlTag("soapbox", "http://soapbox", setAddress), parsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun parseMalformedEmojiSetAddressYieldsNull() {
|
||||||
|
val tag = arrayOf("emoji", "soapbox", "http://soapbox", "not-an-address")
|
||||||
|
val parsed = EmojiUrlTag.parse(tag)
|
||||||
|
assertEquals(EmojiUrlTag("soapbox", "http://soapbox", null), parsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user