Skip to content

Commit

Permalink
Fix: reaction codec crash (#116)
Browse files Browse the repository at this point in the history
* fix: don't crash for unknown schemas and actions

* fix the serialization of the new seal object class
  • Loading branch information
nplasterer authored Sep 13, 2023
1 parent bcb2976 commit c6900f7
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ class ReactionTest {
val canonical = codec.decode(canonicalEncoded)
val legacy = codec.decode(legacyEncoded)

assertEquals(ReactionAction.added, canonical.action)
assertEquals(ReactionAction.added, legacy.action)
assertEquals(ReactionAction.Added, canonical.action)
assertEquals(ReactionAction.Added, legacy.action)
assertEquals("smile", canonical.content)
assertEquals("smile", legacy.content)
assertEquals("abc123", canonical.reference)
assertEquals("abc123", legacy.reference)
assertEquals(ReactionSchema.shortcode, canonical.schema)
assertEquals(ReactionSchema.shortcode, legacy.schema)
assertEquals(ReactionSchema.Shortcode, canonical.schema)
assertEquals(ReactionSchema.Shortcode, legacy.schema)
}

@Test
Expand All @@ -75,9 +75,9 @@ class ReactionTest {

val attachment = Reaction(
reference = messageToReact.id,
action = ReactionAction.added,
action = ReactionAction.Added,
content = "U+1F603",
schema = ReactionSchema.unicode
schema = ReactionSchema.Unicode
)

aliceConversation.send(
Expand All @@ -90,8 +90,8 @@ class ReactionTest {
val content: Reaction? = messages.first().content()
assertEquals("U+1F603", content?.content)
assertEquals(messageToReact.id, content?.reference)
assertEquals(ReactionAction.added, content?.action)
assertEquals(ReactionSchema.unicode, content?.schema)
assertEquals(ReactionAction.Added, content?.action)
assertEquals(ReactionSchema.Unicode, content?.schema)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package org.xmtp.android.library.codecs

import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.protobuf.kotlin.toByteStringUtf8
import java.lang.reflect.Type

val ContentTypeReaction = ContentTypeIdBuilder.builderFromAuthorityId(
"xmtp.org",
Expand All @@ -17,47 +24,109 @@ data class Reaction(
val schema: ReactionSchema,
)

enum class ReactionAction {
added, removed
sealed class ReactionAction {
object Removed : ReactionAction()
object Added : ReactionAction()
object Unknown : ReactionAction()
}

enum class ReactionSchema {
unicode, shortcode, custom
sealed class ReactionSchema {
object Unicode : ReactionSchema()
object Shortcode : ReactionSchema()
object Custom : ReactionSchema()
object Unknown : ReactionSchema()
}

private fun getReactionSchema(schema: String): ReactionSchema {
return when (schema) {
"unicode" -> ReactionSchema.Unicode
"shortcode" -> ReactionSchema.Shortcode
"custom" -> ReactionSchema.Custom
else -> ReactionSchema.Unknown
}
}

private fun getReactionAction(action: String): ReactionAction {
return when (action) {
"removed" -> ReactionAction.Removed
"added" -> ReactionAction.Added
else -> ReactionAction.Unknown
}
}

data class ReactionCodec(override var contentType: ContentTypeId = ContentTypeReaction) :
ContentCodec<Reaction> {

override fun encode(content: Reaction): EncodedContent {
val gson = GsonBuilder().create()
val gson = GsonBuilder()
.registerTypeAdapter(Reaction::class.java, ReactionSerializer())
.create()

return EncodedContent.newBuilder().also {
it.type = ContentTypeReaction
it.content = gson.toJson(content).toByteStringUtf8()
}.build()
}

override fun decode(content: EncodedContent): Reaction {
val text = content.content.toStringUtf8()
val json = content.content.toStringUtf8()

// First try to decode it in the canonical form.
val gson = GsonBuilder()
.registerTypeAdapter(Reaction::class.java, ReactionDeserializer())
.create()
try {
return GsonBuilder().create().fromJson(text, Reaction::class.java)
return gson.fromJson(json, Reaction::class.java)
} catch (ignore: Exception) {
}

// If that fails, try to decode it in the legacy form.
return Reaction(
reference = content.parametersMap["reference"] ?: "",
action = content.parametersMap["action"]?.let { ReactionAction.valueOf(it) }!!,
schema = content.parametersMap["schema"]?.let { ReactionSchema.valueOf(it) }!!,
content = text,
action = getReactionAction(content.parametersMap["action"]?.lowercase() ?: ""),
schema = getReactionSchema(content.parametersMap["schema"]?.lowercase() ?: ""),
content = json,
)
}

override fun fallback(content: Reaction): String? {
return when (content.action) {
ReactionAction.added -> "Reacted “${content.content}” to an earlier message"
ReactionAction.removed -> "Removed “${content.content}” from an earlier message"
ReactionAction.Added -> "Reacted “${content.content}” to an earlier message"
ReactionAction.Removed -> "Removed “${content.content}” from an earlier message"
else -> null
}
}
}

private class ReactionSerializer : JsonSerializer<Reaction> {
override fun serialize(
src: Reaction,
typeOfSrc: Type,
context: JsonSerializationContext,
): JsonObject {
val json = JsonObject()
json.addProperty("reference", src.reference)
json.addProperty("action", src.action.javaClass.simpleName.lowercase())
json.addProperty("content", src.content)
json.addProperty("schema", src.schema.javaClass.simpleName.lowercase())
return json
}
}

private class ReactionDeserializer : JsonDeserializer<Reaction> {
override fun deserialize(
json: JsonElement,
typeOfT: Type?,
context: JsonDeserializationContext?,
): Reaction {
val jsonObject = json.asJsonObject
val reference = jsonObject.get("reference").asString
val actionStr = jsonObject.get("action").asString.lowercase()
val content = jsonObject.get("content").asString
val schemaStr = jsonObject.get("schema").asString.lowercase()

val action = getReactionAction(actionStr)
val schema = getReactionSchema(schemaStr)

return Reaction(reference, action, content, schema)
}
}

0 comments on commit c6900f7

Please sign in to comment.