Skip to content

Commit 8b2616f

Browse files
committed
[AIT-208] feat: update LiveObjects messages fields to support protocol v6
- Expose typed fields on public `ObjectData` (`boolean`, `bytes`, `number`, `string`, `json`) - Remove previous field names on `ObjectData`
1 parent f819138 commit 8b2616f

16 files changed

Lines changed: 255 additions & 334 deletions

File tree

liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,19 +161,4 @@ internal fun ObjectsAdapter.throwIfEchoMessagesDisabled() {
161161
}
162162
}
163163

164-
internal class Binary(val data: ByteArray) {
165-
override fun equals(other: Any?): Boolean {
166-
if (this === other) return true
167-
if (other !is Binary) return false
168-
return data.contentEquals(other.data)
169-
}
170-
171-
override fun hashCode(): Int {
172-
return data.contentHashCode()
173-
}
174-
}
175-
176-
internal fun Binary.size(): Int {
177-
return data.size
178-
}
179164

liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt

Lines changed: 28 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package io.ably.lib.objects
22

3+
import com.google.gson.JsonElement
34
import com.google.gson.JsonObject
45

56
import com.google.gson.annotations.JsonAdapter
67
import com.google.gson.annotations.SerializedName
78
import io.ably.lib.objects.serialization.ObjectDataJsonSerializer
89
import io.ably.lib.objects.serialization.gson
10+
import java.util.Base64
911

1012
/**
1113
* An enum class representing the different actions that can be performed on an object.
@@ -42,28 +44,21 @@ internal data class ObjectData(
4244
*/
4345
val objectId: String? = null,
4446

45-
/**
46-
* String, number, boolean or binary - a concrete value of the object
47-
* Spec: OD2c
48-
*/
49-
val value: ObjectValue? = null,
50-
)
47+
/** String value. Spec: OD2c */
48+
val string: String? = null,
5149

52-
/**
53-
* Represents a value that can be a String, Number, Boolean, Binary, JsonObject or JsonArray.
54-
* Provides compile-time type safety through sealed class pattern.
55-
* Spec: OD2c
56-
*/
57-
internal sealed class ObjectValue {
58-
abstract val value: Any
59-
60-
data class String(override val value: kotlin.String) : ObjectValue()
61-
data class Number(override val value: kotlin.Number) : ObjectValue()
62-
data class Boolean(override val value: kotlin.Boolean) : ObjectValue()
63-
data class Binary(override val value: io.ably.lib.objects.Binary) : ObjectValue()
64-
data class JsonObject(override val value: com.google.gson.JsonObject) : ObjectValue()
65-
data class JsonArray(override val value: com.google.gson.JsonArray) : ObjectValue()
66-
}
50+
/** Numeric value. Spec: OD2c */
51+
val number: Double? = null,
52+
53+
/** Boolean value. Spec: OD2c */
54+
val boolean: Boolean? = null,
55+
56+
/** Binary value encoded as a base64 string. Spec: OD2c */
57+
val bytes: String? = null,
58+
59+
/** JSON object or array value. Spec: OD2c */
60+
val json: JsonElement? = null,
61+
)
6762

6863
/**
6964
* Payload for MAP_CREATE operation.
@@ -529,23 +524,19 @@ private fun ObjectsMapEntry.size(): Int {
529524
* Spec: OD3
530525
*/
531526
private fun ObjectData.size(): Int {
532-
return value?.size() ?: 0 // Spec: OD3f
533-
}
534-
535-
/**
536-
* Calculates the size of an ObjectValue in bytes.
537-
* Spec: OD3*
538-
*/
539-
private fun ObjectValue.size(): Int {
540-
return when (this) {
541-
is ObjectValue.Boolean -> 1 // Spec: OD3b
542-
is ObjectValue.Binary -> value.size() // Spec: OD3c
543-
is ObjectValue.Number -> 8 // Spec: OD3d
544-
is ObjectValue.String -> value.byteSize // Spec: OD3e
545-
is ObjectValue.JsonObject, is ObjectValue.JsonArray -> value.toString().byteSize // Spec: OD3e
546-
}
527+
string?.let { return it.byteSize } // Spec: OD3e
528+
number?.let { return 8 } // Spec: OD3d
529+
boolean?.let { return 1 } // Spec: OD3b
530+
bytes?.let { return Base64.getDecoder().decode(it).size } // Spec: OD3c
531+
json?.let { return it.toString().byteSize } // Spec: OD3e
532+
return 0
547533
}
548534

549535
internal fun ObjectData?.isInvalid(): Boolean {
550-
return this?.objectId.isNullOrEmpty() && this?.value == null
536+
return this?.objectId.isNullOrEmpty() &&
537+
this?.string == null &&
538+
this?.number == null &&
539+
this?.boolean == null &&
540+
this?.bytes == null &&
541+
this?.json == null
551542
}
Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package io.ably.lib.objects.serialization
22

33
import com.google.gson.*
4-
import io.ably.lib.objects.Binary
54
import io.ably.lib.objects.ObjectsMapSemantics
65
import io.ably.lib.objects.ObjectData
76
import io.ably.lib.objects.ObjectMessage
87
import io.ably.lib.objects.ObjectOperationAction
9-
import io.ably.lib.objects.ObjectValue
108
import java.lang.reflect.Type
11-
import java.util.*
129
import kotlin.enums.EnumEntries
1310

1411
// Gson instance for JSON serialization/deserialization
@@ -45,42 +42,26 @@ internal class ObjectDataJsonSerializer : JsonSerializer<ObjectData>, JsonDeseri
4542
override fun serialize(src: ObjectData, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
4643
val obj = JsonObject()
4744
src.objectId?.let { obj.addProperty("objectId", it) }
48-
49-
src.value?.let { v ->
50-
when (v) {
51-
is ObjectValue.Boolean -> obj.addProperty("boolean", v.value)
52-
is ObjectValue.String -> obj.addProperty("string", v.value)
53-
is ObjectValue.Number -> obj.addProperty("number", v.value.toDouble())
54-
is ObjectValue.Binary -> obj.addProperty("bytes", Base64.getEncoder().encodeToString(v.value.data))
55-
is ObjectValue.JsonObject, is ObjectValue.JsonArray -> obj.addProperty("json", v.value.toString()) // Spec: OD4c5
56-
}
57-
}
45+
src.string?.let { obj.addProperty("string", it) }
46+
src.number?.let { obj.addProperty("number", it) }
47+
src.boolean?.let { obj.addProperty("boolean", it) }
48+
src.bytes?.let { obj.addProperty("bytes", it) }
49+
src.json?.let { obj.addProperty("json", it.toString()) } // Spec: OD4c5
5850
return obj
5951
}
6052

6153
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): ObjectData {
6254
val obj = if (json.isJsonObject) json.asJsonObject else throw JsonParseException("Expected JsonObject")
6355
val objectId = if (obj.has("objectId")) obj.get("objectId").asString else null
64-
val value = when {
65-
obj.has("boolean") -> ObjectValue.Boolean(obj.get("boolean").asBoolean)
66-
obj.has("string") -> ObjectValue.String(obj.get("string").asString)
67-
obj.has("number") -> ObjectValue.Number(obj.get("number").asDouble)
68-
obj.has("bytes") -> ObjectValue.Binary(Binary(Base64.getDecoder().decode(obj.get("bytes").asString)))
69-
obj.has("json") -> {
70-
val jsonElement = JsonParser.parseString(obj.get("json").asString)
71-
when {
72-
jsonElement.isJsonObject -> ObjectValue.JsonObject(jsonElement.asJsonObject)
73-
jsonElement.isJsonArray -> ObjectValue.JsonArray(jsonElement.asJsonArray)
74-
else -> throw JsonParseException("Invalid JSON structure")
75-
}
76-
}
77-
else -> {
78-
if (objectId != null)
79-
null
80-
else
81-
throw JsonParseException("Since objectId is not present, at least one of the value fields must be present")
82-
}
56+
val string = if (obj.has("string")) obj.get("string").asString else null
57+
val number = if (obj.has("number")) obj.get("number").asDouble else null
58+
val boolean = if (obj.has("boolean")) obj.get("boolean").asBoolean else null
59+
val bytes = if (obj.has("bytes")) obj.get("bytes").asString else null
60+
val json = if (obj.has("json")) JsonParser.parseString(obj.get("json").asString) else null
61+
62+
if (objectId == null && string == null && number == null && boolean == null && bytes == null && json == null) {
63+
throw JsonParseException("Since objectId is not present, at least one of the value fields must be present")
8364
}
84-
return ObjectData(objectId, value)
65+
return ObjectData(objectId = objectId, string = string, number = number, boolean = boolean, bytes = bytes, json = json)
8566
}
8667
}

liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt

Lines changed: 45 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.ably.lib.objects.serialization
22

3+
import com.google.gson.JsonElement
34
import com.google.gson.JsonObject
45
import com.google.gson.JsonParser
56
import io.ably.lib.objects.*
6-
import io.ably.lib.objects.Binary
77
import io.ably.lib.objects.CounterCreate
88
import io.ably.lib.objects.CounterCreateWithObjectId
99
import io.ably.lib.objects.CounterInc
@@ -22,7 +22,7 @@ import io.ably.lib.objects.ObjectMessage
2222
import io.ably.lib.objects.ObjectOperation
2323
import io.ably.lib.objects.ObjectOperationAction
2424
import io.ably.lib.objects.ObjectState
25-
import io.ably.lib.objects.ObjectValue
25+
import java.util.Base64
2626
import io.ably.lib.util.Serialisation
2727
import org.msgpack.core.MessageFormat
2828
import org.msgpack.core.MessagePacker
@@ -823,9 +823,11 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
823823
var fieldCount = 0
824824

825825
if (objectId != null) fieldCount++
826-
value?.let {
827-
fieldCount++
828-
}
826+
if (string != null) fieldCount++
827+
if (number != null) fieldCount++
828+
if (boolean != null) fieldCount++
829+
if (bytes != null) fieldCount++
830+
if (json != null) fieldCount++
829831

830832
packer.packMapHeader(fieldCount)
831833

@@ -834,34 +836,31 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
834836
packer.packString(objectId)
835837
}
836838

837-
value?.let { v ->
838-
when (v) {
839-
is ObjectValue.Boolean -> {
840-
packer.packString("boolean")
841-
packer.packBoolean(v.value)
842-
}
843-
is ObjectValue.String -> {
844-
packer.packString("string")
845-
packer.packString(v.value)
846-
}
847-
is ObjectValue.Number -> {
848-
packer.packString("number")
849-
packer.packDouble(v.value.toDouble())
850-
}
851-
is ObjectValue.Binary -> {
852-
packer.packString("bytes")
853-
packer.packBinaryHeader(v.value.data.size)
854-
packer.writePayload(v.value.data)
855-
}
856-
is ObjectValue.JsonObject -> {
857-
packer.packString("json")
858-
packer.packString(v.value.toString())
859-
}
860-
is ObjectValue.JsonArray -> {
861-
packer.packString("json")
862-
packer.packString(v.value.toString())
863-
}
864-
}
839+
if (string != null) {
840+
packer.packString("string")
841+
packer.packString(string)
842+
}
843+
844+
if (number != null) {
845+
packer.packString("number")
846+
packer.packDouble(number)
847+
}
848+
849+
if (boolean != null) {
850+
packer.packString("boolean")
851+
packer.packBoolean(boolean)
852+
}
853+
854+
if (bytes != null) {
855+
val rawBytes = Base64.getDecoder().decode(bytes)
856+
packer.packString("bytes")
857+
packer.packBinaryHeader(rawBytes.size)
858+
packer.writePayload(rawBytes)
859+
}
860+
861+
if (json != null) {
862+
packer.packString("json")
863+
packer.packString(json.toString())
865864
}
866865
}
867866

@@ -871,7 +870,11 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
871870
private fun readObjectData(unpacker: MessageUnpacker): ObjectData {
872871
val fieldCount = unpacker.unpackMapHeader()
873872
var objectId: String? = null
874-
var value: ObjectValue? = null
873+
var string: String? = null
874+
var number: Double? = null
875+
var boolean: Boolean? = null
876+
var bytes: String? = null
877+
var json: JsonElement? = null
875878

876879
for (i in 0 until fieldCount) {
877880
val fieldName = unpacker.unpackString().intern()
@@ -884,28 +887,19 @@ private fun readObjectData(unpacker: MessageUnpacker): ObjectData {
884887

885888
when (fieldName) {
886889
"objectId" -> objectId = unpacker.unpackString()
887-
"boolean" -> value = ObjectValue.Boolean(unpacker.unpackBoolean())
888-
"string" -> value = ObjectValue.String(unpacker.unpackString())
889-
"number" -> value = ObjectValue.Number(unpacker.unpackDouble())
890+
"string" -> string = unpacker.unpackString()
891+
"number" -> number = unpacker.unpackDouble()
892+
"boolean" -> boolean = unpacker.unpackBoolean()
890893
"bytes" -> {
891894
val size = unpacker.unpackBinaryHeader()
892-
val bytes = ByteArray(size)
893-
unpacker.readPayload(bytes)
894-
value = ObjectValue.Binary(Binary(bytes))
895-
}
896-
"json" -> {
897-
val jsonString = unpacker.unpackString()
898-
val parsed = JsonParser.parseString(jsonString)
899-
value = when {
900-
parsed.isJsonObject -> ObjectValue.JsonObject(parsed.asJsonObject)
901-
parsed.isJsonArray -> ObjectValue.JsonArray(parsed.asJsonArray)
902-
else ->
903-
throw ablyException("Invalid JSON string for json field", ErrorCode.MapValueDataTypeUnsupported, HttpStatusCode.InternalServerError)
904-
}
895+
val rawBytes = ByteArray(size)
896+
unpacker.readPayload(rawBytes)
897+
bytes = Base64.getEncoder().encodeToString(rawBytes)
905898
}
899+
"json" -> json = JsonParser.parseString(unpacker.unpackString())
906900
else -> unpacker.skipValue()
907901
}
908902
}
909903

910-
return ObjectData(objectId = objectId, value = value)
904+
return ObjectData(objectId = objectId, string = string, number = number, boolean = boolean, bytes = bytes, json = json)
911905
}

liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import io.ably.lib.objects.type.map.LiveMapValue
1818
import io.ably.lib.objects.type.noOp
1919
import io.ably.lib.util.Log
2020
import kotlinx.coroutines.runBlocking
21+
import java.util.Base64
2122
import java.util.concurrent.ConcurrentHashMap
2223
import java.util.AbstractMap
2324

@@ -219,30 +220,22 @@ internal class DefaultLiveMap private constructor(
219220
*/
220221
private fun fromLiveMapValue(value: LiveMapValue): ObjectData {
221222
return when {
222-
value.isLiveMap || value.isLiveCounter -> {
223+
value.isLiveMap || value.isLiveCounter ->
223224
ObjectData(objectId = (value.value as BaseRealtimeObject).objectId)
224-
}
225-
value.isBoolean -> {
226-
ObjectData(value = ObjectValue.Boolean(value.asBoolean))
227-
}
228-
value.isBinary -> {
229-
ObjectData(value = ObjectValue.Binary(Binary(value.asBinary)))
230-
}
231-
value.isNumber -> {
232-
ObjectData(value = ObjectValue.Number(value.asNumber))
233-
}
234-
value.isString -> {
235-
ObjectData(value = ObjectValue.String(value.asString))
236-
}
237-
value.isJsonObject -> {
238-
ObjectData(value = ObjectValue.JsonObject(value.asJsonObject))
239-
}
240-
value.isJsonArray -> {
241-
ObjectData(value = ObjectValue.JsonArray(value.asJsonArray))
242-
}
243-
else -> {
225+
value.isBoolean ->
226+
ObjectData(boolean = value.asBoolean)
227+
value.isBinary ->
228+
ObjectData(bytes = Base64.getEncoder().encodeToString(value.asBinary))
229+
value.isNumber ->
230+
ObjectData(number = value.asNumber.toDouble())
231+
value.isString ->
232+
ObjectData(string = value.asString)
233+
value.isJsonObject ->
234+
ObjectData(json = value.asJsonObject)
235+
value.isJsonArray ->
236+
ObjectData(json = value.asJsonArray)
237+
else ->
244238
throw IllegalArgumentException("Unsupported value type")
245-
}
246239
}
247240
}
248241
}

0 commit comments

Comments
 (0)