Skip to content

Commit c6900f7

Browse files
authored
Fix: reaction codec crash (#116)
* fix: don't crash for unknown schemas and actions * fix the serialization of the new seal object class
1 parent bcb2976 commit c6900f7

File tree

2 files changed

+90
-21
lines changed

2 files changed

+90
-21
lines changed

library/src/androidTest/java/org/xmtp/android/library/ReactionTest.kt

+8-8
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,14 @@ class ReactionTest {
5050
val canonical = codec.decode(canonicalEncoded)
5151
val legacy = codec.decode(legacyEncoded)
5252

53-
assertEquals(ReactionAction.added, canonical.action)
54-
assertEquals(ReactionAction.added, legacy.action)
53+
assertEquals(ReactionAction.Added, canonical.action)
54+
assertEquals(ReactionAction.Added, legacy.action)
5555
assertEquals("smile", canonical.content)
5656
assertEquals("smile", legacy.content)
5757
assertEquals("abc123", canonical.reference)
5858
assertEquals("abc123", legacy.reference)
59-
assertEquals(ReactionSchema.shortcode, canonical.schema)
60-
assertEquals(ReactionSchema.shortcode, legacy.schema)
59+
assertEquals(ReactionSchema.Shortcode, canonical.schema)
60+
assertEquals(ReactionSchema.Shortcode, legacy.schema)
6161
}
6262

6363
@Test
@@ -75,9 +75,9 @@ class ReactionTest {
7575

7676
val attachment = Reaction(
7777
reference = messageToReact.id,
78-
action = ReactionAction.added,
78+
action = ReactionAction.Added,
7979
content = "U+1F603",
80-
schema = ReactionSchema.unicode
80+
schema = ReactionSchema.Unicode
8181
)
8282

8383
aliceConversation.send(
@@ -90,8 +90,8 @@ class ReactionTest {
9090
val content: Reaction? = messages.first().content()
9191
assertEquals("U+1F603", content?.content)
9292
assertEquals(messageToReact.id, content?.reference)
93-
assertEquals(ReactionAction.added, content?.action)
94-
assertEquals(ReactionSchema.unicode, content?.schema)
93+
assertEquals(ReactionAction.Added, content?.action)
94+
assertEquals(ReactionSchema.Unicode, content?.schema)
9595
}
9696
}
9797
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package org.xmtp.android.library.codecs
22

33
import com.google.gson.GsonBuilder
4+
import com.google.gson.JsonDeserializationContext
5+
import com.google.gson.JsonDeserializer
6+
import com.google.gson.JsonElement
7+
import com.google.gson.JsonObject
8+
import com.google.gson.JsonSerializationContext
9+
import com.google.gson.JsonSerializer
410
import com.google.protobuf.kotlin.toByteStringUtf8
11+
import java.lang.reflect.Type
512

613
val ContentTypeReaction = ContentTypeIdBuilder.builderFromAuthorityId(
714
"xmtp.org",
@@ -17,47 +24,109 @@ data class Reaction(
1724
val schema: ReactionSchema,
1825
)
1926

20-
enum class ReactionAction {
21-
added, removed
27+
sealed class ReactionAction {
28+
object Removed : ReactionAction()
29+
object Added : ReactionAction()
30+
object Unknown : ReactionAction()
2231
}
2332

24-
enum class ReactionSchema {
25-
unicode, shortcode, custom
33+
sealed class ReactionSchema {
34+
object Unicode : ReactionSchema()
35+
object Shortcode : ReactionSchema()
36+
object Custom : ReactionSchema()
37+
object Unknown : ReactionSchema()
38+
}
39+
40+
private fun getReactionSchema(schema: String): ReactionSchema {
41+
return when (schema) {
42+
"unicode" -> ReactionSchema.Unicode
43+
"shortcode" -> ReactionSchema.Shortcode
44+
"custom" -> ReactionSchema.Custom
45+
else -> ReactionSchema.Unknown
46+
}
47+
}
48+
49+
private fun getReactionAction(action: String): ReactionAction {
50+
return when (action) {
51+
"removed" -> ReactionAction.Removed
52+
"added" -> ReactionAction.Added
53+
else -> ReactionAction.Unknown
54+
}
2655
}
2756

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

3160
override fun encode(content: Reaction): EncodedContent {
32-
val gson = GsonBuilder().create()
61+
val gson = GsonBuilder()
62+
.registerTypeAdapter(Reaction::class.java, ReactionSerializer())
63+
.create()
64+
3365
return EncodedContent.newBuilder().also {
3466
it.type = ContentTypeReaction
3567
it.content = gson.toJson(content).toByteStringUtf8()
3668
}.build()
3769
}
3870

3971
override fun decode(content: EncodedContent): Reaction {
40-
val text = content.content.toStringUtf8()
72+
val json = content.content.toStringUtf8()
4173

42-
// First try to decode it in the canonical form.
74+
val gson = GsonBuilder()
75+
.registerTypeAdapter(Reaction::class.java, ReactionDeserializer())
76+
.create()
4377
try {
44-
return GsonBuilder().create().fromJson(text, Reaction::class.java)
78+
return gson.fromJson(json, Reaction::class.java)
4579
} catch (ignore: Exception) {
4680
}
4781

4882
// If that fails, try to decode it in the legacy form.
4983
return Reaction(
5084
reference = content.parametersMap["reference"] ?: "",
51-
action = content.parametersMap["action"]?.let { ReactionAction.valueOf(it) }!!,
52-
schema = content.parametersMap["schema"]?.let { ReactionSchema.valueOf(it) }!!,
53-
content = text,
85+
action = getReactionAction(content.parametersMap["action"]?.lowercase() ?: ""),
86+
schema = getReactionSchema(content.parametersMap["schema"]?.lowercase() ?: ""),
87+
content = json,
5488
)
5589
}
5690

5791
override fun fallback(content: Reaction): String? {
5892
return when (content.action) {
59-
ReactionAction.added -> "Reacted “${content.content}” to an earlier message"
60-
ReactionAction.removed -> "Removed “${content.content}” from an earlier message"
93+
ReactionAction.Added -> "Reacted “${content.content}” to an earlier message"
94+
ReactionAction.Removed -> "Removed “${content.content}” from an earlier message"
95+
else -> null
6196
}
6297
}
6398
}
99+
100+
private class ReactionSerializer : JsonSerializer<Reaction> {
101+
override fun serialize(
102+
src: Reaction,
103+
typeOfSrc: Type,
104+
context: JsonSerializationContext,
105+
): JsonObject {
106+
val json = JsonObject()
107+
json.addProperty("reference", src.reference)
108+
json.addProperty("action", src.action.javaClass.simpleName.lowercase())
109+
json.addProperty("content", src.content)
110+
json.addProperty("schema", src.schema.javaClass.simpleName.lowercase())
111+
return json
112+
}
113+
}
114+
115+
private class ReactionDeserializer : JsonDeserializer<Reaction> {
116+
override fun deserialize(
117+
json: JsonElement,
118+
typeOfT: Type?,
119+
context: JsonDeserializationContext?,
120+
): Reaction {
121+
val jsonObject = json.asJsonObject
122+
val reference = jsonObject.get("reference").asString
123+
val actionStr = jsonObject.get("action").asString.lowercase()
124+
val content = jsonObject.get("content").asString
125+
val schemaStr = jsonObject.get("schema").asString.lowercase()
126+
127+
val action = getReactionAction(actionStr)
128+
val schema = getReactionSchema(schemaStr)
129+
130+
return Reaction(reference, action, content, schema)
131+
}
132+
}

0 commit comments

Comments
 (0)