Skip to content

Commit 548c0b5

Browse files
committed
Added impl. for DefaultObjectMessage and WireObjectMessage along with relevant
error codes under `Errors.kt`
1 parent 68edd4b commit 548c0b5

5 files changed

Lines changed: 502 additions & 4 deletions

File tree

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.ably.lib.`object`
2+
3+
import io.ably.lib.types.AblyException
4+
import io.ably.lib.types.ErrorInfo
5+
6+
/**
7+
* Error codes and helpers for the path-based public API implementation.
8+
* Copied (and extended with the path-API codes) from the legacy package so
9+
* this package has no dependency on `io.ably.lib.objects`.
10+
*/
11+
internal enum class ObjectErrorCode(val code: Int) {
12+
BadRequest(40_000),
13+
InternalError(50_000),
14+
MaxMessageSizeExceeded(40_009),
15+
InvalidObject(92_000),
16+
InvalidInputParams(40_003),
17+
MapValueDataTypeUnsupported(40_013),
18+
PathNotResolved(92_005), // RTPO3c2 - write operation on a path that does not resolve
19+
ObjectsTypeMismatch(92_007), // RTTS5d2/RTTS9d2 - operation on a cast wrapper with mismatched resolved type
20+
// Channel mode and state validation error codes
21+
ChannelModeRequired(40_024),
22+
ChannelStateError(90_001),
23+
PublishAndApplyFailedDueToChannelState(92_008),
24+
}
25+
26+
internal enum class ObjectHttpStatusCode(val code: Int) {
27+
BadRequest(400),
28+
InternalServerError(500),
29+
}
30+
31+
internal fun objectException(
32+
errorMessage: String,
33+
errorCode: ObjectErrorCode,
34+
statusCode: ObjectHttpStatusCode = ObjectHttpStatusCode.BadRequest,
35+
cause: Throwable? = null,
36+
): AblyException {
37+
val errorInfo = ErrorInfo(errorMessage, statusCode.code, errorCode.code)
38+
return cause?.let { AblyException.fromErrorInfo(it, errorInfo) } ?: AblyException.fromErrorInfo(errorInfo)
39+
}
40+
41+
/** ErrorInfo 400 / 40003 - invalid input (RTLMV4a/b, RTLCV4a, key validation). */
42+
internal fun invalidInputError(message: String) =
43+
objectException(message, ObjectErrorCode.InvalidInputParams)
44+
45+
/** ErrorInfo 400 / 92005 - write operation on an unresolvable path (RTPO3c2). */
46+
internal fun pathNotResolvedError(path: String) =
47+
objectException("Path could not be resolved: \"$path\"", ObjectErrorCode.PathNotResolved)
48+
49+
/** ErrorInfo 400 / 92007 - resolved/wrapped type does not match the typed wrapper (RTTS5d2/RTTS9d2). */
50+
internal fun typeMismatchError(message: String) =
51+
objectException(message, ObjectErrorCode.ObjectsTypeMismatch)
52+
53+
/** ErrorInfo 500 / 92000 - invalid internal object state. */
54+
internal fun objectStateError(message: String) =
55+
objectException(message, ObjectErrorCode.InvalidObject, ObjectHttpStatusCode.InternalServerError)

liveobjects/src/main/kotlin/io/ably/lib/object/instance/DefaultInstanceSubscriptionEvent.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ import io.ably.lib.`object`.message.ObjectMessage
1010
* Spec: RTINS16e
1111
*/
1212
internal class DefaultInstanceSubscriptionEvent(
13-
private val objectAt: Instance,
13+
private val instance: Instance,
1414
private val message: ObjectMessage?,
1515
) : InstanceSubscriptionEvent {
1616

17-
override fun getObject(): Instance = objectAt
17+
override fun getObject(): Instance = instance
1818

1919
override fun getMessage(): ObjectMessage? = message
2020
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
package io.ably.lib.`object`.message
2+
3+
import com.google.gson.JsonElement
4+
import com.google.gson.JsonObject
5+
import io.ably.lib.`object`.objectStateError
6+
import java.util.*
7+
8+
/**
9+
* Builds the user-facing PublicAPI::ObjectMessage from an inbound wire
10+
* ObjectMessage that carried an operation. Mirrors ably-js
11+
* `objectmessage.ts#toUserFacingMessage`.
12+
*
13+
* Precondition (PAOM3a1): the source message has its `operation` populated.
14+
*
15+
* Spec: PAOM3
16+
*/
17+
internal fun WireObjectMessage.toPublicMessage(channelName: String): ObjectMessage =
18+
DefaultObjectMessage(this, channelName)
19+
20+
/**
21+
* PublicAPI::ObjectMessage implementation - a read-only view over the source
22+
* wire message. Spec: PAOM1, PAOM2
23+
*/
24+
internal class DefaultObjectMessage(
25+
private val message: WireObjectMessage,
26+
private val channelName: String,
27+
) : ObjectMessage {
28+
29+
private val operation: ObjectOperation = DefaultObjectOperation(
30+
message.operation ?: throw objectStateError("Cannot build public ObjectMessage without an operation") // PAOM3a1
31+
)
32+
33+
override fun getId(): String? = message.id // PAOM2a
34+
override fun getClientId(): String? = message.clientId // PAOM2b
35+
override fun getConnectionId(): String? = message.connectionId // PAOM2c
36+
override fun getTimestamp(): Long? = message.timestamp // PAOM2d
37+
override fun getChannel(): String = channelName // PAOM2e, PAOM3b
38+
override fun getOperation(): ObjectOperation = operation // PAOM2f
39+
override fun getSerial(): String? = message.serial // PAOM2g
40+
override fun getSerialTimestamp(): Long? = message.serialTimestamp // PAOM2h
41+
override fun getSiteCode(): String? = message.siteCode // PAOM2i
42+
override fun getExtras(): JsonObject? = message.extras // PAOM2j
43+
}
44+
45+
/**
46+
* PublicAPI::ObjectOperation implementation. Resolves the outbound-only
47+
* `*CreateWithObjectId` variants back to their derived MapCreate/CounterCreate
48+
* forms. Spec: PAOOP1, PAOOP2, PAOOP3
49+
*/
50+
internal class DefaultObjectOperation(private val operation: WireObjectOperation) : ObjectOperation {
51+
52+
override fun getAction(): ObjectOperationAction = operation.action.toPublic() // PAOOP2a
53+
54+
override fun getObjectId(): String = operation.objectId // PAOOP2b
55+
56+
// PAOOP3b - prefer mapCreate, else the MapCreate the WithObjectId variant was derived from
57+
override fun getMapCreate(): MapCreate? =
58+
(operation.mapCreate ?: operation.mapCreateWithObjectId?.derivedFrom)?.let { DefaultMapCreate(it) }
59+
60+
override fun getMapSet(): MapSet? = operation.mapSet?.let { DefaultMapSet(it) } // PAOOP2d
61+
62+
override fun getMapRemove(): MapRemove? = operation.mapRemove?.let { DefaultMapRemove(it) } // PAOOP2e
63+
64+
// PAOOP3c - prefer counterCreate, else the derived CounterCreate
65+
override fun getCounterCreate(): CounterCreate? =
66+
(operation.counterCreate ?: operation.counterCreateWithObjectId?.derivedFrom)?.let { DefaultCounterCreate(it) }
67+
68+
override fun getCounterInc(): CounterInc? = operation.counterInc?.let { DefaultCounterInc(it) } // PAOOP2g
69+
70+
override fun getObjectDelete(): ObjectDelete? = operation.objectDelete?.let { DefaultObjectDelete } // PAOOP2h
71+
72+
override fun getMapClear(): MapClear? = operation.mapClear?.let { DefaultMapClear } // PAOOP2i
73+
}
74+
75+
/** Spec: MCR2 */
76+
internal class DefaultMapCreate(private val mapCreate: WireMapCreate) : MapCreate {
77+
override fun getSemantics(): ObjectsMapSemantics = mapCreate.semantics.toPublic()
78+
override fun getEntries(): Map<String, ObjectsMapEntry> =
79+
Collections.unmodifiableMap(mapCreate.entries.mapValues { (_, entry) -> DefaultObjectsMapEntry(entry) })
80+
}
81+
82+
/** Spec: MST2 */
83+
internal class DefaultMapSet(private val mapSet: WireMapSet) : MapSet {
84+
override fun getKey(): String = mapSet.key
85+
override fun getValue(): ObjectData = DefaultObjectData(mapSet.value)
86+
}
87+
88+
/** Spec: MRM2 */
89+
internal class DefaultMapRemove(private val mapRemove: WireMapRemove) : MapRemove {
90+
override fun getKey(): String = mapRemove.key
91+
}
92+
93+
/** Spec: CCR2 */
94+
internal class DefaultCounterCreate(private val counterCreate: WireCounterCreate) : CounterCreate {
95+
override fun getCount(): Double = counterCreate.count
96+
}
97+
98+
/** Spec: CIN2 */
99+
internal class DefaultCounterInc(private val counterInc: WireCounterInc) : CounterInc {
100+
override fun getNumber(): Double = counterInc.number
101+
}
102+
103+
/** Spec: ODE2 - no attributes */
104+
internal object DefaultObjectDelete : ObjectDelete
105+
106+
/** Spec: MCL2 - no attributes */
107+
internal object DefaultMapClear : MapClear
108+
109+
/** Spec: OME2 */
110+
internal class DefaultObjectsMapEntry(private val entry: WireObjectsMapEntry) : ObjectsMapEntry {
111+
override fun getTombstone(): Boolean? = entry.tombstone
112+
override fun getTimeserial(): String? = entry.timeserial
113+
override fun getSerialTimestamp(): Long? = entry.serialTimestamp
114+
override fun getData(): ObjectData? = entry.data?.let { DefaultObjectData(it) }
115+
}
116+
117+
/**
118+
* Decoded public ObjectData: binary is delivered decoded (the wire form is
119+
* base64); there is no `encoding` field in the public shape. Spec: OD2
120+
*/
121+
internal class DefaultObjectData(private val data: WireObjectData) : ObjectData {
122+
override fun getObjectId(): String? = data.objectId
123+
override fun getString(): String? = data.string
124+
override fun getNumber(): Double? = data.number
125+
override fun getBoolean(): Boolean? = data.boolean
126+
override fun getBytes(): ByteArray? = data.bytes?.let { Base64.getDecoder().decode(it) }
127+
override fun getJson(): JsonElement? = data.json
128+
}
129+
130+
/** Internal action -> public enum; unrecognized wire values map to UNKNOWN. Spec: PAOOP2a, OOP2 */
131+
internal fun WireObjectOperationAction.toPublic(): ObjectOperationAction = when (this) {
132+
WireObjectOperationAction.MapCreate -> ObjectOperationAction.MAP_CREATE
133+
WireObjectOperationAction.MapSet -> ObjectOperationAction.MAP_SET
134+
WireObjectOperationAction.MapRemove -> ObjectOperationAction.MAP_REMOVE
135+
WireObjectOperationAction.CounterCreate -> ObjectOperationAction.COUNTER_CREATE
136+
WireObjectOperationAction.CounterInc -> ObjectOperationAction.COUNTER_INC
137+
WireObjectOperationAction.ObjectDelete -> ObjectOperationAction.OBJECT_DELETE
138+
WireObjectOperationAction.MapClear -> ObjectOperationAction.MAP_CLEAR
139+
WireObjectOperationAction.Unknown -> ObjectOperationAction.UNKNOWN
140+
}
141+
142+
/** Internal semantics -> public enum. Spec: OMP2 */
143+
internal fun WireObjectsMapSemantics.toPublic(): ObjectsMapSemantics = when (this) {
144+
WireObjectsMapSemantics.LWW -> ObjectsMapSemantics.LWW
145+
WireObjectsMapSemantics.Unknown -> ObjectsMapSemantics.UNKNOWN
146+
}

0 commit comments

Comments
 (0)