diff --git a/lib/src/main/java/io/ably/lib/transport/Defaults.java b/lib/src/main/java/io/ably/lib/transport/Defaults.java
index 83d5d83e4..66e3e897c 100644
--- a/lib/src/main/java/io/ably/lib/transport/Defaults.java
+++ b/lib/src/main/java/io/ably/lib/transport/Defaults.java
@@ -12,7 +12,7 @@ public class Defaults {
* spec: G4
*
*/
- public static final String ABLY_PROTOCOL_VERSION = "5";
+ public static final String ABLY_PROTOCOL_VERSION = "6";
public static final String ABLY_AGENT_VERSION = String.format("%s/%s", "ably-java", BuildConfig.VERSION);
diff --git a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java
index 0aa8f9c1a..fb258afb9 100644
--- a/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java
+++ b/lib/src/test/java/io/ably/lib/test/realtime/RealtimeHttpHeaderTest.java
@@ -81,7 +81,7 @@ public void realtime_websocket_param_test() {
* Defaults.ABLY_VERSION_PARAM, as ultimately the request param has been derived from those values.
*/
assertEquals("Verify correct version", requestParameters.get("v"),
- Collections.singletonList("5"));
+ Collections.singletonList("6"));
/* Spec RSC7d3
* This test should not directly validate version against Defaults.ABLY_AGENT_VERSION, nor
diff --git a/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java b/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java
index f26fda4eb..14578a03b 100644
--- a/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java
+++ b/lib/src/test/java/io/ably/lib/test/rest/HttpHeaderTest.java
@@ -81,7 +81,7 @@ public void header_lib_channel_publish() {
* from those values.
*/
Assert.assertNotNull("Expected headers", headers);
- Assert.assertEquals(headers.get("x-ably-version"), "5");
+ Assert.assertEquals(headers.get("x-ably-version"), "6");
Assert.assertEquals(headers.get("ably-agent"), expectedAblyAgentHeader);
// RSA7e2
Assert.assertNull("Shouldn't include 'x-ably-clientid' if `clientId` is not specified", headers.get("x-ably-clientid"));
diff --git a/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java b/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java
index 7f52ecb83..ae41df572 100644
--- a/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java
+++ b/lib/src/test/java/io/ably/lib/transport/DefaultsTest.java
@@ -9,7 +9,7 @@ public class DefaultsTest {
@Test
public void protocol_version_CSV2() {
- assertThat(Defaults.ABLY_PROTOCOL_VERSION, is("5"));
+ assertThat(Defaults.ABLY_PROTOCOL_VERSION, is("6"));
}
@Test
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
index 056969aa8..2a89f957d 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/DefaultRealtimeObjects.kt
@@ -126,7 +126,7 @@ internal class DefaultRealtimeObjects(internal val channelName: String, internal
operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = objectId,
- map = initialMapValue.map,
+ mapCreate = initialMapValue,
nonce = nonce,
initialValue = initialValueJSONString,
)
@@ -161,7 +161,7 @@ internal class DefaultRealtimeObjects(internal val channelName: String, internal
operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = objectId,
- counter = initialCounterValue.counter,
+ counterCreate = initialCounterValue,
nonce = nonce,
initialValue = initialValueJSONString
)
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
index 72d72eab1..6a855868c 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/Helpers.kt
@@ -161,26 +161,4 @@ internal fun ObjectsAdapter.throwIfEchoMessagesDisabled() {
}
}
-internal class Binary(val data: ByteArray) {
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is Binary) return false
- return data.contentEquals(other.data)
- }
-
- override fun hashCode(): Int {
- return data.contentHashCode()
- }
-}
-
-internal fun Binary.size(): Int {
- return data.size
-}
-
-internal data class CounterCreatePayload(
- val counter: ObjectsCounter
-)
-internal data class MapCreatePayload(
- val map: ObjectsMap
-)
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt
index 0415cc8d5..09015a87c 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/ObjectMessage.kt
@@ -1,11 +1,13 @@
package io.ably.lib.objects
+import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.annotations.JsonAdapter
import com.google.gson.annotations.SerializedName
import io.ably.lib.objects.serialization.ObjectDataJsonSerializer
import io.ably.lib.objects.serialization.gson
+import java.util.Base64
/**
* An enum class representing the different actions that can be performed on an object.
@@ -42,57 +44,87 @@ internal data class ObjectData(
*/
val objectId: String? = null,
- /**
- * String, number, boolean or binary - a concrete value of the object
- * Spec: OD2c
- */
- val value: ObjectValue? = null,
+ /** String value. Spec: OD2c */
+ val string: String? = null,
+
+ /** Numeric value. Spec: OD2c */
+ val number: Double? = null,
+
+ /** Boolean value. Spec: OD2c */
+ val boolean: Boolean? = null,
+
+ /** Binary value encoded as a base64 string. Spec: OD2c */
+ val bytes: String? = null,
+
+ /** JSON object or array value. Spec: OD2c */
+ val json: JsonElement? = null,
)
/**
- * Represents a value that can be a String, Number, Boolean, Binary, JsonObject or JsonArray.
- * Provides compile-time type safety through sealed class pattern.
- * Spec: OD2c
+ * Payload for MAP_CREATE operation.
+ * Spec: MCR*
*/
-internal sealed class ObjectValue {
- abstract val value: Any
-
- data class String(override val value: kotlin.String) : ObjectValue()
- data class Number(override val value: kotlin.Number) : ObjectValue()
- data class Boolean(override val value: kotlin.Boolean) : ObjectValue()
- data class Binary(override val value: io.ably.lib.objects.Binary) : ObjectValue()
- data class JsonObject(override val value: com.google.gson.JsonObject) : ObjectValue()
- data class JsonArray(override val value: com.google.gson.JsonArray) : ObjectValue()
-}
+internal data class MapCreate(
+ val semantics: ObjectsMapSemantics, // MCR2a
+ val entries: Map // MCR2b
+)
/**
- * A MapOp describes an operation to be applied to a Map object.
- * Spec: OMO1
+ * Payload for MAP_SET operation.
+ * Spec: MST*
*/
-internal data class ObjectsMapOp(
- /**
- * The key of the map entry to which the operation should be applied.
- * Spec: OMO2a
- */
- val key: String,
+internal data class MapSet(
+ val key: String, // MST2a
+ val value: ObjectData // MST2b - REQUIRED
+)
- /**
- * The data that the map entry should contain if the operation is a MAP_SET operation.
- * Spec: OMO2b
- */
- val data: ObjectData? = null
+/**
+ * Payload for MAP_REMOVE operation.
+ * Spec: MRM*
+ */
+internal data class MapRemove(
+ val key: String // MRM2a
)
/**
- * A CounterOp describes an operation to be applied to a Counter object.
- * Spec: OCO1
+ * Payload for COUNTER_CREATE operation.
+ * Spec: CCR*
*/
-internal data class ObjectsCounterOp(
- /**
- * The data value that should be added to the counter
- * Spec: OCO2a
- */
- val amount: Double? = null
+internal data class CounterCreate(
+ val count: Double // CCR2a - REQUIRED
+)
+
+/**
+ * Payload for COUNTER_INC operation.
+ * Spec: CIN*
+ */
+internal data class CounterInc(
+ val number: Double // CIN2a - REQUIRED
+)
+
+/**
+ * Payload for OBJECT_DELETE operation.
+ * Spec: ODE*
+ * No fields - action is sufficient
+ */
+internal object ObjectDelete
+
+/**
+ * Payload for MAP_CREATE_WITH_OBJECT_ID operation.
+ * Spec: MCRO*
+ */
+internal data class MapCreateWithObjectId(
+ val initialValue: String, // MCRO2a
+ val nonce: String // MCRO2b
+)
+
+/**
+ * Payload for COUNTER_CREATE_WITH_OBJECT_ID operation.
+ * Spec: CCRO*
+ */
+internal data class CounterCreateWithObjectId(
+ val initialValue: String, // CCRO2a
+ val nonce: String // CCRO2b
)
/**
@@ -175,32 +207,52 @@ internal data class ObjectOperation(
val objectId: String,
/**
- * The payload for the operation if it is an operation on a Map object type.
- * i.e. MAP_SET, MAP_REMOVE.
- * Spec: OOP3c
+ * Payload for MAP_CREATE operation.
+ * Spec: OOP3j
*/
- val mapOp: ObjectsMapOp? = null,
+ val mapCreate: MapCreate? = null,
/**
- * The payload for the operation if it is an operation on a Counter object type.
- * i.e. COUNTER_INC.
- * Spec: OOP3d
+ * Payload for MAP_SET operation.
+ * Spec: OOP3k
*/
- val counterOp: ObjectsCounterOp? = null,
+ val mapSet: MapSet? = null,
/**
- * The payload for the operation if the operation is MAP_CREATE.
- * Defines the initial value for the Map object.
- * Spec: OOP3e
+ * Payload for MAP_REMOVE operation.
+ * Spec: OOP3l
*/
- val map: ObjectsMap? = null,
+ val mapRemove: MapRemove? = null,
/**
- * The payload for the operation if the operation is COUNTER_CREATE.
- * Defines the initial value for the Counter object.
- * Spec: OOP3f
+ * Payload for COUNTER_CREATE operation.
+ * Spec: OOP3m
*/
- val counter: ObjectsCounter? = null,
+ val counterCreate: CounterCreate? = null,
+
+ /**
+ * Payload for COUNTER_INC operation.
+ * Spec: OOP3n
+ */
+ val counterInc: CounterInc? = null,
+
+ /**
+ * Payload for OBJECT_DELETE operation.
+ * Spec: OOP3o
+ */
+ val objectDelete: ObjectDelete? = null,
+
+ /**
+ * Payload for MAP_CREATE_WITH_OBJECT_ID operation.
+ * Spec: OOP3p
+ */
+ val mapCreateWithObjectId: MapCreateWithObjectId? = null,
+
+ /**
+ * Payload for COUNTER_CREATE_WITH_OBJECT_ID operation.
+ * Spec: OOP3q
+ */
+ val counterCreateWithObjectId: CounterCreateWithObjectId? = null,
/**
* The nonce, must be present on create operations. This is the random part
@@ -362,12 +414,17 @@ internal fun ObjectMessage.size(): Int {
* Spec: OOP4
*/
private fun ObjectOperation.size(): Int {
- val mapOpSize = mapOp?.size() ?: 0 // Spec: OOP4b, OMO3
- val counterOpSize = counterOp?.size() ?: 0 // Spec: OOP4c, OCO3
- val mapSize = map?.size() ?: 0 // Spec: OOP4d, OMP4
- val counterSize = counter?.size() ?: 0 // Spec: OOP4e, OCN3
-
- return mapOpSize + counterOpSize + mapSize + counterSize
+ val mapCreateSize = mapCreate?.size() ?: 0
+ val mapSetSize = mapSet?.size() ?: 0
+ val mapRemoveSize = mapRemove?.size() ?: 0
+ val counterCreateSize = counterCreate?.size() ?: 0
+ val counterIncSize = counterInc?.size() ?: 0
+ val mapCreateWithObjectIdSize = mapCreateWithObjectId?.size() ?: 0
+ val counterCreateWithObjectIdSize = counterCreateWithObjectId?.size() ?: 0
+
+ return mapCreateSize + mapSetSize + mapRemoveSize +
+ counterCreateSize + counterIncSize +
+ mapCreateWithObjectIdSize + counterCreateWithObjectIdSize
}
/**
@@ -383,22 +440,52 @@ private fun ObjectState.size(): Int {
}
/**
- * Calculates the size of an ObjectMapOp in bytes.
- * Spec: OMO3
+ * Calculates the size of a MapCreate payload in bytes.
*/
-private fun ObjectsMapOp.size(): Int {
- val keySize = key.length // Spec: OMO3d - Size of the key
- val dataSize = data?.size() ?: 0 // Spec: OMO3b - Size of the data, calculated per "OD3"
- return keySize + dataSize
+private fun MapCreate.size(): Int {
+ return entries.entries.sumOf { it.key.length + it.value.size() }
}
/**
- * Calculates the size of a CounterOp in bytes.
- * Spec: OCO3
+ * Calculates the size of a MapSet payload in bytes.
*/
-private fun ObjectsCounterOp.size(): Int {
- // Size is 8 if amount is a number, 0 if amount is null or omitted
- return if (amount != null) 8 else 0 // Spec: OCO3a, OCO3b
+private fun MapSet.size(): Int {
+ return key.length + value.size()
+}
+
+/**
+ * Calculates the size of a MapRemove payload in bytes.
+ */
+private fun MapRemove.size(): Int {
+ return key.length
+}
+
+/**
+ * Calculates the size of a CounterCreate payload in bytes.
+ */
+private fun CounterCreate.size(): Int {
+ return 8 // Double is 8 bytes
+}
+
+/**
+ * Calculates the size of a CounterInc payload in bytes.
+ */
+private fun CounterInc.size(): Int {
+ return 8 // Double is 8 bytes
+}
+
+/**
+ * Calculates the size of a MapCreateWithObjectId payload in bytes.
+ */
+private fun MapCreateWithObjectId.size(): Int {
+ return initialValue.length + nonce.length
+}
+
+/**
+ * Calculates the size of a CounterCreateWithObjectId payload in bytes.
+ */
+private fun CounterCreateWithObjectId.size(): Int {
+ return initialValue.length + nonce.length
}
/**
@@ -437,23 +524,19 @@ private fun ObjectsMapEntry.size(): Int {
* Spec: OD3
*/
private fun ObjectData.size(): Int {
- return value?.size() ?: 0 // Spec: OD3f
-}
-
-/**
- * Calculates the size of an ObjectValue in bytes.
- * Spec: OD3*
- */
-private fun ObjectValue.size(): Int {
- return when (this) {
- is ObjectValue.Boolean -> 1 // Spec: OD3b
- is ObjectValue.Binary -> value.size() // Spec: OD3c
- is ObjectValue.Number -> 8 // Spec: OD3d
- is ObjectValue.String -> value.byteSize // Spec: OD3e
- is ObjectValue.JsonObject, is ObjectValue.JsonArray -> value.toString().byteSize // Spec: OD3e
- }
+ string?.let { return it.byteSize } // Spec: OD3e
+ number?.let { return 8 } // Spec: OD3d
+ boolean?.let { return 1 } // Spec: OD3b
+ bytes?.let { return Base64.getDecoder().decode(it).size } // Spec: OD3c
+ json?.let { return it.toString().byteSize } // Spec: OD3e
+ return 0
}
internal fun ObjectData?.isInvalid(): Boolean {
- return this?.objectId.isNullOrEmpty() && this?.value == null
+ return this?.objectId.isNullOrEmpty() &&
+ this?.string == null &&
+ this?.number == null &&
+ this?.boolean == null &&
+ this?.bytes == null &&
+ this?.json == null
}
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt
index e610ddc6d..fbf5acb88 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/JsonSerialization.kt
@@ -1,14 +1,11 @@
package io.ably.lib.objects.serialization
import com.google.gson.*
-import io.ably.lib.objects.Binary
import io.ably.lib.objects.ObjectsMapSemantics
import io.ably.lib.objects.ObjectData
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperationAction
-import io.ably.lib.objects.ObjectValue
import java.lang.reflect.Type
-import java.util.*
import kotlin.enums.EnumEntries
// Gson instance for JSON serialization/deserialization
@@ -45,42 +42,26 @@ internal class ObjectDataJsonSerializer : JsonSerializer, JsonDeseri
override fun serialize(src: ObjectData, typeOfSrc: Type?, context: JsonSerializationContext?): JsonElement {
val obj = JsonObject()
src.objectId?.let { obj.addProperty("objectId", it) }
-
- src.value?.let { v ->
- when (v) {
- is ObjectValue.Boolean -> obj.addProperty("boolean", v.value)
- is ObjectValue.String -> obj.addProperty("string", v.value)
- is ObjectValue.Number -> obj.addProperty("number", v.value.toDouble())
- is ObjectValue.Binary -> obj.addProperty("bytes", Base64.getEncoder().encodeToString(v.value.data))
- is ObjectValue.JsonObject, is ObjectValue.JsonArray -> obj.addProperty("json", v.value.toString()) // Spec: OD4c5
- }
- }
+ src.string?.let { obj.addProperty("string", it) }
+ src.number?.let { obj.addProperty("number", it) }
+ src.boolean?.let { obj.addProperty("boolean", it) }
+ src.bytes?.let { obj.addProperty("bytes", it) }
+ src.json?.let { obj.addProperty("json", it.toString()) } // Spec: OD4c5
return obj
}
override fun deserialize(json: JsonElement, typeOfT: Type?, context: JsonDeserializationContext?): ObjectData {
val obj = if (json.isJsonObject) json.asJsonObject else throw JsonParseException("Expected JsonObject")
val objectId = if (obj.has("objectId")) obj.get("objectId").asString else null
- val value = when {
- obj.has("boolean") -> ObjectValue.Boolean(obj.get("boolean").asBoolean)
- obj.has("string") -> ObjectValue.String(obj.get("string").asString)
- obj.has("number") -> ObjectValue.Number(obj.get("number").asDouble)
- obj.has("bytes") -> ObjectValue.Binary(Binary(Base64.getDecoder().decode(obj.get("bytes").asString)))
- obj.has("json") -> {
- val jsonElement = JsonParser.parseString(obj.get("json").asString)
- when {
- jsonElement.isJsonObject -> ObjectValue.JsonObject(jsonElement.asJsonObject)
- jsonElement.isJsonArray -> ObjectValue.JsonArray(jsonElement.asJsonArray)
- else -> throw JsonParseException("Invalid JSON structure")
- }
- }
- else -> {
- if (objectId != null)
- null
- else
- throw JsonParseException("Since objectId is not present, at least one of the value fields must be present")
- }
+ val string = if (obj.has("string")) obj.get("string").asString else null
+ val number = if (obj.has("number")) obj.get("number").asDouble else null
+ val boolean = if (obj.has("boolean")) obj.get("boolean").asBoolean else null
+ val bytes = if (obj.has("bytes")) obj.get("bytes").asString else null
+ val json = if (obj.has("json")) JsonParser.parseString(obj.get("json").asString) else null
+
+ if (objectId == null && string == null && number == null && boolean == null && bytes == null && json == null) {
+ throw JsonParseException("Since objectId is not present, at least one of the value fields must be present")
}
- return ObjectData(objectId, value)
+ return ObjectData(objectId = objectId, string = string, number = number, boolean = boolean, bytes = bytes, json = json)
}
}
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt
index 797977a39..0feef39bc 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/serialization/MsgpackSerialization.kt
@@ -1,22 +1,28 @@
package io.ably.lib.objects.serialization
+import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import io.ably.lib.objects.*
-import io.ably.lib.objects.Binary
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.CounterCreateWithObjectId
+import io.ably.lib.objects.CounterInc
import io.ably.lib.objects.ErrorCode
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapCreateWithObjectId
+import io.ably.lib.objects.MapRemove
+import io.ably.lib.objects.MapSet
+import io.ably.lib.objects.ObjectDelete
import io.ably.lib.objects.ObjectsMapSemantics
import io.ably.lib.objects.ObjectsCounter
-import io.ably.lib.objects.ObjectsCounterOp
import io.ably.lib.objects.ObjectData
import io.ably.lib.objects.ObjectsMap
import io.ably.lib.objects.ObjectsMapEntry
-import io.ably.lib.objects.ObjectsMapOp
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectOperationAction
import io.ably.lib.objects.ObjectState
-import io.ably.lib.objects.ObjectValue
+import java.util.Base64
import io.ably.lib.util.Serialisation
import org.msgpack.core.MessageFormat
import org.msgpack.core.MessagePacker
@@ -160,10 +166,14 @@ private fun ObjectOperation.writeMsgpack(packer: MessagePacker) {
require(objectId.isNotEmpty()) { "objectId must be non-empty per Objects protocol" }
fieldCount++
- if (mapOp != null) fieldCount++
- if (counterOp != null) fieldCount++
- if (map != null) fieldCount++
- if (counter != null) fieldCount++
+ if (mapCreate != null) fieldCount++
+ if (mapSet != null) fieldCount++
+ if (mapRemove != null) fieldCount++
+ if (counterCreate != null) fieldCount++
+ if (counterInc != null) fieldCount++
+ if (objectDelete != null) fieldCount++
+ if (mapCreateWithObjectId != null) fieldCount++
+ if (counterCreateWithObjectId != null) fieldCount++
if (nonce != null) fieldCount++
if (initialValue != null) fieldCount++
@@ -176,24 +186,44 @@ private fun ObjectOperation.writeMsgpack(packer: MessagePacker) {
packer.packString("objectId")
packer.packString(objectId)
- if (mapOp != null) {
- packer.packString("mapOp")
- mapOp.writeMsgpack(packer)
+ if (mapCreate != null) {
+ packer.packString("mapCreate")
+ mapCreate.writeMsgpack(packer)
}
- if (counterOp != null) {
- packer.packString("counterOp")
- counterOp.writeMsgpack(packer)
+ if (mapSet != null) {
+ packer.packString("mapSet")
+ mapSet.writeMsgpack(packer)
}
- if (map != null) {
- packer.packString("map")
- map.writeMsgpack(packer)
+ if (mapRemove != null) {
+ packer.packString("mapRemove")
+ mapRemove.writeMsgpack(packer)
}
- if (counter != null) {
- packer.packString("counter")
- counter.writeMsgpack(packer)
+ if (counterCreate != null) {
+ packer.packString("counterCreate")
+ counterCreate.writeMsgpack(packer)
+ }
+
+ if (counterInc != null) {
+ packer.packString("counterInc")
+ counterInc.writeMsgpack(packer)
+ }
+
+ if (objectDelete != null) {
+ packer.packString("objectDelete")
+ packer.packMapHeader(0) // empty map
+ }
+
+ if (mapCreateWithObjectId != null) {
+ packer.packString("mapCreateWithObjectId")
+ mapCreateWithObjectId.writeMsgpack(packer)
+ }
+
+ if (counterCreateWithObjectId != null) {
+ packer.packString("counterCreateWithObjectId")
+ counterCreateWithObjectId.writeMsgpack(packer)
}
if (nonce != null) {
@@ -215,10 +245,14 @@ private fun readObjectOperation(unpacker: MessageUnpacker): ObjectOperation {
var action: ObjectOperationAction? = null
var objectId: String = ""
- var mapOp: ObjectsMapOp? = null
- var counterOp: ObjectsCounterOp? = null
- var map: ObjectsMap? = null
- var counter: ObjectsCounter? = null
+ var mapCreate: MapCreate? = null
+ var mapSet: MapSet? = null
+ var mapRemove: MapRemove? = null
+ var counterCreate: CounterCreate? = null
+ var counterInc: CounterInc? = null
+ var objectDelete: ObjectDelete? = null
+ var mapCreateWithObjectId: MapCreateWithObjectId? = null
+ var counterCreateWithObjectId: CounterCreateWithObjectId? = null
var nonce: String? = null
var initialValue: String? = null
@@ -239,10 +273,17 @@ private fun readObjectOperation(unpacker: MessageUnpacker): ObjectOperation {
?: throw objectError("Unknown ObjectOperationAction code: $actionCode and no Unknown fallback found")
}
"objectId" -> objectId = unpacker.unpackString()
- "mapOp" -> mapOp = readObjectMapOp(unpacker)
- "counterOp" -> counterOp = readObjectCounterOp(unpacker)
- "map" -> map = readObjectMap(unpacker)
- "counter" -> counter = readObjectCounter(unpacker)
+ "mapCreate" -> mapCreate = readMapCreate(unpacker)
+ "mapSet" -> mapSet = readMapSet(unpacker)
+ "mapRemove" -> mapRemove = readMapRemove(unpacker)
+ "counterCreate" -> counterCreate = readCounterCreate(unpacker)
+ "counterInc" -> counterInc = readCounterInc(unpacker)
+ "objectDelete" -> {
+ unpacker.skipValue() // empty map, just consume it
+ objectDelete = ObjectDelete
+ }
+ "mapCreateWithObjectId" -> mapCreateWithObjectId = readMapCreateWithObjectId(unpacker)
+ "counterCreateWithObjectId" -> counterCreateWithObjectId = readCounterCreateWithObjectId(unpacker)
"nonce" -> nonce = unpacker.unpackString()
"initialValue" -> initialValue = unpacker.unpackString()
else -> unpacker.skipValue()
@@ -256,10 +297,14 @@ private fun readObjectOperation(unpacker: MessageUnpacker): ObjectOperation {
return ObjectOperation(
action = action,
objectId = objectId,
- mapOp = mapOp,
- counterOp = counterOp,
- map = map,
- counter = counter,
+ mapCreate = mapCreate,
+ mapSet = mapSet,
+ mapRemove = mapRemove,
+ counterCreate = counterCreate,
+ counterInc = counterInc,
+ objectDelete = objectDelete,
+ mapCreateWithObjectId = mapCreateWithObjectId,
+ counterCreateWithObjectId = counterCreateWithObjectId,
nonce = nonce,
initialValue = initialValue,
)
@@ -359,92 +404,240 @@ private fun readObjectState(unpacker: MessageUnpacker): ObjectState {
}
/**
- * Write ObjectMapOp to MessagePacker
+ * Write MapCreate to MessagePacker
*/
-private fun ObjectsMapOp.writeMsgpack(packer: MessagePacker) {
- var fieldCount = 1 // key is required
+private fun MapCreate.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(2)
+ packer.packString("semantics")
+ packer.packInt(semantics.code)
+ packer.packString("entries")
+ packer.packMapHeader(entries.size)
+ for ((key, value) in entries) {
+ packer.packString(key)
+ value.writeMsgpack(packer)
+ }
+}
- if (data != null) fieldCount++
+/**
+ * Read MapCreate from MessageUnpacker
+ */
+private fun readMapCreate(unpacker: MessageUnpacker): MapCreate {
+ val fieldCount = unpacker.unpackMapHeader()
+ var semantics: ObjectsMapSemantics = ObjectsMapSemantics.LWW
+ var entries: Map = emptyMap()
- packer.packMapHeader(fieldCount)
+ for (i in 0 until fieldCount) {
+ val fieldName = unpacker.unpackString().intern()
+ val fieldFormat = unpacker.nextFormat
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
+ when (fieldName) {
+ "semantics" -> {
+ val code = unpacker.unpackInt()
+ semantics = ObjectsMapSemantics.entries.firstOrNull { it.code == code }
+ ?: ObjectsMapSemantics.entries.firstOrNull { it.code == -1 }
+ ?: throw objectError("Unknown MapSemantics code: $code and no UNKNOWN fallback found")
+ }
+ "entries" -> {
+ val mapSize = unpacker.unpackMapHeader()
+ val tempMap = mutableMapOf()
+ for (j in 0 until mapSize) {
+ tempMap[unpacker.unpackString()] = readObjectMapEntry(unpacker)
+ }
+ entries = tempMap
+ }
+ else -> unpacker.skipValue()
+ }
+ }
+ return MapCreate(semantics = semantics, entries = entries)
+}
+/**
+ * Write MapSet to MessagePacker
+ */
+private fun MapSet.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(2)
packer.packString("key")
packer.packString(key)
-
- if (data != null) {
- packer.packString("data")
- data.writeMsgpack(packer)
- }
+ packer.packString("value")
+ value.writeMsgpack(packer)
}
/**
- * Read ObjectMapOp from MessageUnpacker
+ * Read MapSet from MessageUnpacker
*/
-private fun readObjectMapOp(unpacker: MessageUnpacker): ObjectsMapOp {
+private fun readMapSet(unpacker: MessageUnpacker): MapSet {
val fieldCount = unpacker.unpackMapHeader()
-
- var key = ""
- var data: ObjectData? = null
+ var key: String? = null
+ var value: ObjectData? = null
for (i in 0 until fieldCount) {
val fieldName = unpacker.unpackString().intern()
val fieldFormat = unpacker.nextFormat
-
- if (fieldFormat == MessageFormat.NIL) {
- unpacker.unpackNil()
- continue
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
+ when (fieldName) {
+ "key" -> key = unpacker.unpackString()
+ "value" -> value = readObjectData(unpacker)
+ else -> unpacker.skipValue()
}
+ }
+ return MapSet(
+ key = key ?: throw objectError("Missing 'key' in MapSet payload"),
+ value = value ?: throw objectError("Missing 'value' in MapSet payload")
+ )
+}
+
+/**
+ * Write MapRemove to MessagePacker
+ */
+private fun MapRemove.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(1)
+ packer.packString("key")
+ packer.packString(key)
+}
+
+/**
+ * Read MapRemove from MessageUnpacker
+ */
+private fun readMapRemove(unpacker: MessageUnpacker): MapRemove {
+ val fieldCount = unpacker.unpackMapHeader()
+ var key: String? = null
+ for (i in 0 until fieldCount) {
+ val fieldName = unpacker.unpackString().intern()
+ val fieldFormat = unpacker.nextFormat
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
when (fieldName) {
"key" -> key = unpacker.unpackString()
- "data" -> data = readObjectData(unpacker)
else -> unpacker.skipValue()
}
}
+ return MapRemove(key = key ?: throw objectError("Missing 'key' in MapRemove payload"))
+}
- return ObjectsMapOp(key = key, data = data)
+/**
+ * Write CounterCreate to MessagePacker
+ */
+private fun CounterCreate.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(1)
+ packer.packString("count")
+ packer.packDouble(count)
}
/**
- * Write ObjectCounterOp to MessagePacker
+ * Read CounterCreate from MessageUnpacker
*/
-private fun ObjectsCounterOp.writeMsgpack(packer: MessagePacker) {
- var fieldCount = 0
+private fun readCounterCreate(unpacker: MessageUnpacker): CounterCreate {
+ val fieldCount = unpacker.unpackMapHeader()
+ var count: Double? = null
- if (amount != null) fieldCount++
+ for (i in 0 until fieldCount) {
+ val fieldName = unpacker.unpackString().intern()
+ val fieldFormat = unpacker.nextFormat
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
+ when (fieldName) {
+ "count" -> count = unpacker.unpackDouble()
+ else -> unpacker.skipValue()
+ }
+ }
+ return CounterCreate(count = count ?: throw objectError("Missing 'count' in CounterCreate payload"))
+}
- packer.packMapHeader(fieldCount)
+/**
+ * Write CounterInc to MessagePacker
+ */
+private fun CounterInc.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(1)
+ packer.packString("number")
+ packer.packDouble(number)
+}
- if (amount != null) {
- packer.packString("amount")
- packer.packDouble(amount)
+/**
+ * Read CounterInc from MessageUnpacker
+ */
+private fun readCounterInc(unpacker: MessageUnpacker): CounterInc {
+ val fieldCount = unpacker.unpackMapHeader()
+ var number: Double? = null
+
+ for (i in 0 until fieldCount) {
+ val fieldName = unpacker.unpackString().intern()
+ val fieldFormat = unpacker.nextFormat
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
+ when (fieldName) {
+ "number" -> number = unpacker.unpackDouble()
+ else -> unpacker.skipValue()
+ }
}
+ return CounterInc(number = number ?: throw objectError("Missing 'number' in CounterInc payload"))
}
/**
- * Read ObjectCounterOp from MessageUnpacker
+ * Write MapCreateWithObjectId to MessagePacker
*/
-private fun readObjectCounterOp(unpacker: MessageUnpacker): ObjectsCounterOp {
- val fieldCount = unpacker.unpackMapHeader()
+private fun MapCreateWithObjectId.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(2)
+ packer.packString("initialValue")
+ packer.packString(initialValue)
+ packer.packString("nonce")
+ packer.packString(nonce)
+}
- var amount: Double? = null
+/**
+ * Read MapCreateWithObjectId from MessageUnpacker
+ */
+private fun readMapCreateWithObjectId(unpacker: MessageUnpacker): MapCreateWithObjectId {
+ val fieldCount = unpacker.unpackMapHeader()
+ var initialValue: String? = null
+ var nonce: String? = null
for (i in 0 until fieldCount) {
val fieldName = unpacker.unpackString().intern()
val fieldFormat = unpacker.nextFormat
-
- if (fieldFormat == MessageFormat.NIL) {
- unpacker.unpackNil()
- continue
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
+ when (fieldName) {
+ "initialValue" -> initialValue = unpacker.unpackString()
+ "nonce" -> nonce = unpacker.unpackString()
+ else -> unpacker.skipValue()
}
+ }
+ return MapCreateWithObjectId(
+ initialValue = initialValue ?: throw objectError("Missing 'initialValue' in MapCreateWithObjectId payload"),
+ nonce = nonce ?: throw objectError("Missing 'nonce' in MapCreateWithObjectId payload")
+ )
+}
+
+/**
+ * Write CounterCreateWithObjectId to MessagePacker
+ */
+private fun CounterCreateWithObjectId.writeMsgpack(packer: MessagePacker) {
+ packer.packMapHeader(2)
+ packer.packString("initialValue")
+ packer.packString(initialValue)
+ packer.packString("nonce")
+ packer.packString(nonce)
+}
+
+/**
+ * Read CounterCreateWithObjectId from MessageUnpacker
+ */
+private fun readCounterCreateWithObjectId(unpacker: MessageUnpacker): CounterCreateWithObjectId {
+ val fieldCount = unpacker.unpackMapHeader()
+ var initialValue: String? = null
+ var nonce: String? = null
+ for (i in 0 until fieldCount) {
+ val fieldName = unpacker.unpackString().intern()
+ val fieldFormat = unpacker.nextFormat
+ if (fieldFormat == MessageFormat.NIL) { unpacker.unpackNil(); continue }
when (fieldName) {
- "amount" -> amount = unpacker.unpackDouble()
+ "initialValue" -> initialValue = unpacker.unpackString()
+ "nonce" -> nonce = unpacker.unpackString()
else -> unpacker.skipValue()
}
}
-
- return ObjectsCounterOp(amount = amount)
+ return CounterCreateWithObjectId(
+ initialValue = initialValue ?: throw objectError("Missing 'initialValue' in CounterCreateWithObjectId payload"),
+ nonce = nonce ?: throw objectError("Missing 'nonce' in CounterCreateWithObjectId payload")
+ )
}
/**
@@ -630,9 +823,11 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
var fieldCount = 0
if (objectId != null) fieldCount++
- value?.let {
- fieldCount++
- }
+ if (string != null) fieldCount++
+ if (number != null) fieldCount++
+ if (boolean != null) fieldCount++
+ if (bytes != null) fieldCount++
+ if (json != null) fieldCount++
packer.packMapHeader(fieldCount)
@@ -641,34 +836,31 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
packer.packString(objectId)
}
- value?.let { v ->
- when (v) {
- is ObjectValue.Boolean -> {
- packer.packString("boolean")
- packer.packBoolean(v.value)
- }
- is ObjectValue.String -> {
- packer.packString("string")
- packer.packString(v.value)
- }
- is ObjectValue.Number -> {
- packer.packString("number")
- packer.packDouble(v.value.toDouble())
- }
- is ObjectValue.Binary -> {
- packer.packString("bytes")
- packer.packBinaryHeader(v.value.data.size)
- packer.writePayload(v.value.data)
- }
- is ObjectValue.JsonObject -> {
- packer.packString("json")
- packer.packString(v.value.toString())
- }
- is ObjectValue.JsonArray -> {
- packer.packString("json")
- packer.packString(v.value.toString())
- }
- }
+ if (string != null) {
+ packer.packString("string")
+ packer.packString(string)
+ }
+
+ if (number != null) {
+ packer.packString("number")
+ packer.packDouble(number)
+ }
+
+ if (boolean != null) {
+ packer.packString("boolean")
+ packer.packBoolean(boolean)
+ }
+
+ if (bytes != null) {
+ val rawBytes = Base64.getDecoder().decode(bytes)
+ packer.packString("bytes")
+ packer.packBinaryHeader(rawBytes.size)
+ packer.writePayload(rawBytes)
+ }
+
+ if (json != null) {
+ packer.packString("json")
+ packer.packString(json.toString())
}
}
@@ -678,7 +870,11 @@ private fun ObjectData.writeMsgpack(packer: MessagePacker) {
private fun readObjectData(unpacker: MessageUnpacker): ObjectData {
val fieldCount = unpacker.unpackMapHeader()
var objectId: String? = null
- var value: ObjectValue? = null
+ var string: String? = null
+ var number: Double? = null
+ var boolean: Boolean? = null
+ var bytes: String? = null
+ var json: JsonElement? = null
for (i in 0 until fieldCount) {
val fieldName = unpacker.unpackString().intern()
@@ -691,28 +887,19 @@ private fun readObjectData(unpacker: MessageUnpacker): ObjectData {
when (fieldName) {
"objectId" -> objectId = unpacker.unpackString()
- "boolean" -> value = ObjectValue.Boolean(unpacker.unpackBoolean())
- "string" -> value = ObjectValue.String(unpacker.unpackString())
- "number" -> value = ObjectValue.Number(unpacker.unpackDouble())
+ "string" -> string = unpacker.unpackString()
+ "number" -> number = unpacker.unpackDouble()
+ "boolean" -> boolean = unpacker.unpackBoolean()
"bytes" -> {
val size = unpacker.unpackBinaryHeader()
- val bytes = ByteArray(size)
- unpacker.readPayload(bytes)
- value = ObjectValue.Binary(Binary(bytes))
- }
- "json" -> {
- val jsonString = unpacker.unpackString()
- val parsed = JsonParser.parseString(jsonString)
- value = when {
- parsed.isJsonObject -> ObjectValue.JsonObject(parsed.asJsonObject)
- parsed.isJsonArray -> ObjectValue.JsonArray(parsed.asJsonArray)
- else ->
- throw ablyException("Invalid JSON string for json field", ErrorCode.MapValueDataTypeUnsupported, HttpStatusCode.InternalServerError)
- }
+ val rawBytes = ByteArray(size)
+ unpacker.readPayload(rawBytes)
+ bytes = Base64.getEncoder().encodeToString(rawBytes)
}
+ "json" -> json = JsonParser.parseString(unpacker.unpackString())
else -> unpacker.skipValue()
}
}
- return ObjectData(objectId = objectId, value = value)
+ return ObjectData(objectId = objectId, string = string, number = number, boolean = boolean, bytes = bytes, json = json)
}
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
index 164cdb28a..87c1de3b5 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt
@@ -1,6 +1,8 @@
package io.ably.lib.objects.type.livecounter
import io.ably.lib.objects.*
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.CounterInc
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectState
import io.ably.lib.objects.type.BaseRealtimeObject
@@ -81,7 +83,7 @@ internal class DefaultLiveCounter private constructor(
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = objectId,
- counterOp = ObjectsCounterOp(amount = amount)
+ counterInc = CounterInc(number = amount)
)
)
@@ -124,13 +126,11 @@ internal class DefaultLiveCounter private constructor(
}
/**
- * Creates initial value operation for counter creation.
+ * Creates initial value payload for counter creation.
* Spec: RTO12f2
*/
- internal fun initialValue(count: Number): CounterCreatePayload {
- return CounterCreatePayload(
- counter = ObjectsCounter(count = count.toDouble())
- )
+ internal fun initialValue(count: Number): CounterCreate {
+ return CounterCreate(count = count.toDouble())
}
}
}
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt
index 943faf4ce..d4e8b349a 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livecounter/LiveCounterManager.kt
@@ -1,6 +1,7 @@
package io.ably.lib.objects.type.livecounter
import io.ably.lib.objects.*
+import io.ably.lib.objects.CounterInc
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectOperationAction
import io.ably.lib.objects.ObjectState
@@ -47,8 +48,8 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter):
true // RTLC7d1b
}
ObjectOperationAction.CounterInc -> {
- if (operation.counterOp != null) {
- val update = applyCounterInc(operation.counterOp) // RTLC7d2
+ if (operation.counterInc != null) {
+ val update = applyCounterInc(operation.counterInc) // RTLC7d2
liveCounter.notifyUpdated(update) // RTLC7d2a
true // RTLC7d2b
} else {
@@ -89,8 +90,8 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter):
/**
* @spec RTLC9 - Applies counter increment operation
*/
- private fun applyCounterInc(counterOp: ObjectsCounterOp): LiveCounterUpdate {
- val amount = counterOp.amount ?: 0.0
+ private fun applyCounterInc(counterInc: CounterInc): LiveCounterUpdate {
+ val amount = counterInc.number
val previousValue = liveCounter.data.get()
liveCounter.data.set(previousValue + amount) // RTLC9b
return LiveCounterUpdate(amount)
@@ -108,7 +109,7 @@ internal class LiveCounterManager(private val liveCounter: DefaultLiveCounter):
// note that it is intentional to SUM the incoming count from the create op.
// if we got here, it means that current counter instance is missing the initial value in its data reference,
// which we're going to add now.
- val count = operation.counter?.count ?: 0.0
+ val count = operation.counterCreate?.count ?: 0.0
val previousValue = liveCounter.data.get()
liveCounter.data.set(previousValue + count) // RTLC10a
liveCounter.createOperationIsMerged = true // RTLC10b
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
index cd0604dbf..e2da9461b 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/DefaultLiveMap.kt
@@ -1,6 +1,9 @@
package io.ably.lib.objects.type.livemap
import io.ably.lib.objects.*
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapRemove
+import io.ably.lib.objects.MapSet
import io.ably.lib.objects.ObjectsMapSemantics
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperation
@@ -15,6 +18,7 @@ import io.ably.lib.objects.type.map.LiveMapValue
import io.ably.lib.objects.type.noOp
import io.ably.lib.util.Log
import kotlinx.coroutines.runBlocking
+import java.util.Base64
import java.util.concurrent.ConcurrentHashMap
import java.util.AbstractMap
@@ -128,9 +132,9 @@ internal class DefaultLiveMap private constructor(
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = objectId,
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = keyName,
- data = fromLiveMapValue(value)
+ value = fromLiveMapValue(value)
)
)
)
@@ -153,7 +157,7 @@ internal class DefaultLiveMap private constructor(
operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = objectId,
- mapOp = ObjectsMapOp(key = keyName)
+ mapRemove = MapRemove(key = keyName)
)
)
@@ -196,20 +200,18 @@ internal class DefaultLiveMap private constructor(
}
/**
- * Creates an ObjectMap from map entries.
+ * Creates a MapCreate payload from map entries.
* Spec: RTO11f4
*/
- internal fun initialValue(entries: MutableMap): MapCreatePayload {
- return MapCreatePayload(
- map = ObjectsMap(
- semantics = ObjectsMapSemantics.LWW,
- entries = entries.mapValues { (_, value) ->
- ObjectsMapEntry(
- tombstone = false,
- data = fromLiveMapValue(value)
- )
- }
- )
+ internal fun initialValue(entries: MutableMap): MapCreate {
+ return MapCreate(
+ semantics = ObjectsMapSemantics.LWW,
+ entries = entries.mapValues { (_, value) ->
+ ObjectsMapEntry(
+ tombstone = false,
+ data = fromLiveMapValue(value)
+ )
+ }
)
}
@@ -218,30 +220,22 @@ internal class DefaultLiveMap private constructor(
*/
private fun fromLiveMapValue(value: LiveMapValue): ObjectData {
return when {
- value.isLiveMap || value.isLiveCounter -> {
+ value.isLiveMap || value.isLiveCounter ->
ObjectData(objectId = (value.value as BaseRealtimeObject).objectId)
- }
- value.isBoolean -> {
- ObjectData(value = ObjectValue.Boolean(value.asBoolean))
- }
- value.isBinary -> {
- ObjectData(value = ObjectValue.Binary(Binary(value.asBinary)))
- }
- value.isNumber -> {
- ObjectData(value = ObjectValue.Number(value.asNumber))
- }
- value.isString -> {
- ObjectData(value = ObjectValue.String(value.asString))
- }
- value.isJsonObject -> {
- ObjectData(value = ObjectValue.JsonObject(value.asJsonObject))
- }
- value.isJsonArray -> {
- ObjectData(value = ObjectValue.JsonArray(value.asJsonArray))
- }
- else -> {
+ value.isBoolean ->
+ ObjectData(boolean = value.asBoolean)
+ value.isBinary ->
+ ObjectData(bytes = Base64.getEncoder().encodeToString(value.asBinary))
+ value.isNumber ->
+ ObjectData(number = value.asNumber.toDouble())
+ value.isString ->
+ ObjectData(string = value.asString)
+ value.isJsonObject ->
+ ObjectData(json = value.asJsonObject)
+ value.isJsonArray ->
+ ObjectData(json = value.asJsonArray)
+ else ->
throw IllegalArgumentException("Unsupported value type")
- }
}
}
}
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt
index 4c32366e1..df2259583 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapEntry.kt
@@ -9,6 +9,7 @@ import io.ably.lib.objects.type.ObjectType
import io.ably.lib.objects.type.counter.LiveCounter
import io.ably.lib.objects.type.map.LiveMap
import io.ably.lib.objects.type.map.LiveMapValue
+import java.util.Base64
/**
* @spec RTLM3 - Map data structure storing entries
@@ -45,14 +46,25 @@ internal fun LiveMapEntry.isEntryOrRefTombstoned(objectsPool: ObjectsPool): Bool
internal fun LiveMapEntry.getResolvedValue(objectsPool: ObjectsPool): LiveMapValue? {
if (isTombstoned) { return null } // RTLM5d2a
- data?.value?.let { return fromObjectValue(it) } // RTLM5d2b, RTLM5d2c, RTLM5d2d, RTLM5d2e
-
- data?.objectId?.let { refId -> // RTLM5d2f -has an objectId reference
- objectsPool.get(refId)?.let { refObject ->
- if (refObject.isTombstoned) {
- return null // tombstoned objects must not be surfaced to the end users
+ data?.let { d -> // RTLM5d2b, RTLM5d2c, RTLM5d2d, RTLM5d2e
+ d.string?.let { return LiveMapValue.of(it) }
+ d.number?.let { return LiveMapValue.of(it) }
+ d.boolean?.let { return LiveMapValue.of(it) }
+ d.bytes?.let { return LiveMapValue.of(Base64.getDecoder().decode(it)) }
+ d.json?.let { parsed ->
+ return when {
+ parsed.isJsonObject -> LiveMapValue.of(parsed.asJsonObject)
+ parsed.isJsonArray -> LiveMapValue.of(parsed.asJsonArray)
+ else -> null
+ }
+ }
+ d.objectId?.let { refId -> // RTLM5d2f - has an objectId reference
+ objectsPool.get(refId)?.let { refObject ->
+ if (refObject.isTombstoned) {
+ return null // tombstoned objects must not be surfaced to the end users
+ }
+ return fromRealtimeObject(refObject) // RTLM5d2f2
}
- return fromRealtimeObject(refObject) // RTLM5d2f2
}
}
return null // RTLM5d2g, RTLM5d2f1
@@ -66,17 +78,6 @@ internal fun LiveMapEntry.isEligibleForGc(): Boolean {
return isTombstoned && tombstonedAt?.let { currentTime - it >= ObjectsPoolDefaults.GC_GRACE_PERIOD_MS } == true
}
-private fun fromObjectValue(objValue: ObjectValue): LiveMapValue {
- return when (objValue) {
- is ObjectValue.String -> LiveMapValue.of(objValue.value)
- is ObjectValue.Number -> LiveMapValue.of(objValue.value)
- is ObjectValue.Boolean -> LiveMapValue.of(objValue.value)
- is ObjectValue.Binary -> LiveMapValue.of(objValue.value.data)
- is ObjectValue.JsonObject -> LiveMapValue.of(objValue.value)
- is ObjectValue.JsonArray -> LiveMapValue.of(objValue.value)
- }
-}
-
private fun fromRealtimeObject(realtimeObject: BaseRealtimeObject): LiveMapValue {
return when (realtimeObject.objectType) {
ObjectType.Map -> LiveMapValue.of(realtimeObject as LiveMap)
diff --git a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt
index 90c920cf2..0a5dd11fe 100644
--- a/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt
+++ b/liveobjects/src/main/kotlin/io/ably/lib/objects/type/livemap/LiveMapManager.kt
@@ -1,7 +1,9 @@
package io.ably.lib.objects.type.livemap
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapRemove
+import io.ably.lib.objects.MapSet
import io.ably.lib.objects.ObjectsMapSemantics
-import io.ably.lib.objects.ObjectsMapOp
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectOperationAction
import io.ably.lib.objects.ObjectState
@@ -59,8 +61,8 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
true // RTLM15d1b
}
ObjectOperationAction.MapSet -> {
- if (operation.mapOp != null) {
- val update = applyMapSet(operation.mapOp, serial) // RTLM15d2
+ if (operation.mapSet != null) {
+ val update = applyMapSet(operation.mapSet, serial) // RTLM15d2
liveMap.notifyUpdated(update) // RTLM15d2a
true // RTLM15d2b
} else {
@@ -68,8 +70,8 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
}
}
ObjectOperationAction.MapRemove -> {
- if (operation.mapOp != null) {
- val update = applyMapRemove(operation.mapOp, serial, serialTimestamp) // RTLM15d3
+ if (operation.mapRemove != null) {
+ val update = applyMapRemove(operation.mapRemove, serial, serialTimestamp) // RTLM15d3
liveMap.notifyUpdated(update) // RTLM15d3a
true // RTLM15d3b
} else {
@@ -104,7 +106,7 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
return noOpMapUpdate
}
- validateMapSemantics(operation.map?.semantics) // RTLM16c
+ validateMapSemantics(operation.mapCreate?.semantics) // RTLM16c
return mergeInitialDataFromCreateOperation(operation) // RTLM16d
}
@@ -113,27 +115,27 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
* @spec RTLM7 - Applies MAP_SET operation to LiveMap
*/
private fun applyMapSet(
- mapOp: ObjectsMapOp, // RTLM7d1
+ mapSet: MapSet, // RTLM7d1
timeSerial: String?, // RTLM7d2
): LiveMapUpdate {
- val existingEntry = liveMap.data[mapOp.key]
+ val existingEntry = liveMap.data[mapSet.key]
// RTLM7a
if (existingEntry != null && !canApplyMapOperation(existingEntry.timeserial, timeSerial)) {
// RTLM7a1 - the operation's serial <= the entry's serial, ignore the operation
Log.v(tag,
- "Skipping update for key=\"${mapOp.key}\": op serial $timeSerial <= entry serial ${existingEntry.timeserial};" +
+ "Skipping update for key=\"${mapSet.key}\": op serial $timeSerial <= entry serial ${existingEntry.timeserial};" +
" objectId=${objectId}"
)
return noOpMapUpdate
}
- if (mapOp.data.isInvalid()) {
- throw objectError("Invalid object data for MAP_SET op on objectId=${objectId} on key=${mapOp.key}")
+ if (mapSet.value.isInvalid()) {
+ throw objectError("Invalid object data for MAP_SET op on objectId=${objectId} on key=${mapSet.key}")
}
// RTLM7c
- mapOp.data?.objectId?.let {
+ mapSet.value.objectId?.let {
// this MAP_SET op is setting a key to point to another object via its object id,
// but it is possible that we don't have the corresponding object in the pool yet (for example, we haven't seen the *_CREATE op for it).
// we don't want to return undefined from this map's .get() method even if we don't have the object,
@@ -143,39 +145,39 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
if (existingEntry != null) {
// RTLM7a2 - Replace existing entry with new one instead of mutating
- liveMap.data[mapOp.key] = LiveMapEntry(
+ liveMap.data[mapSet.key] = LiveMapEntry(
isTombstoned = false, // RTLM7a2c
timeserial = timeSerial, // RTLM7a2b
- data = mapOp.data // RTLM7a2a
+ data = mapSet.value // RTLM7a2a
)
} else {
// RTLM7b, RTLM7b1
- liveMap.data[mapOp.key] = LiveMapEntry(
+ liveMap.data[mapSet.key] = LiveMapEntry(
isTombstoned = false, // RTLM7b2
timeserial = timeSerial,
- data = mapOp.data
+ data = mapSet.value
)
}
- return LiveMapUpdate(mapOf(mapOp.key to LiveMapUpdate.Change.UPDATED))
+ return LiveMapUpdate(mapOf(mapSet.key to LiveMapUpdate.Change.UPDATED))
}
/**
* @spec RTLM8 - Applies MAP_REMOVE operation to LiveMap
*/
private fun applyMapRemove(
- mapOp: ObjectsMapOp, // RTLM8c1
+ mapRemove: MapRemove, // RTLM8c1
timeSerial: String?, // RTLM8c2
timeStamp: Long?, // RTLM8c3
): LiveMapUpdate {
- val existingEntry = liveMap.data[mapOp.key]
+ val existingEntry = liveMap.data[mapRemove.key]
// RTLM8a
if (existingEntry != null && !canApplyMapOperation(existingEntry.timeserial, timeSerial)) {
// RTLM8a1 - the operation's serial <= the entry's serial, ignore the operation
Log.v(
tag,
- "Skipping remove for key=\"${mapOp.key}\": op serial $timeSerial <= entry serial ${existingEntry.timeserial}; " +
+ "Skipping remove for key=\"${mapRemove.key}\": op serial $timeSerial <= entry serial ${existingEntry.timeserial}; " +
"objectId=${objectId}"
)
return noOpMapUpdate
@@ -184,7 +186,7 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
val tombstonedAt = if (timeStamp != null) timeStamp else {
Log.w(
tag,
- "No timestamp provided for MAP_REMOVE op on key=\"${mapOp.key}\"; using current time as tombstone time; " +
+ "No timestamp provided for MAP_REMOVE op on key=\"${mapRemove.key}\"; using current time as tombstone time; " +
"objectId=${objectId}"
)
System.currentTimeMillis()
@@ -192,7 +194,7 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
if (existingEntry != null) {
// RTLM8a2 - Replace existing entry with new one instead of mutating
- liveMap.data[mapOp.key] = LiveMapEntry(
+ liveMap.data[mapRemove.key] = LiveMapEntry(
isTombstoned = true, // RTLM8a2c
tombstonedAt = tombstonedAt,
timeserial = timeSerial, // RTLM8a2b
@@ -200,14 +202,14 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
)
} else {
// RTLM8b, RTLM8b1
- liveMap.data[mapOp.key] = LiveMapEntry(
+ liveMap.data[mapRemove.key] = LiveMapEntry(
isTombstoned = true, // RTLM8b2
tombstonedAt = tombstonedAt,
timeserial = timeSerial
)
}
- return LiveMapUpdate(mapOf(mapOp.key to LiveMapUpdate.Change.REMOVED))
+ return LiveMapUpdate(mapOf(mapRemove.key to LiveMapUpdate.Change.REMOVED))
}
/**
@@ -232,7 +234,7 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
* @spec RTLM17 - Merges initial data from create operation
*/
private fun mergeInitialDataFromCreateOperation(operation: ObjectOperation): LiveMapUpdate {
- if (operation.map?.entries.isNullOrEmpty()) { // no map entries in MAP_CREATE op
+ if (operation.mapCreate?.entries.isNullOrEmpty()) { // no map entries in MAP_CREATE op
return noOpMapUpdate
}
@@ -241,15 +243,15 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
// RTLM17a
// in order to apply MAP_CREATE op for an existing map, we should merge their underlying entries keys.
// we can do this by iterating over entries from MAP_CREATE op and apply changes on per-key basis as if we had MAP_SET, MAP_REMOVE operations.
- operation.map?.entries?.forEach { (key, entry) ->
+ operation.mapCreate?.entries?.forEach { (key, entry) ->
// for a MAP_CREATE operation we must use the serial value available on an entry, instead of a serial on a message
val opTimeserial = entry.timeserial
val update = if (entry.tombstone == true) {
// RTLM17a2 - entry in MAP_CREATE op is removed, try to apply MAP_REMOVE op
- applyMapRemove(ObjectsMapOp(key), opTimeserial, entry.serialTimestamp)
+ applyMapRemove(MapRemove(key), opTimeserial, entry.serialTimestamp)
} else {
// RTLM17a1 - entry in MAP_CREATE op is not removed, try to set it via MAP_SET op
- applyMapSet(ObjectsMapOp(key, entry.data), opTimeserial)
+ applyMapSet(MapSet(key, entry.data ?: return@forEach), opTimeserial)
}
// skip noop updates
@@ -327,7 +329,7 @@ internal class LiveMapManager(private val liveMap: DefaultLiveMap): LiveMapChang
state.createOp?.let { createOp ->
liveMap.validateObjectId(createOp.objectId)
validateMapCreateAction(createOp.action)
- validateMapSemantics(createOp.map?.semantics)
+ validateMapSemantics(createOp.mapCreate?.semantics)
}
}
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt
index e3043abc1..0f2abb567 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/DefaultLiveMapTest.kt
@@ -2,7 +2,6 @@ package io.ably.lib.objects.integration
import io.ably.lib.objects.*
import io.ably.lib.objects.ObjectData
-import io.ably.lib.objects.ObjectValue
import io.ably.lib.objects.integration.helpers.fixtures.createUserMapObject
import io.ably.lib.objects.integration.helpers.fixtures.createUserProfileMapObject
import io.ably.lib.objects.integration.setup.IntegrationTest
@@ -112,9 +111,9 @@ class DefaultLiveMapTest: IntegrationTest() {
val testMapObjectId = restObjects.createMap(
channelName,
data = mapOf(
- "name" to ObjectData(value = ObjectValue.String("Alice")),
- "age" to ObjectData(value = ObjectValue.Number(30)),
- "isActive" to ObjectData(value = ObjectValue.Boolean(true))
+ "name" to ObjectData(string = "Alice"),
+ "age" to ObjectData(number = 30.0),
+ "isActive" to ObjectData(boolean = true)
)
)
restObjects.setMapRef(channelName, "root", "testMap", testMapObjectId)
@@ -131,7 +130,7 @@ class DefaultLiveMapTest: IntegrationTest() {
assertEquals(true, testMap.get("isActive")?.asBoolean, "Initial active status should be true")
// Step 2: Update an existing field (name from "Alice" to "Bob")
- restObjects.setMapValue(channelName, testMapObjectId, "name", ObjectValue.String("Bob"))
+ restObjects.setMapValue(channelName, testMapObjectId, "name", ObjectData(string = "Bob"))
// Wait for the map to be updated
assertWaiter { testMap.get("name")?.asString == "Bob" }
@@ -142,7 +141,7 @@ class DefaultLiveMapTest: IntegrationTest() {
assertEquals(true, testMap.get("isActive")?.asBoolean, "Active status should remain unchanged")
// Step 3: Add a new field (email)
- restObjects.setMapValue(channelName, testMapObjectId, "email", ObjectValue.String("bob@example.com"))
+ restObjects.setMapValue(channelName, testMapObjectId, "email", ObjectData(string = "bob@example.com"))
// Wait for the map to be updated
assertWaiter { testMap.get("email")?.asString == "bob@example.com" }
@@ -154,7 +153,7 @@ class DefaultLiveMapTest: IntegrationTest() {
assertEquals("bob@example.com", testMap.get("email")?.asString, "Email should be added successfully")
// Step 4: Add another new field with different data type (score as number)
- restObjects.setMapValue(channelName, testMapObjectId, "score", ObjectValue.Number(85))
+ restObjects.setMapValue(channelName, testMapObjectId, "score", ObjectData(number = 85.0))
// Wait for the map to be updated
assertWaiter { testMap.get("score")?.asNumber == 85.0 }
@@ -167,7 +166,7 @@ class DefaultLiveMapTest: IntegrationTest() {
assertEquals(85.0, testMap.get("score")?.asNumber, "Score should be added as numeric value")
// Step 5: Update the boolean field
- restObjects.setMapValue(channelName, testMapObjectId, "isActive", ObjectValue.Boolean(false))
+ restObjects.setMapValue(channelName, testMapObjectId, "isActive", ObjectData(boolean = false))
// Wait for the map to be updated
assertWaiter { testMap.get("isActive")?.asBoolean == false }
@@ -357,7 +356,7 @@ class DefaultLiveMapTest: IntegrationTest() {
val userProfileSubscription = userProfile.subscribe { update -> userProfileUpdates.add(update) }
// Step 1: Update an existing field in the user profile map (change the name)
- restObjects.setMapValue(channelName, userProfileObjectId, "name", ObjectValue.String("Bob Smith"))
+ restObjects.setMapValue(channelName, userProfileObjectId, "name", ObjectData(string = "Bob Smith"))
// Wait for the update to be received
assertWaiter { userProfileUpdates.isNotEmpty() }
@@ -374,7 +373,7 @@ class DefaultLiveMapTest: IntegrationTest() {
// Step 2: Update another field in the user profile map (change the email)
userProfileUpdates.clear()
- restObjects.setMapValue(channelName, userProfileObjectId, "email", ObjectValue.String("bob@example.com"))
+ restObjects.setMapValue(channelName, userProfileObjectId, "email", ObjectData(string = "bob@example.com"))
// Wait for the second update
assertWaiter { userProfileUpdates.isNotEmpty() }
@@ -414,7 +413,7 @@ class DefaultLiveMapTest: IntegrationTest() {
userProfileUpdates.clear()
userProfileSubscription.unsubscribe()
// No updates should be received after unsubscribing
- restObjects.setMapValue(channelName, userProfileObjectId, "country", ObjectValue.String("uk"))
+ restObjects.setMapValue(channelName, userProfileObjectId, "country", ObjectData(string = "uk"))
// Wait for a moment to ensure no updates are received
assertWaiter { userProfile.size() == 4L }
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt
index 165563bd2..d06559377 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/RestObjects.kt
@@ -2,7 +2,6 @@ package io.ably.lib.objects.integration.helpers
import com.google.gson.JsonObject
import io.ably.lib.objects.ObjectData
-import io.ably.lib.objects.ObjectValue
import io.ably.lib.rest.AblyRest
import io.ably.lib.http.HttpUtils
import io.ably.lib.objects.integration.helpers.fixtures.DataFixtures
@@ -28,8 +27,7 @@ internal class RestObjects(options: ClientOptions) {
/**
* Sets a value (primitives, JsonObject, JsonArray, etc.) at the specified key in an existing map.
*/
- internal fun setMapValue(channelName: String, mapObjectId: String, key: String, value: ObjectValue) {
- val data = ObjectData(value = value)
+ internal fun setMapValue(channelName: String, mapObjectId: String, key: String, data: ObjectData) {
val mapCreateOp = PayloadBuilder.mapSetRestOp(mapObjectId, key, data)
operationRequest(channelName, mapCreateOp)
}
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/DataFixtures.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/DataFixtures.kt
index 18928cd19..f6f305aba 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/DataFixtures.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/DataFixtures.kt
@@ -2,53 +2,52 @@ package io.ably.lib.objects.integration.helpers.fixtures
import com.google.gson.JsonArray
import com.google.gson.JsonObject
-import io.ably.lib.objects.Binary
import io.ably.lib.objects.ObjectData
-import io.ably.lib.objects.ObjectValue
+import java.util.Base64
internal object DataFixtures {
/** Test fixture for string value ("stringValue") data type */
- internal val stringData = ObjectData(value = ObjectValue.String("stringValue"))
+ internal val stringData = ObjectData(string = "stringValue")
/** Test fixture for empty string data type */
- internal val emptyStringData = ObjectData(value = ObjectValue.String(""))
+ internal val emptyStringData = ObjectData(string = "")
/** Test fixture for binary data containing encoded JSON */
internal val bytesData = ObjectData(
- value = ObjectValue.Binary(Binary("eyJwcm9kdWN0SWQiOiAiMDAxIiwgInByb2R1Y3ROYW1lIjogImNhciJ9".toByteArray())))
+ bytes = Base64.getEncoder().encodeToString("eyJwcm9kdWN0SWQiOiAiMDAxIiwgInByb2R1Y3ROYW1lIjogImNhciJ9".toByteArray()))
/** Test fixture for empty binary data (zero-length byte array) */
- internal val emptyBytesData = ObjectData(value = ObjectValue.Binary(Binary(ByteArray(0))))
+ internal val emptyBytesData = ObjectData(bytes = Base64.getEncoder().encodeToString(ByteArray(0)))
/** Test fixture for maximum safe number value */
- internal val maxSafeNumberData = ObjectData(value = ObjectValue.Number(99999999.0))
+ internal val maxSafeNumberData = ObjectData(number = 99999999.0)
/** Test fixture for minimum safe number value */
- internal val negativeMaxSafeNumberData = ObjectData(value = ObjectValue.Number(-99999999.0))
+ internal val negativeMaxSafeNumberData = ObjectData(number = -99999999.0)
/** Test fixture for positive number value (1) */
- internal val numberData = ObjectData(value = ObjectValue.Number(1.0))
+ internal val numberData = ObjectData(number = 1.0)
/** Test fixture for zero number value */
- internal val zeroData = ObjectData(value = ObjectValue.Number(0.0))
+ internal val zeroData = ObjectData(number = 0.0)
/** Test fixture for boolean true value */
- internal val trueData = ObjectData(value = ObjectValue.Boolean(true))
+ internal val trueData = ObjectData(boolean = true)
/** Test fixture for boolean false value */
- internal val falseData = ObjectData(value = ObjectValue.Boolean(false))
+ internal val falseData = ObjectData(boolean = false)
/** Test fixture for JSON object value with single property */
- internal val objectData = ObjectData(value = ObjectValue.JsonObject(JsonObject().apply { addProperty("foo", "bar")}))
+ internal val objectData = ObjectData(json = JsonObject().apply { addProperty("foo", "bar") })
/** Test fixture for JSON array value with three string elements */
internal val arrayData = ObjectData(
- value = ObjectValue.JsonArray(JsonArray().apply {
+ json = JsonArray().apply {
add("foo")
add("bar")
add("baz")
- })
+ }
)
/**
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt
index b7979310c..475bbe86a 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/integration/helpers/fixtures/MapFixtures.kt
@@ -1,7 +1,6 @@
package io.ably.lib.objects.integration.helpers.fixtures
import io.ably.lib.objects.ObjectData
-import io.ably.lib.objects.ObjectValue
import io.ably.lib.objects.integration.helpers.RestObjects
/**
@@ -115,10 +114,10 @@ internal fun RestObjects.createUserMapObject(channelName: String): String {
val preferencesMapObjectId = createMap(
channelName,
data = mapOf(
- "theme" to ObjectData(value = ObjectValue.String("dark")),
- "notifications" to ObjectData(value = ObjectValue.Boolean(true)),
- "language" to ObjectData(value = ObjectValue.String("en")),
- "maxRetries" to ObjectData(value = ObjectValue.Number(3))
+ "theme" to ObjectData(string = "dark"),
+ "notifications" to ObjectData(boolean = true),
+ "language" to ObjectData(string = "en"),
+ "maxRetries" to ObjectData(number = 3.0)
)
)
@@ -128,8 +127,8 @@ internal fun RestObjects.createUserMapObject(channelName: String): String {
data = mapOf(
"totalLogins" to DataFixtures.mapRef(loginCounterObjectId),
"activeSessions" to DataFixtures.mapRef(sessionCounterObjectId),
- "lastLoginTime" to ObjectData(value = ObjectValue.String("2024-01-01T08:30:00Z")),
- "profileViews" to ObjectData(value = ObjectValue.Number(42))
+ "lastLoginTime" to ObjectData(string = "2024-01-01T08:30:00Z"),
+ "profileViews" to ObjectData(number = 42.0)
)
)
@@ -175,10 +174,10 @@ internal fun RestObjects.createUserProfileMapObject(channelName: String): String
return createMap(
channelName,
data = mapOf(
- "userId" to ObjectData(value = ObjectValue.String("user123")),
- "name" to ObjectData(value = ObjectValue.String("John Doe")),
- "email" to ObjectData(value = ObjectValue.String("john@example.com")),
- "isActive" to ObjectData(value = ObjectValue.Boolean(true)),
+ "userId" to ObjectData(string = "user123"),
+ "name" to ObjectData(string = "John Doe"),
+ "email" to ObjectData(string = "john@example.com"),
+ "isActive" to ObjectData(boolean = true),
)
)
}
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
index b2db8d292..5750046b0 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/HelpersTest.kt
@@ -428,21 +428,4 @@ class HelpersTest {
assertEquals(ErrorCode.ChannelStateError.code, ex2.errorInfo.code)
}
- // Binary utilities
- @Test
- fun testBinaryEqualityHashCodeAndSize() {
- val data1 = byteArrayOf(1, 2, 3, 4)
- val data2 = byteArrayOf(1, 2, 3, 4)
- val data3 = byteArrayOf(4, 3, 2, 1)
-
- val b1 = Binary(data1)
- val b2 = Binary(data2)
- val b3 = Binary(data3)
-
- assertEquals(b1, b2)
- assertEquals(b1.hashCode(), b2.hashCode())
- assertNotEquals(b1, b3)
-
- assertEquals(4, b1.size())
- }
}
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt
index de90b8648..b5b21f526 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSerializationTest.kt
@@ -158,9 +158,7 @@ class ObjectMessageSerializationTest {
extras = null,
operation = objectMessage.operation?.copy(
initialValue = null, // initialValue set to null
- mapOp = objectMessage.operation.mapOp?.copy(
- data = null // objectData set to null
- )
+ mapCreate = null
),
objectState = null,
serial = null,
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
index 32a51069a..1e7e0dfee 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/ObjectMessageSizeTest.kt
@@ -2,12 +2,14 @@ package io.ably.lib.objects.unit
import com.google.gson.JsonObject
import io.ably.lib.objects.*
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.CounterInc
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapSet
import io.ably.lib.objects.ObjectData
-import io.ably.lib.objects.ObjectsMapOp
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectOperationAction
-import io.ably.lib.objects.ObjectValue
import io.ably.lib.objects.ensureMessageSizeWithinLimit
import io.ably.lib.objects.size
import io.ably.lib.transport.Defaults
@@ -40,43 +42,43 @@ class ObjectMessageSizeTest {
action = ObjectOperationAction.MapCreate,
objectId = "obj_54321", // Not counted in operation size
- // MapOp contributes to operation size
- mapOp = ObjectsMapOp(
+ // MapSet contributes to operation size
+ mapSet = MapSet(
key = "mapKey", // Size: 6 bytes (UTF-8 byte length)
- data = ObjectData(
+ value = ObjectData(
objectId = "ref_obj", // Not counted in data size
- value = ObjectValue.String("sample") // Size: 6 bytes (UTF-8 byte length)
+ string = "sample" // Size: 6 bytes (UTF-8 byte length)
) // Total ObjectData size: 6 bytes
- ), // Total ObjectMapOp size: 6 + 6 = 12 bytes
+ ), // Total MapSet size: 6 + 6 = 12 bytes
- // CounterOp contributes to operation size
- counterOp = ObjectsCounterOp(
- amount = 10.0 // Size: 8 bytes (number is always 8 bytes)
- ), // Total ObjectCounterOp size: 8 bytes
+ // CounterInc contributes to operation size
+ counterInc = CounterInc(
+ number = 10.0 // Size: 8 bytes (number is always 8 bytes)
+ ), // Total CounterInc size: 8 bytes
- // Map contributes to operation size (for MAP_CREATE operations)
- map = ObjectsMap(
+ // MapCreate contributes to operation size (for MAP_CREATE operations)
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW, // Not counted in size
entries = mapOf(
"entry1" to ObjectsMapEntry( // Key size: 6 bytes
tombstone = false, // Not counted in entry size
timeserial = "ts_123", // Not counted in entry size
data = ObjectData(
- value = ObjectValue.String("value1") // Size: 6 bytes
+ string = "value1" // Size: 6 bytes
) // ObjectMapEntry size: 6 bytes
), // Total for this entry: 6 (key) + 6 (entry) = 12 bytes
"entry2" to ObjectsMapEntry( // Key size: 6 bytes
data = ObjectData(
- value = ObjectValue.Number(42) // Size: 8 bytes (number)
+ number = 42.0 // Size: 8 bytes (number)
) // ObjectMapEntry size: 8 bytes
) // Total for this entry: 6 (key) + 8 (entry) = 14 bytes
) // Total entries size: 12 + 14 = 26 bytes
- ), // Total ObjectMap size: 26 bytes
+ ), // Total MapCreate size: 26 bytes
- // Counter contributes to operation size (for COUNTER_CREATE operations)
- counter = ObjectsCounter(
+ // CounterCreate contributes to operation size (for COUNTER_CREATE operations)
+ counterCreate = CounterCreate(
count = 100.0 // Size: 8 bytes (number is always 8 bytes)
- ), // Total ObjectCounter size: 8 bytes
+ ), // Total CounterCreate size: 8 bytes
nonce = "nonce123", // Not counted in operation size
initialValue = "some-value", // Not counted in operation size
@@ -91,12 +93,12 @@ class ObjectMessageSizeTest {
createOp = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "create_obj",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "createKey", // Size: 9 bytes
- data = ObjectData(
- value = ObjectValue.String("createValue") // Size: 11 bytes
+ value = ObjectData(
+ string = "createValue" // Size: 11 bytes
) // ObjectData size: 11 bytes
- ) // ObjectMapOp size: 9 + 11 = 20 bytes
+ ) // MapSet size: 9 + 11 = 20 bytes
), // Total createOp size: 20 bytes
// map contributes to state size
@@ -104,7 +106,7 @@ class ObjectMessageSizeTest {
entries = mapOf(
"stateKey" to ObjectsMapEntry( // Key size: 8 bytes
data = ObjectData(
- value = ObjectValue.String("stateValue") // Size: 10 bytes
+ string = "stateValue" // Size: 10 bytes
) // ObjectMapEntry size: 10 bytes
) // Total: 8 + 10 = 18 bytes
)
@@ -133,11 +135,11 @@ class ObjectMessageSizeTest {
val objectMessage = ObjectMessage(
operation = ObjectOperation(
objectId = "",
- action = ObjectOperationAction.MapCreate,
- mapOp = ObjectsMapOp(
+ action = ObjectOperationAction.MapSet,
+ mapSet = MapSet(
key = "",
- data = ObjectData(
- value = ObjectValue.String("你😊") // 你 -> 3 bytes, 😊 -> 4 bytes
+ value = ObjectData(
+ string = "你😊" // 你 -> 3 bytes, 😊 -> 4 bytes
),
),
)
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt
index e09101ac0..2cd28574a 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/fixtures/ObjectMessageFixtures.kt
@@ -3,25 +3,27 @@ package io.ably.lib.objects.unit.fixtures
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import io.ably.lib.objects.*
-import io.ably.lib.objects.Binary
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapSet
import io.ably.lib.objects.ObjectData
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectState
-import io.ably.lib.objects.ObjectValue
+import java.util.Base64
-internal val dummyObjectDataStringValue = ObjectData(objectId = "object-id", ObjectValue.String("dummy string"))
+internal val dummyObjectDataStringValue = ObjectData(objectId = "object-id", string = "dummy string")
-internal val dummyBinaryObjectValue = ObjectData(objectId = "object-id", ObjectValue.Binary(Binary(byteArrayOf(1, 2, 3))))
+internal val dummyBinaryObjectValue = ObjectData(objectId = "object-id", bytes = Base64.getEncoder().encodeToString(byteArrayOf(1, 2, 3)))
-internal val dummyNumberObjectValue = ObjectData(objectId = "object-id", ObjectValue.Number(42.0))
+internal val dummyNumberObjectValue = ObjectData(objectId = "object-id", number = 42.0)
-internal val dummyBooleanObjectValue = ObjectData(objectId = "object-id", ObjectValue.Boolean(true))
+internal val dummyBooleanObjectValue = ObjectData(objectId = "object-id", boolean = true)
val dummyJsonObject = JsonObject().apply { addProperty("foo", "bar") }
-internal val dummyJsonObjectValue = ObjectData(objectId = "object-id", ObjectValue.JsonObject(dummyJsonObject))
+internal val dummyJsonObjectValue = ObjectData(objectId = "object-id", json = dummyJsonObject)
val dummyJsonArray = JsonArray().apply { add(1); add(2); add(3) }
-internal val dummyJsonArrayValue = ObjectData(objectId = "object-id", ObjectValue.JsonArray(dummyJsonArray))
+internal val dummyJsonArrayValue = ObjectData(objectId = "object-id", json = dummyJsonArray)
internal val dummyObjectsMapEntry = ObjectsMapEntry(
tombstone = false,
@@ -38,22 +40,15 @@ internal val dummyObjectsCounter = ObjectsCounter(
count = 123.0
)
-internal val dummyObjectsMapOp = ObjectsMapOp(
- key = "dummy-key",
- data = dummyObjectDataStringValue
-)
-
-internal val dummyObjectsCounterOp = ObjectsCounterOp(
- amount = 10.0
+internal val dummyMapCreate = MapCreate(
+ semantics = ObjectsMapSemantics.LWW,
+ entries = mapOf("dummy-key" to dummyObjectsMapEntry)
)
internal val dummyObjectOperation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "dummy-object-id",
- mapOp = dummyObjectsMapOp,
- counterOp = dummyObjectsCounterOp,
- map = dummyObjectsMap,
- counter = dummyObjectsCounter,
+ mapCreate = dummyMapCreate,
nonce = "dummy-nonce",
initialValue = "{\"foo\":\"bar\"}"
)
@@ -86,11 +81,8 @@ internal fun dummyObjectMessageWithStringData(): ObjectMessage {
internal fun dummyObjectMessageWithBinaryData(): ObjectMessage {
val binaryObjectMapEntry = dummyObjectsMapEntry.copy(data = dummyBinaryObjectValue)
val binaryObjectMap = dummyObjectsMap.copy(entries = mapOf("dummy-key" to binaryObjectMapEntry))
- val binaryObjectMapOp = dummyObjectsMapOp.copy(data = dummyBinaryObjectValue)
- val binaryObjectOperation = dummyObjectOperation.copy(
- mapOp = binaryObjectMapOp,
- map = binaryObjectMap
- )
+ val binaryMapCreate = dummyMapCreate.copy(entries = mapOf("dummy-key" to binaryObjectMapEntry))
+ val binaryObjectOperation = dummyObjectOperation.copy(mapCreate = binaryMapCreate)
val binaryObjectState = dummyObjectState.copy(
map = binaryObjectMap,
createOp = binaryObjectOperation
@@ -104,11 +96,8 @@ internal fun dummyObjectMessageWithBinaryData(): ObjectMessage {
internal fun dummyObjectMessageWithNumberData(): ObjectMessage {
val numberObjectMapEntry = dummyObjectsMapEntry.copy(data = dummyNumberObjectValue)
val numberObjectMap = dummyObjectsMap.copy(entries = mapOf("dummy-key" to numberObjectMapEntry))
- val numberObjectMapOp = dummyObjectsMapOp.copy(data = dummyNumberObjectValue)
- val numberObjectOperation = dummyObjectOperation.copy(
- mapOp = numberObjectMapOp,
- map = numberObjectMap
- )
+ val numberMapCreate = dummyMapCreate.copy(entries = mapOf("dummy-key" to numberObjectMapEntry))
+ val numberObjectOperation = dummyObjectOperation.copy(mapCreate = numberMapCreate)
val numberObjectState = dummyObjectState.copy(
map = numberObjectMap,
createOp = numberObjectOperation
@@ -122,11 +111,8 @@ internal fun dummyObjectMessageWithNumberData(): ObjectMessage {
internal fun dummyObjectMessageWithBooleanData(): ObjectMessage {
val booleanObjectMapEntry = dummyObjectsMapEntry.copy(data = dummyBooleanObjectValue)
val booleanObjectMap = dummyObjectsMap.copy(entries = mapOf("dummy-key" to booleanObjectMapEntry))
- val booleanObjectMapOp = dummyObjectsMapOp.copy(data = dummyBooleanObjectValue)
- val booleanObjectOperation = dummyObjectOperation.copy(
- mapOp = booleanObjectMapOp,
- map = booleanObjectMap
- )
+ val booleanMapCreate = dummyMapCreate.copy(entries = mapOf("dummy-key" to booleanObjectMapEntry))
+ val booleanObjectOperation = dummyObjectOperation.copy(mapCreate = booleanMapCreate)
val booleanObjectState = dummyObjectState.copy(
map = booleanObjectMap,
createOp = booleanObjectOperation
@@ -140,11 +126,11 @@ internal fun dummyObjectMessageWithBooleanData(): ObjectMessage {
internal fun dummyObjectMessageWithJsonObjectData(): ObjectMessage {
val jsonObjectMapEntry = dummyObjectsMapEntry.copy(data = dummyJsonObjectValue)
val jsonObjectMap = dummyObjectsMap.copy(entries = mapOf("dummy-key" to jsonObjectMapEntry))
- val jsonObjectMapOp = dummyObjectsMapOp.copy(data = dummyJsonObjectValue)
+ val jsonMapCreate = dummyMapCreate.copy(entries = mapOf("dummy-key" to jsonObjectMapEntry))
val jsonObjectOperation = dummyObjectOperation.copy(
action = ObjectOperationAction.MapSet,
- mapOp = jsonObjectMapOp,
- map = jsonObjectMap
+ mapCreate = null,
+ mapSet = MapSet(key = "dummy-key", value = dummyJsonObjectValue)
)
val jsonObjectState = dummyObjectState.copy(
map = jsonObjectMap,
@@ -159,11 +145,10 @@ internal fun dummyObjectMessageWithJsonObjectData(): ObjectMessage {
internal fun dummyObjectMessageWithJsonArrayData(): ObjectMessage {
val jsonArrayMapEntry = dummyObjectsMapEntry.copy(data = dummyJsonArrayValue)
val jsonArrayMap = dummyObjectsMap.copy(entries = mapOf("dummy-key" to jsonArrayMapEntry))
- val jsonArrayMapOp = dummyObjectsMapOp.copy(data = dummyJsonArrayValue)
val jsonArrayOperation = dummyObjectOperation.copy(
action = ObjectOperationAction.MapSet,
- mapOp = jsonArrayMapOp,
- map = jsonArrayMap
+ mapCreate = null,
+ mapSet = MapSet(key = "dummy-key", value = dummyJsonArrayValue)
)
val jsonArrayState = dummyObjectState.copy(
map = jsonArrayMap,
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
index 40565cabe..8afcd691a 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/DefaultRealtimeObjectsTest.kt
@@ -1,7 +1,6 @@
package io.ably.lib.objects.unit.objects
import io.ably.lib.objects.*
-import io.ably.lib.objects.ObjectsCounterOp
import io.ably.lib.objects.ObjectData
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperation
@@ -105,7 +104,7 @@ class DefaultRealtimeObjectsTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:testObject@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "serial1",
siteCode = "site1"
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
index 0a4ee5008..fd6cc874a 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/objects/ObjectsManagerTest.kt
@@ -197,7 +197,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "counter:testObject@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "serial1",
siteCode = "site1"
@@ -236,7 +236,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-ack-01",
siteCode = "site1"
@@ -267,7 +267,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = ObjectData(value = ObjectValue.String("value1")))
+ mapSet = MapSet(key = "key1", value = ObjectData(string = "value1"))
),
serial = "ser-map-01",
siteCode = "site1"
@@ -277,7 +277,7 @@ class ObjectsManagerTest {
objectsManager.applyAckResult(listOf(msg))
// Verify entry was set (LOCAL source)
- assertEquals("value1", liveMap.data["key1"]?.data?.value?.value,
+ assertEquals("value1", liveMap.data["key1"]?.data?.string,
"MAP_SET should be applied locally on ACK")
// Entry timeserial should be updated (within LiveMapManager, regardless of source)
assertEquals("ser-map-01", liveMap.data["key1"]?.timeserial,
@@ -306,7 +306,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-echo-01",
siteCode = "site1"
@@ -338,7 +338,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 3.0)
+ counterInc = CounterInc(number = 3.0)
),
serial = "ser-channel-01",
siteCode = "site1"
@@ -369,7 +369,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-ack-01",
siteCode = "site1"
@@ -411,7 +411,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-01",
siteCode = "site1"
@@ -468,7 +468,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-buffered",
siteCode = "site1"
@@ -527,7 +527,7 @@ class ObjectsManagerTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:test@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
),
serial = "ser-01",
siteCode = "site1"
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt
index 9c6bca377..3e82cebc9 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/DefaultLiveCounterTest.kt
@@ -1,7 +1,7 @@
package io.ably.lib.objects.unit.type.livecounter
-import io.ably.lib.objects.ObjectsCounter
-import io.ably.lib.objects.ObjectsCounterOp
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.CounterInc
import io.ably.lib.objects.ObjectMessage
import io.ably.lib.objects.ObjectOperation
import io.ably.lib.objects.ObjectOperationAction
@@ -49,7 +49,7 @@ class DefaultLiveCounterTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "counter:testCounter@2", // Different objectId
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
val message = ObjectMessage(
@@ -81,7 +81,7 @@ class DefaultLiveCounterTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "counter:testCounter@1", // Matching objectId
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
val message = ObjectMessage(
@@ -108,7 +108,7 @@ class DefaultLiveCounterTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "counter:testCounter@1", // Matching objectId
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
val message = ObjectMessage(
@@ -135,7 +135,7 @@ class DefaultLiveCounterTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:testCounter@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = io.ably.lib.objects.CounterInc(number = 5.0)
),
serial = "serial1",
siteCode = "site1"
@@ -160,7 +160,7 @@ class DefaultLiveCounterTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:testCounter@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = io.ably.lib.objects.CounterInc(number = 5.0)
),
serial = "serial1", // Older than "serial5"
siteCode = "site1"
@@ -184,7 +184,7 @@ class DefaultLiveCounterTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:testCounter@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = io.ably.lib.objects.CounterInc(number = 5.0)
),
serial = "serial1",
siteCode = "site1"
@@ -205,7 +205,7 @@ class DefaultLiveCounterTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "counter:testCounter@1",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = io.ably.lib.objects.CounterInc(number = 5.0)
),
serial = "serial1",
siteCode = "site1"
@@ -227,7 +227,7 @@ class DefaultLiveCounterTest {
operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "counter:testCounter@1",
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
),
serial = "serial1",
siteCode = "site1"
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt
index 813f44dc5..99124237c 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livecounter/LiveCounterManagerTest.kt
@@ -1,6 +1,9 @@
package io.ably.lib.objects.unit.type.livecounter
import io.ably.lib.objects.*
+import io.ably.lib.objects.CounterCreate
+import io.ably.lib.objects.CounterInc
+import io.ably.lib.objects.MapCreate
import io.ably.lib.objects.unit.LiveCounterManager
import io.ably.lib.objects.unit.TombstonedAt
import io.ably.lib.objects.unit.getDefaultLiveCounterWithMockedDeps
@@ -44,7 +47,7 @@ class DefaultLiveCounterManagerTest {
val createOp = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = ObjectsCounter(count = 10.0)
+ counterCreate = CounterCreate(count = 10.0)
)
val objectState = ObjectState(
@@ -71,7 +74,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = ObjectsCounter(count = 10.0)
+ counterCreate = CounterCreate(count = 10.0)
)
// RTLC7d1b - Should return true for successful COUNTER_CREATE
@@ -87,7 +90,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "testCounterId",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
)
// RTLC7d2b - Should return true for successful COUNTER_INC
@@ -119,7 +122,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate, // Unsupported action for counter
objectId = "testCounterId",
- map = ObjectsMap(semantics = ObjectsMapSemantics.LWW, entries = emptyMap())
+ mapCreate = MapCreate(semantics = ObjectsMapSemantics.LWW, entries = emptyMap())
)
// RTLC7d3 - Should return false for unsupported action (no longer throws)
@@ -135,7 +138,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
// RTLC7d1 - Apply counter create operation
@@ -158,7 +161,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
// RTLC8b - Should skip if already merged
@@ -181,7 +184,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = CounterCreate(count = 20.0)
)
// RTLC8c - Should apply if not merged
@@ -203,7 +206,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate,
objectId = "testCounterId",
- counter = null // No count specified
+ counterCreate = null // No count specified
)
// RTLC10a - Should default to 0
@@ -225,7 +228,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "testCounterId",
- counterOp = ObjectsCounterOp(amount = 5.0)
+ counterInc = CounterInc(number = 5.0)
)
// RTLC7d2 - Apply counter increment operation
@@ -242,7 +245,7 @@ class DefaultLiveCounterManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "testCounterId",
- counterOp = null // Missing payload
+ counterInc = null // Missing payload
)
// RTLC7d2 - Should throw error for missing payload
@@ -265,36 +268,36 @@ class DefaultLiveCounterManagerTest {
// Set initial data
liveCounter.data.set(10.0)
- val counterOp = ObjectsCounterOp(amount = 7.0)
+ val counterInc = CounterInc(number = 7.0)
// RTLC9b - Apply counter increment
liveCounterManager.applyOperation(ObjectOperation(
action = ObjectOperationAction.CounterInc,
objectId = "testCounterId",
- counterOp = counterOp
+ counterInc = counterInc
), null)
assertEquals(17.0, liveCounter.data.get()) // 10 + 7
}
@Test
- fun `(RTLC9, RTLC9b) LiveCounterManager should handle null amount in counter increment`() {
+ fun `(RTLC7, RTLC7d2) LiveCounterManager should throw error when counterInc payload missing`() {
val liveCounter = getDefaultLiveCounterWithMockedDeps()
val liveCounterManager = liveCounter.LiveCounterManager
// Set initial data
liveCounter.data.set(10.0)
- val counterOp = ObjectsCounterOp(amount = null) // Null amount
-
- // RTLC9b - Apply counter increment with null amount
- liveCounterManager.applyOperation(ObjectOperation(
- action = ObjectOperationAction.CounterInc,
- objectId = "testCounterId",
- counterOp = counterOp
- ), null)
-
- assertEquals(10.0, liveCounter.data.get()) // Should not change (null defaults to 0)
+ // RTLC7d2 - Apply counter increment with no payload - throws error
+ val exception = assertFailsWith {
+ liveCounterManager.applyOperation(ObjectOperation(
+ action = ObjectOperationAction.CounterInc,
+ objectId = "testCounterId",
+ counterInc = null
+ ), null)
+ }
+ assertNotNull(exception.errorInfo)
+ assertEquals(92000, exception.errorInfo.code)
}
@Test
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt
index 4746b91ee..7ddd43937 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/DefaultLiveMapTest.kt
@@ -2,7 +2,9 @@ package io.ably.lib.objects.unit.type.livemap
import io.ably.lib.objects.ObjectsMapSemantics
import io.ably.lib.objects.ObjectsMap
-import io.ably.lib.objects.ObjectsMapOp
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapSet
+import io.ably.lib.objects.MapRemove
import io.ably.lib.objects.ObjectsOperationSource
import io.ably.lib.objects.ObjectState
import io.ably.lib.objects.ObjectMessage
@@ -53,7 +55,7 @@ class DefaultLiveMapTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@2", // Different objectId
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = emptyMap()
)
@@ -88,7 +90,7 @@ class DefaultLiveMapTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1", // Matching objectId
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = emptyMap()
)
@@ -118,7 +120,7 @@ class DefaultLiveMapTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1", // Matching objectId
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = emptyMap()
)
@@ -148,7 +150,7 @@ class DefaultLiveMapTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = io.ably.lib.objects.ObjectData(value = io.ably.lib.objects.ObjectValue.String("value1")))
+ mapSet = io.ably.lib.objects.MapSet(key = "key1", value = io.ably.lib.objects.ObjectData(string = "value1"))
),
serial = "serial1",
siteCode = "site1"
@@ -158,7 +160,7 @@ class DefaultLiveMapTest {
val result = liveMap.applyObject(message, ObjectsOperationSource.LOCAL)
assertTrue(result, "applyObject should return true for successful MAP_SET")
- assertEquals("value1", liveMap.data["key1"]?.data?.value?.value, "map entry should be updated for LOCAL source")
+ assertEquals("value1", liveMap.data["key1"]?.data?.string, "map entry should be updated for LOCAL source")
assertFalse(liveMap.siteTimeserials.containsKey("site1"),
"siteTimeserials should NOT be updated for LOCAL source")
}
@@ -173,7 +175,7 @@ class DefaultLiveMapTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = io.ably.lib.objects.ObjectData(value = io.ably.lib.objects.ObjectValue.String("value1")))
+ mapSet = io.ably.lib.objects.MapSet(key = "key1", value = io.ably.lib.objects.ObjectData(string = "value1"))
),
serial = "serial1", // Older than "serial5"
siteCode = "site1"
@@ -196,7 +198,7 @@ class DefaultLiveMapTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = io.ably.lib.objects.ObjectData(value = io.ably.lib.objects.ObjectValue.String("value1")))
+ mapSet = io.ably.lib.objects.MapSet(key = "key1", value = io.ably.lib.objects.ObjectData(string = "value1"))
),
serial = "serial1",
siteCode = "site1"
@@ -217,7 +219,7 @@ class DefaultLiveMapTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = io.ably.lib.objects.ObjectData(value = io.ably.lib.objects.ObjectValue.String("value1")))
+ mapSet = io.ably.lib.objects.MapSet(key = "key1", value = io.ably.lib.objects.ObjectData(string = "value1"))
),
serial = "serial1",
siteCode = "site1"
@@ -227,7 +229,7 @@ class DefaultLiveMapTest {
val result = liveMap.applyObject(message, ObjectsOperationSource.CHANNEL)
assertTrue(result, "applyObject should return true for successful MAP_SET")
- assertEquals("value1", liveMap.data["key1"]?.data?.value?.value)
+ assertEquals("value1", liveMap.data["key1"]?.data?.string)
}
@Test
@@ -239,7 +241,7 @@ class DefaultLiveMapTest {
operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1")
+ mapRemove = io.ably.lib.objects.MapRemove(key = "key1")
),
serial = "serial1",
siteCode = "site1"
diff --git a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt
index a1da570fe..238e1798d 100644
--- a/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt
+++ b/liveobjects/src/test/kotlin/io/ably/lib/objects/unit/type/livemap/LiveMapManagerTest.kt
@@ -1,6 +1,9 @@
package io.ably.lib.objects.unit.type.livemap
import io.ably.lib.objects.*
+import io.ably.lib.objects.MapCreate
+import io.ably.lib.objects.MapRemove
+import io.ably.lib.objects.MapSet
import io.ably.lib.objects.type.livemap.LiveMapEntry
import io.ably.lib.objects.type.livemap.LiveMapManager
import io.ably.lib.objects.type.map.LiveMapUpdate
@@ -26,7 +29,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val objectState = ObjectState(
@@ -35,11 +38,11 @@ class LiveMapManagerTest {
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue1")),
+ data = ObjectData(string = "newValue1"),
timeserial = "serial1"
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value2")),
+ data = ObjectData(string = "value2"),
timeserial = "serial2"
)
)
@@ -52,8 +55,8 @@ class LiveMapManagerTest {
assertFalse(liveMap.createOperationIsMerged) // RTLM6b
assertEquals(2, liveMap.data.size) // RTLM6c
- assertEquals("newValue1", liveMap.data["key1"]?.data?.value?.value) // RTLM6c
- assertEquals("value2", liveMap.data["key2"]?.data?.value?.value) // RTLM6c
+ assertEquals("newValue1", liveMap.data["key1"]?.data?.string) // RTLM6c
+ assertEquals("value2", liveMap.data["key2"]?.data?.string) // RTLM6c
// Assert on update field - should show changes from old to new state
val expectedUpdate = mapOf(
@@ -72,7 +75,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val objectState = ObjectState(
@@ -104,7 +107,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val objectState = ObjectState(
@@ -133,21 +136,21 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val createOp = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("createValue")),
+ data = ObjectData(string = "createValue"),
timeserial = "serial1"
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue")),
+ data = ObjectData(string = "newValue"),
timeserial = "serial2"
)
)
@@ -160,7 +163,7 @@ class LiveMapManagerTest {
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("stateValue")),
+ data = ObjectData(string = "stateValue"),
timeserial = "serial3"
)
)
@@ -174,8 +177,8 @@ class LiveMapManagerTest {
val update = liveMapManager.applyState(objectState, null)
assertEquals(2, liveMap.data.size) // Should have both state and create op entries
- assertEquals("stateValue", liveMap.data["key1"]?.data?.value?.value) // State value takes precedence
- assertEquals("newValue", liveMap.data["key2"]?.data?.value?.value) // Create op value
+ assertEquals("stateValue", liveMap.data["key1"]?.data?.string) // State value takes precedence
+ assertEquals("newValue", liveMap.data["key2"]?.data?.string) // Create op value
// Assert on update field - should show changes from create operation
val expectedUpdate = mapOf(
@@ -194,7 +197,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val expectedTimestamp = 1234567890L
@@ -204,13 +207,13 @@ class LiveMapManagerTest {
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue")),
+ data = ObjectData(string = "newValue"),
timeserial = "serial1",
tombstone = true,
serialTimestamp = expectedTimestamp
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value2")),
+ data = ObjectData(string = "value2"),
timeserial = "serial2"
)
)
@@ -225,7 +228,7 @@ class LiveMapManagerTest {
assertEquals(2, liveMap.data.size) // RTLM6c
assertTrue(liveMap.data["key1"]?.isTombstoned == true) // Should be tombstoned
assertEquals(expectedTimestamp, liveMap.data["key1"]?.tombstonedAt) // Should use provided serialTimestamp
- assertEquals("value2", liveMap.data["key2"]?.data?.value?.value) // RTLM6c
+ assertEquals("value2", liveMap.data["key2"]?.data?.string) // RTLM6c
// Assert on update field - should show that key1 was removed (tombstoned)
val expectedUpdate = mapOf(
@@ -244,7 +247,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val objectState = ObjectState(
@@ -253,13 +256,13 @@ class LiveMapManagerTest {
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue")),
+ data = ObjectData(string = "newValue"),
timeserial = "serial1",
tombstone = true,
serialTimestamp = null // No timestamp provided
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value2")),
+ data = ObjectData(string = "value2"),
timeserial = "serial2"
)
)
@@ -278,7 +281,7 @@ class LiveMapManagerTest {
assertNotNull(liveMap.data["key1"]?.tombstonedAt) // Should have timestamp
assertTrue(liveMap.data["key1"]?.tombstonedAt!! >= beforeOperation) // Should be after operation start
assertTrue(liveMap.data["key1"]?.tombstonedAt!! <= afterOperation) // Should be before operation end
- assertEquals("value2", liveMap.data["key2"]?.data?.value?.value) // RTLM6c
+ assertEquals("value2", liveMap.data["key2"]?.data?.string) // RTLM6c
// Assert on update field - should show that key1 was removed (tombstoned)
val expectedUpdate = mapOf(
@@ -297,15 +300,15 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value1")),
+ data = ObjectData(string = "value1"),
timeserial = "serial1"
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value2")),
+ data = ObjectData(string = "value2"),
timeserial = "serial2"
)
)
@@ -316,8 +319,8 @@ class LiveMapManagerTest {
liveMapManager.applyOperation(operation, "serial1", null)
assertEquals(2, liveMap.data.size) // Should have both entries
- assertEquals("value1", liveMap.data["key1"]?.data?.value?.value) // Should have value1
- assertEquals("value2", liveMap.data["key2"]?.data?.value?.value) // Should have value2
+ assertEquals("value1", liveMap.data["key1"]?.data?.string) // Should have value1
+ assertEquals("value2", liveMap.data["key2"]?.data?.string) // Should have value2
assertTrue(liveMap.createOperationIsMerged) // Should be marked as merged
}
@@ -330,24 +333,24 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val expectedTimestamp = 1234567890L
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("createValue")),
+ data = ObjectData(string = "createValue"),
timeserial = "serial2",
tombstone = true,
serialTimestamp = expectedTimestamp
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue")),
+ data = ObjectData(string = "newValue"),
timeserial = "serial3"
),
"key3" to ObjectsMapEntry(
@@ -365,7 +368,7 @@ class LiveMapManagerTest {
assertEquals(3, liveMap.data.size) // Should have all entries
assertTrue(liveMap.data["key1"]?.isTombstoned == true) // RTLM17a2 - Should be tombstoned
assertEquals(expectedTimestamp, liveMap.data["key1"]?.tombstonedAt) // Should use provided serialTimestamp
- assertEquals("newValue", liveMap.data["key2"]?.data?.value?.value) // RTLM17a1 - Should be added
+ assertEquals("newValue", liveMap.data["key2"]?.data?.string) // RTLM17a1 - Should be added
assertTrue(liveMap.data["key3"]?.isTombstoned == true) // RTLM17a2 - Should be tombstoned
assertTrue(liveMap.createOperationIsMerged) // RTLM17b - Should be marked as merged
}
@@ -379,22 +382,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM15d2 - Apply map set operation
liveMapManager.applyOperation(operation, "serial2", null)
- assertEquals("newValue", liveMap.data["key1"]?.data?.value?.value) // RTLM7a2a
+ assertEquals("newValue", liveMap.data["key1"]?.data?.string) // RTLM7a2a
assertEquals("serial2", liveMap.data["key1"]?.timeserial) // RTLM7a2b
assertFalse(liveMap.data["key1"]?.isTombstoned == true) // RTLM7a2c
}
@@ -408,13 +411,13 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1")
+ mapRemove = MapRemove(key = "key1")
)
val expectedTimestamp = 1234567890L
@@ -436,13 +439,13 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1")
+ mapRemove = MapRemove(key = "key1")
)
val beforeOperation = System.currentTimeMillis()
@@ -466,7 +469,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(semantics = ObjectsMapSemantics.LWW, entries = emptyMap())
+ mapCreate = MapCreate(semantics = ObjectsMapSemantics.LWW, entries = emptyMap())
)
// RTLM15d1b - Should return true for successful MAP_CREATE
@@ -482,7 +485,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1", data = ObjectData(value = ObjectValue.String("value1")))
+ mapSet = MapSet(key = "key1", value = ObjectData(string = "value1"))
)
// RTLM15d2b - Should return true for successful MAP_SET
@@ -498,7 +501,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1")
+ mapRemove = MapRemove(key = "key1")
)
// RTLM15d3b - Should return true for successful MAP_REMOVE
@@ -530,7 +533,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.CounterCreate, // Unsupported action for map
objectId = "map:testMap@1",
- counter = ObjectsCounter(count = 20.0)
+ counterCreate = io.ably.lib.objects.CounterCreate(count = 20.0)
)
// RTLM15d4 - Should return false for unsupported action (no longer throws)
@@ -549,11 +552,11 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("value1")),
+ data = ObjectData(string = "value1"),
timeserial = "serial1"
)
)
@@ -578,21 +581,21 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.LWW,
entries = mapOf(
"key1" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("createValue")),
+ data = ObjectData(string = "createValue"),
timeserial = "serial2"
),
"key2" to ObjectsMapEntry(
- data = ObjectData(value = ObjectValue.String("newValue")),
+ data = ObjectData(string = "newValue"),
timeserial = "serial3"
),
"key3" to ObjectsMapEntry(
@@ -608,8 +611,8 @@ class LiveMapManagerTest {
liveMapManager.applyOperation(operation, "serial1", null)
assertEquals(3, liveMap.data.size) // Should have all entries
- assertEquals("createValue", liveMap.data["key1"]?.data?.value?.value) // RTLM17a1 - Should be updated
- assertEquals("newValue", liveMap.data["key2"]?.data?.value?.value) // RTLM17a1 - Should be added
+ assertEquals("createValue", liveMap.data["key1"]?.data?.string) // RTLM17a1 - Should be updated
+ assertEquals("newValue", liveMap.data["key2"]?.data?.string) // RTLM17a1 - Should be added
assertTrue(liveMap.data["key3"]?.isTombstoned == true) // RTLM17a2 - Should be tombstoned
assertTrue(liveMap.createOperationIsMerged) // RTLM17b - Should be marked as merged
}
@@ -622,9 +625,9 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "newKey",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
@@ -632,7 +635,7 @@ class LiveMapManagerTest {
liveMapManager.applyOperation(operation, "serial1", null)
assertEquals(1, liveMap.data.size) // Should have one entry
- assertEquals("newValue", liveMap.data["newKey"]?.data?.value?.value) // RTLM7b1
+ assertEquals("newValue", liveMap.data["newKey"]?.data?.string) // RTLM7b1
assertEquals("serial1", liveMap.data["newKey"]?.timeserial) // Should have serial
assertFalse(liveMap.data["newKey"]?.isTombstoned == true) // RTLM7b2
}
@@ -646,22 +649,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial2", // Higher than "serial1"
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM7a - Should skip operation with lower serial
liveMapManager.applyOperation(operation, "serial1", null)
- assertEquals("existingValue", liveMap.data["key1"]?.data?.value?.value) // Should not change
+ assertEquals("existingValue", liveMap.data["key1"]?.data?.string) // Should not change
assertEquals("serial2", liveMap.data["key1"]?.timeserial) // Should keep original serial
}
@@ -673,7 +676,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "nonExistingKey")
+ mapRemove = MapRemove(key = "nonExistingKey")
)
// RTLM8b - Create tombstoned entry for non-existing key
@@ -694,19 +697,19 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial2", // Higher than "serial1"
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapRemove,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(key = "key1")
+ mapRemove = MapRemove(key = "key1")
)
// RTLM8a - Should skip operation with lower serial
liveMapManager.applyOperation(operation, "serial1", null)
- assertEquals("existingValue", liveMap.data["key1"]?.data?.value?.value) // Should not change
+ assertEquals("existingValue", liveMap.data["key1"]?.data?.string) // Should not change
assertEquals("serial2", liveMap.data["key1"]?.timeserial) // Should keep original serial
assertFalse(liveMap.data["key1"]?.isTombstoned == true) // Should not be tombstoned
}
@@ -720,22 +723,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = null,
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM9b - Both null serials should be treated as equal
liveMapManager.applyOperation(operation, null, null)
- assertEquals("existingValue", liveMap.data["key1"]?.data?.value?.value) // Should not change
+ assertEquals("existingValue", liveMap.data["key1"]?.data?.string) // Should not change
}
@Test
@@ -747,22 +750,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = null,
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM9d - Operation serial is greater than missing entry serial
liveMapManager.applyOperation(operation, "serial1", null)
- assertEquals("newValue", liveMap.data["key1"]?.data?.value?.value) // Should be updated
+ assertEquals("newValue", liveMap.data["key1"]?.data?.string) // Should be updated
assertEquals("serial1", liveMap.data["key1"]?.timeserial) // Should have new serial
}
@@ -775,22 +778,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM9c - Missing operation serial is lower than existing entry serial
liveMapManager.applyOperation(operation, null, null)
- assertEquals("existingValue", liveMap.data["key1"]?.data?.value?.value) // Should not change
+ assertEquals("existingValue", liveMap.data["key1"]?.data?.string) // Should not change
assertEquals("serial1", liveMap.data["key1"]?.timeserial) // Should keep original serial
}
@@ -803,22 +806,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial1",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM9e - Higher serial should be applied
liveMapManager.applyOperation(operation, "serial2", null)
- assertEquals("newValue", liveMap.data["key1"]?.data?.value?.value) // Should be updated
+ assertEquals("newValue", liveMap.data["key1"]?.data?.string) // Should be updated
assertEquals("serial2", liveMap.data["key1"]?.timeserial) // Should have new serial
}
@@ -831,22 +834,22 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "serial2",
- data = ObjectData(value = ObjectValue.String("existingValue"))
+ data = ObjectData(string = "existingValue")
)
val operation = ObjectOperation(
action = ObjectOperationAction.MapSet,
objectId = "map:testMap@1",
- mapOp = ObjectsMapOp(
+ mapSet = MapSet(
key = "key1",
- data = ObjectData(value = ObjectValue.String("newValue"))
+ value = ObjectData(string = "newValue")
)
)
// RTLM9e - Lower serial should be skipped
liveMapManager.applyOperation(operation, "serial1", null)
- assertEquals("existingValue", liveMap.data["key1"]?.data?.value?.value) // Should not change
+ assertEquals("existingValue", liveMap.data["key1"]?.data?.string) // Should not change
assertEquals("serial2", liveMap.data["key1"]?.timeserial) // Should keep original serial
}
@@ -858,7 +861,7 @@ class LiveMapManagerTest {
val operation = ObjectOperation(
action = ObjectOperationAction.MapCreate,
objectId = "map:testMap@1",
- map = ObjectsMap(
+ mapCreate = MapCreate(
semantics = ObjectsMapSemantics.Unknown, // This should match, but we'll test error case
entries = emptyMap()
)
@@ -890,7 +893,7 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val result2 = livemapManager.calculateUpdateFromDataDiff(prevData2, newData2)
@@ -901,7 +904,7 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val newData3 = mapOf()
@@ -913,14 +916,14 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val newData4 = mapOf(
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "2",
- data = ObjectData(value = ObjectValue.String("value2"))
+ data = ObjectData(string = "value2")
)
)
val result4 = livemapManager.calculateUpdateFromDataDiff(prevData4, newData4)
@@ -931,7 +934,7 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val newData5 = mapOf(
@@ -956,7 +959,7 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "2",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val result6 = livemapManager.calculateUpdateFromDataDiff(prevData6, newData6)
@@ -974,7 +977,7 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = true,
timeserial = "2",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val result7 = livemapManager.calculateUpdateFromDataDiff(prevData7, newData7)
@@ -997,24 +1000,24 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
),
"key2" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value2"))
+ data = ObjectData(string = "value2")
)
)
val newData9 = mapOf(
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "2",
- data = ObjectData(value = ObjectValue.String("value1_updated"))
+ data = ObjectData(string = "value1_updated")
),
"key3" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value3"))
+ data = ObjectData(string = "value3")
)
)
val result9 = livemapManager.calculateUpdateFromDataDiff(prevData9, newData9)
@@ -1048,14 +1051,14 @@ class LiveMapManagerTest {
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val newData11 = mapOf(
"key1" to LiveMapEntry(
isTombstoned = false,
timeserial = "2",
- data = ObjectData(value = ObjectValue.String("value1"))
+ data = ObjectData(string = "value1")
)
)
val result11 = livemapManager.calculateUpdateFromDataDiff(prevData11, newData11)
@@ -1071,7 +1074,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val expectedTimestamp = 1234567890L
@@ -1102,7 +1105,7 @@ class LiveMapManagerTest {
liveMap.data["key1"] = LiveMapEntry(
isTombstoned = false,
timeserial = "1",
- data = ObjectData(value = ObjectValue.String("oldValue"))
+ data = ObjectData(string = "oldValue")
)
val objectState = ObjectState(