Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/AblyLiveObjects/Utility/ExtendedJSONValue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ internal extension ExtendedJSONValue {

/// Converts an `ExtendedJSONValue` to an object.
///
/// The contract for what this will return are the same as those of `JSONValue.toJSONSerializationInputElemtn`, with one addition: any values in the input of case `.extra` will be passed to the `serializeExtraValue` function, and the result of this function call will be inserted into the output object.
/// The contract for what this will return are the same as those of `JSONValue.toJSONSerializationInputElement`, with one addition: any values in the input of case `.extra` will be passed to the `serializeExtraValue` function, and the result of this function call will be inserted into the output object.
func serialized(serializeExtraValue: (Extra) -> Any) -> Any {
switch self {
case let .object(underlying):
Expand Down
87 changes: 47 additions & 40 deletions Tests/AblyLiveObjectsTests/JS Integration Tests/ObjectsHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,8 @@ final class ObjectsHelper: Sendable {
// MARK: - Wire Object Messages

/// Creates a map create operation
func mapCreateOp(objectId: String? = nil, entries: [String: JSONValue]? = nil) -> [String: JSONValue] {
var operation: [String: JSONValue] = [
func mapCreateOp(objectId: String? = nil, entries: [String: WireValue]? = nil) -> [String: WireValue] {
var operation: [String: WireValue] = [
Comment thread
maratal marked this conversation as resolved.
"action": .number(NSNumber(value: Actions.mapCreate.rawValue)),
"nonce": .string(nonce()),
"map": .object(["semantics": .number(NSNumber(value: 0))]),
Expand All @@ -151,7 +151,7 @@ final class ObjectsHelper: Sendable {
}

/// Creates a map set operation
func mapSetOp(objectId: String, key: String, data: JSONValue) -> [String: JSONValue] {
func mapSetOp(objectId: String, key: String, data: WireValue) -> [String: WireValue] {
[
"operation": .object([
"action": .number(NSNumber(value: Actions.mapSet.rawValue)),
Expand All @@ -165,7 +165,7 @@ final class ObjectsHelper: Sendable {
}

/// Creates a map remove operation
func mapRemoveOp(objectId: String, key: String) -> [String: JSONValue] {
func mapRemoveOp(objectId: String, key: String) -> [String: WireValue] {
[
"operation": .object([
"action": .number(NSNumber(value: Actions.mapRemove.rawValue)),
Expand All @@ -178,8 +178,8 @@ final class ObjectsHelper: Sendable {
}

/// Creates a counter create operation
func counterCreateOp(objectId: String? = nil, count: Int? = nil) -> [String: JSONValue] {
var operation: [String: JSONValue] = [
func counterCreateOp(objectId: String? = nil, count: Int? = nil) -> [String: WireValue] {
var operation: [String: WireValue] = [
"action": .number(NSNumber(value: Actions.counterCreate.rawValue)),
"nonce": .string(nonce()),
]
Expand All @@ -196,7 +196,7 @@ final class ObjectsHelper: Sendable {
}

/// Creates a counter increment operation
func counterIncOp(objectId: String, amount: Int) -> [String: JSONValue] {
func counterIncOp(objectId: String, amount: Int) -> [String: WireValue] {
[
"operation": .object([
"action": .number(NSNumber(value: Actions.counterInc.rawValue)),
Expand All @@ -209,7 +209,7 @@ final class ObjectsHelper: Sendable {
}

/// Creates an object delete operation
func objectDeleteOp(objectId: String) -> [String: JSONValue] {
func objectDeleteOp(objectId: String) -> [String: WireValue] {
[
"operation": .object([
"action": .number(NSNumber(value: Actions.objectDelete.rawValue)),
Expand All @@ -222,11 +222,11 @@ final class ObjectsHelper: Sendable {
func mapObject(
objectId: String,
siteTimeserials: [String: String],
initialEntries: [String: JSONValue]? = nil,
materialisedEntries: [String: JSONValue]? = nil,
initialEntries: [String: WireValue]? = nil,
materialisedEntries: [String: WireValue]? = nil,
tombstone: Bool = false,
) -> [String: JSONValue] {
var object: [String: JSONValue] = [
) -> [String: WireValue] {
var object: [String: WireValue] = [
"objectId": .string(objectId),
"siteTimeserials": .object(siteTimeserials.mapValues { .string($0) }),
"tombstone": .bool(tombstone),
Expand All @@ -251,14 +251,14 @@ final class ObjectsHelper: Sendable {
initialCount: Int? = nil,
materialisedCount: Int? = nil,
tombstone: Bool = false,
) -> [String: JSONValue] {
let materialisedCountValue: JSONValue = if let materialisedCount {
) -> [String: WireValue] {
let materialisedCountValue: WireValue = if let materialisedCount {
.number(NSNumber(value: materialisedCount))
} else {
.null
}

var object: [String: JSONValue] = [
var object: [String: WireValue] = [
"objectId": .string(objectId),
"siteTimeserials": .object(siteTimeserials.mapValues { .string($0) }),
"tombstone": .bool(tombstone),
Expand All @@ -280,16 +280,16 @@ final class ObjectsHelper: Sendable {
channelName: String,
serial: String,
siteCode: String,
state: [[String: JSONValue]]? = nil,
) -> [String: JSONValue] {
state: [[String: WireValue]]? = nil,
) -> [String: WireValue] {
let stateWithSerials = state?.map { objectMessage in
var message = objectMessage
message["serial"] = .string(serial)
message["siteCode"] = .string(siteCode)
return message
}

let stateArray = stateWithSerials?.map { dict in JSONValue.object(dict) } ?? []
let stateArray = stateWithSerials?.map { dict in WireValue.object(dict) } ?? []

return [
"action": .number(NSNumber(value: 19)), // OBJECT
Expand All @@ -303,9 +303,9 @@ final class ObjectsHelper: Sendable {
func objectStateMessage(
channelName: String,
syncSerial: String,
state: [[String: JSONValue]]? = nil,
) -> [String: JSONValue] {
let stateArray = state?.map { dict in JSONValue.object(dict) } ?? []
state: [[String: WireValue]]? = nil,
) -> [String: WireValue] {
let stateArray = state?.map { dict in WireValue.object(dict) } ?? []
return [
"action": .number(NSNumber(value: 20)), // OBJECT_SYNC
"channel": .string(channelName),
Expand All @@ -316,32 +316,39 @@ final class ObjectsHelper: Sendable {

/// This is the equivalent of the JS ObjectHelper's channel.processMessage(createPM(…)).
private func processDeserializedProtocolMessage(
_ deserialized: [String: JSONValue],
_ deserialized: [String: WireValue],
channel: ARTRealtimeChannel,
) {
let jsonEncoder = ARTJsonEncoder()
let encoder = ARTJsonLikeEncoder(
rest: channel.internal.realtime!.rest,
delegate: jsonEncoder,
logger: channel.internal.logger,
)
) async {
await withCheckedContinuation { continuation in
channel.internal.queue.async {
let useBinaryProtocol = channel.realtimeInternal.options.useBinaryProtocol
let jsonLikeEncoderDelegate: ARTJsonLikeEncoderDelegate = useBinaryProtocol ? ARTMsgPackEncoder() : ARTJsonEncoder()

let encoder = ARTJsonLikeEncoder(
rest: channel.internal.realtime!.rest,
delegate: jsonLikeEncoderDelegate,
logger: channel.internal.logger,
)

let foundationObject = deserialized.toJSONSerializationInput
let protocolMessage = withExtendedLifetime(jsonEncoder) {
encoder.protocolMessage(from: foundationObject)!
}
let foundationObject = deserialized.toAblyPluginDataDictionary
let protocolMessage = withExtendedLifetime(jsonLikeEncoderDelegate) {
encoder.protocolMessage(from: foundationObject)!
}

channel.internal.onChannelMessage(protocolMessage)
channel.internal.onChannelMessage(protocolMessage)
continuation.resume()
}
}
}

/// Processes an object operation message on a channel
func processObjectOperationMessageOnChannel(
channel: ARTRealtimeChannel,
serial: String,
siteCode: String,
state: [[String: JSONValue]]? = nil,
) async throws {
processDeserializedProtocolMessage(
state: [[String: WireValue]]? = nil,
) async {
await processDeserializedProtocolMessage(
objectOperationMessage(
channelName: channel.name,
serial: serial,
Expand All @@ -356,9 +363,9 @@ final class ObjectsHelper: Sendable {
func processObjectStateMessageOnChannel(
channel: ARTRealtimeChannel,
syncSerial: String,
state: [[String: JSONValue]]? = nil,
) async throws {
processDeserializedProtocolMessage(
state: [[String: WireValue]]? = nil,
) async {
await processDeserializedProtocolMessage(
objectStateMessage(
channelName: channel.name,
syncSerial: syncSerial,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -513,15 +513,15 @@ private struct ObjectsIntegrationTests {
disabled: false,
allTransportsAndProtocols: false,
description: "OBJECT_SYNC sequence with object state \"tombstone\" property creates tombstoned object",
action: { ctx in
action: { ctx throws in
let root = ctx.root
let objectsHelper = ctx.objectsHelper
let channel = ctx.channel

let mapId = objectsHelper.fakeMapObjectId()
let counterId = objectsHelper.fakeCounterObjectId()

try await objectsHelper.processObjectStateMessageOnChannel(
await objectsHelper.processObjectStateMessageOnChannel(
channel: channel,
syncSerial: "serial:", // empty serial so sync sequence ends immediately
// add object states with tombstone=true
Expand Down Expand Up @@ -588,7 +588,7 @@ private struct ObjectsIntegrationTests {
#expect(try root.get(key: "counter") != nil, "Check counter exists on root before OBJECT_SYNC sequence with \"tombstone=true\"")

// inject an OBJECT_SYNC message where a counter is now tombstoned
try await objectsHelper.processObjectStateMessageOnChannel(
await objectsHelper.processObjectStateMessageOnChannel(
channel: channel,
syncSerial: "serial:", // empty serial so sync sequence ends immediately
state: [
Expand Down Expand Up @@ -647,7 +647,7 @@ private struct ObjectsIntegrationTests {
}()

// inject an OBJECT_SYNC message where a counter is now tombstoned
try await objectsHelper.processObjectStateMessageOnChannel(
await objectsHelper.processObjectStateMessageOnChannel(
channel: channel,
syncSerial: "serial:", // empty serial so sync sequence ends immediately
state: [
Expand Down