Skip to content

Commit 010a4e3

Browse files
committed
[ECO-5482] Annotated objects realtime API with relevant spec ids
1 parent ef1b3d7 commit 010a4e3

8 files changed

Lines changed: 54 additions & 29 deletions

File tree

lib/src/main/java/io/ably/lib/objects/LiveObjects.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import io.ably.lib.objects.type.counter.LiveCounter;
55
import io.ably.lib.objects.type.map.LiveMap;
66
import io.ably.lib.objects.type.map.LiveMapValue;
7-
import io.ably.lib.types.Callback;
87
import org.jetbrains.annotations.Blocking;
98
import org.jetbrains.annotations.NonBlocking;
109
import org.jetbrains.annotations.NotNull;

live-objects/src/main/kotlin/io/ably/lib/objects/DefaultLiveObjects.kt

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -98,19 +98,19 @@ internal class DefaultLiveObjects(internal val channelName: String, internal val
9898
}
9999

100100
private suspend fun createMapAsync(entries: MutableMap<String, LiveMapValue>): LiveMap {
101-
adapter.throwIfInvalidWriteApiConfiguration(channelName)
101+
adapter.throwIfInvalidWriteApiConfiguration(channelName) // RTO11c, RTO11d, RTO11e
102102

103-
if (entries.keys.any { it.isEmpty() }) {
104-
throw objectError("Map keys should not be empty")
103+
if (entries.keys.any { it.isEmpty() }) { // RTO11f2
104+
throw invalidInputError("Map keys should not be empty")
105105
}
106106

107-
// Create initial value operation
107+
// RTO11f4 - Create initial value operation
108108
val initialMapValue = DefaultLiveMap.initialValue(entries)
109109

110-
// Create initial value JSON string
110+
// RTO11f5 - Create initial value JSON string
111111
val initialValueJSONString = gson.toJson(initialMapValue)
112112

113-
// Create object ID from initial value
113+
// RTO11f8 - Create object ID from initial value
114114
val (objectId, nonce) = getObjectIdStringWithNonce(ObjectType.Map, initialValueJSONString)
115115

116116
// Create ObjectMessage with the operation
@@ -124,28 +124,29 @@ internal class DefaultLiveObjects(internal val channelName: String, internal val
124124
)
125125
)
126126

127-
// Publish the message
127+
// RTO11g - Publish the message
128128
publish(arrayOf(msg))
129129

130-
// Check if object already exists in pool, otherwise create a zero-value object using the sequential scope
130+
// RTO11h - Check if object already exists in pool, otherwise create a zero-value object using the sequential scope
131131
return objectsPool.get(objectId) as? LiveMap ?: withContext(sequentialScope.coroutineContext) {
132132
objectsPool.createZeroValueObjectIfNotExists(objectId) as LiveMap
133133
}
134134
}
135135

136136
private suspend fun createCounterAsync(initialValue: Number): LiveCounter {
137-
adapter.throwIfInvalidWriteApiConfiguration(channelName)
137+
adapter.throwIfInvalidWriteApiConfiguration(channelName) // RTO12c, RTO12d, RTO12e
138138

139139
// Validate input parameter
140140
if (initialValue.toDouble().isNaN() || initialValue.toDouble().isInfinite()) {
141-
throw objectError("Counter value should be a valid number")
141+
throw invalidInputError("Counter value should be a valid number")
142142
}
143143

144+
// RTO12f2
144145
val initialCounterValue = DefaultLiveCounter.initialValue(initialValue)
145-
// Create initial value operation
146+
// RTO12f3 - Create initial value operation
146147
val initialValueJSONString = gson.toJson(initialCounterValue)
147148

148-
// Create object ID from initial value
149+
// RTO12f6- Create object ID from initial value
149150
val (objectId, nonce) = getObjectIdStringWithNonce(ObjectType.Counter, initialValueJSONString)
150151

151152
// Create ObjectMessage with the operation
@@ -159,15 +160,18 @@ internal class DefaultLiveObjects(internal val channelName: String, internal val
159160
)
160161
)
161162

162-
// Publish the message
163+
// RTO12g - Publish the message
163164
publish(arrayOf(msg))
164165

165-
// Check if object already exists in pool, otherwise create a zero-value object using the sequential scope
166+
// RTO12h - Check if object already exists in pool, otherwise create a zero-value object using the sequential scope
166167
return objectsPool.get(objectId) as? LiveCounter ?: withContext(sequentialScope.coroutineContext) {
167168
objectsPool.createZeroValueObjectIfNotExists(objectId) as LiveCounter
168169
}
169170
}
170171

172+
/**
173+
* Spec: RTO11f8, RTO12f6
174+
*/
171175
private suspend fun getObjectIdStringWithNonce(objectType: ObjectType, initialValue: String): Pair<String, String> {
172176
val nonce = generateNonce()
173177
val msTimestamp = ServerTime.getCurrentTime(adapter) // RTO16 - Get server time for nonce generation

live-objects/src/main/kotlin/io/ably/lib/objects/ErrorCodes.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ internal enum class ErrorCode(public val code: Int) {
66
MaxMessageSizeExceeded(40_009),
77
InvalidObject(92_000),
88
// LiveMap specific error codes
9-
MapKeyShouldBeString(40_003),
9+
InvalidInputParams(40_003),
1010
MapValueDataTypeUnsupported(40_013),
1111
// Channel mode and state validation error codes
1212
ChannelModeRequired(40_024),

live-objects/src/main/kotlin/io/ably/lib/objects/ObjectId.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,20 @@ internal class ObjectId private constructor(
1212
) {
1313
/**
1414
* Converts ObjectId to string representation.
15+
* Spec: RTO6b1
1516
*/
1617
override fun toString(): String {
1718
return "${type.value}:$hash@$timestampMs"
1819
}
1920

2021
companion object {
2122

23+
/**
24+
* Spec: RTO14
25+
*/
2226
internal fun fromInitialValue(objectType: ObjectType, initialValue: String, nonce: String, msTimeStamp: Long): ObjectId {
2327
val valueForHash = "$initialValue:$nonce".toByteArray(StandardCharsets.UTF_8)
28+
// RTO14b - Hash the initial value and nonce to create a unique identifier
2429
val hashBytes = MessageDigest.getInstance("SHA-256").digest(valueForHash)
2530
val urlSafeHash = Base64.getUrlEncoder().withoutPadding().encodeToString(hashBytes)
2631

live-objects/src/main/kotlin/io/ably/lib/objects/ServerTime.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,18 @@ import kotlinx.coroutines.sync.withLock
77
import kotlinx.coroutines.withContext
88
import kotlin.concurrent.Volatile
99

10+
/**
11+
* ServerTime is a utility object that provides the current server time
12+
* Spec: RTO16
13+
*/
1014
internal object ServerTime {
1115
@Volatile
1216
private var serverTimeOffset: Long? = null
1317
private val mutex = Mutex()
1418

19+
/**
20+
* Spec: RTO16a
21+
*/
1522
@Throws(AblyException::class)
1623
internal suspend fun getCurrentTime(adapter: LiveObjectsAdapter): Long {
1724
if (serverTimeOffset == null) {

live-objects/src/main/kotlin/io/ably/lib/objects/Utils.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ internal fun serverError(errorMessage: String) = ablyException(errorMessage, Err
4141
internal fun objectError(errorMessage: String, cause: Throwable? = null): AblyException {
4242
return ablyException(errorMessage, ErrorCode.InvalidObject, HttpStatusCode.InternalServerError, cause)
4343
}
44+
45+
internal fun invalidInputError(errorMessage: String, cause: Throwable? = null): AblyException {
46+
return ablyException(errorMessage, ErrorCode.InvalidInputParams, HttpStatusCode.InternalServerError, cause)
47+
}
48+
4449
/**
4550
* Calculates the byte size of a string.
4651
* For non-ASCII, the byte size can be 2–4x the character count. For ASCII, there is no difference.

live-objects/src/main/kotlin/io/ably/lib/objects/type/livecounter/DefaultLiveCounter.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,15 +70,15 @@ internal class DefaultLiveCounter private constructor(
7070
override fun validate(state: ObjectState) = liveCounterManager.validate(state)
7171

7272
private suspend fun incrementAsync(amount: Double) {
73-
// Validate write API configuration
73+
// RTLC12b, RTLC12c, RTLC12d - Validate write API configuration
7474
adapter.throwIfInvalidWriteApiConfiguration(channelName)
7575

76-
// Validate input parameter
76+
// RTLC12e1 - Validate input parameter
7777
if (amount.isNaN() || amount.isInfinite()) {
78-
throw objectError("Counter value increment should be a valid number")
78+
throw invalidInputError("Counter value increment should be a valid number")
7979
}
8080

81-
// Create ObjectMessage with the COUNTER_INC operation
81+
// RTLC12e2, RTLC12e3, RTLC12e4 - Create ObjectMessage with the COUNTER_INC operation
8282
val msg = ObjectMessage(
8383
operation = ObjectOperation(
8484
action = ObjectOperationAction.CounterInc,
@@ -87,7 +87,7 @@ internal class DefaultLiveCounter private constructor(
8787
)
8888
)
8989

90-
// Publish the message
90+
// RTLC12f - Publish the message
9191
liveObjects.publish(arrayOf(msg))
9292
}
9393

@@ -127,6 +127,7 @@ internal class DefaultLiveCounter private constructor(
127127

128128
/**
129129
* Creates initial value operation for counter creation.
130+
* Spec: RTO12f2
130131
*/
131132
internal fun initialValue(count: Number): CounterCreatePayload {
132133
return CounterCreatePayload(

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,15 @@ internal class DefaultLiveMap private constructor(
117117
override fun unsubscribeAll() = liveMapManager.unsubscribeAll()
118118

119119
private suspend fun setAsync(keyName: String, value: LiveMapValue) {
120-
// Validate write API configuration
120+
// RTLM20b, RTLM20c, RTLM20d - Validate write API configuration
121121
adapter.throwIfInvalidWriteApiConfiguration(channelName)
122122

123123
// Validate input parameters
124124
if (keyName.isEmpty()) {
125-
throw objectError("Map key should not be empty")
125+
throw invalidInputError("Map key should not be empty")
126126
}
127127

128-
// Create ObjectMessage with the MAP_SET operation
128+
// RTLM20e - Create ObjectMessage with the MAP_SET operation
129129
val msg = ObjectMessage(
130130
operation = ObjectOperation(
131131
action = ObjectOperationAction.MapSet,
@@ -137,20 +137,20 @@ internal class DefaultLiveMap private constructor(
137137
)
138138
)
139139

140-
// Publish the message
140+
// RTLM20f - Publish the message
141141
liveObjects.publish(arrayOf(msg))
142142
}
143143

144144
private suspend fun removeAsync(keyName: String) {
145-
// Validate write API configuration
145+
// RTLM21b, RTLM21cm RTLM21d - Validate write API configuration
146146
adapter.throwIfInvalidWriteApiConfiguration(channelName)
147147

148148
// Validate input parameter
149149
if (keyName.isEmpty()) {
150-
throw objectError("Map key should not be empty")
150+
throw invalidInputError("Map key should not be empty")
151151
}
152152

153-
// Create ObjectMessage with the MAP_REMOVE operation
153+
// RTLM21e - Create ObjectMessage with the MAP_REMOVE operation
154154
val msg = ObjectMessage(
155155
operation = ObjectOperation(
156156
action = ObjectOperationAction.MapRemove,
@@ -159,7 +159,7 @@ internal class DefaultLiveMap private constructor(
159159
)
160160
)
161161

162-
// Publish the message
162+
// RTLM21f - Publish the message
163163
liveObjects.publish(arrayOf(msg))
164164
}
165165

@@ -199,6 +199,7 @@ internal class DefaultLiveMap private constructor(
199199

200200
/**
201201
* Creates an ObjectMap from map entries.
202+
* Spec: RTO11f4
202203
*/
203204
internal fun initialValue(entries: MutableMap<String, LiveMapValue>): MapCreatePayload {
204205
return MapCreatePayload(
@@ -214,6 +215,9 @@ internal class DefaultLiveMap private constructor(
214215
)
215216
}
216217

218+
/**
219+
* Spec: RTLM20e5
220+
*/
217221
private fun fromLiveMapValue(value: LiveMapValue): ObjectData {
218222
return when {
219223
value.isLiveMap || value.isLiveCounter -> {

0 commit comments

Comments
 (0)