Skip to content

Commit

Permalink
Merge pull request #1048 from ably/ECO-5139/parse-action-and-serial
Browse files Browse the repository at this point in the history
[ECO-5139] feat: add `action` and `serial` fields
  • Loading branch information
ttypic authored Nov 28, 2024
2 parents 1ba7723 + 0c65aff commit 6c0ffcf
Show file tree
Hide file tree
Showing 7 changed files with 225 additions and 3 deletions.
7 changes: 7 additions & 0 deletions lib/src/main/java/io/ably/lib/realtime/ChannelBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.ably.lib.types.DeltaExtras;
import io.ably.lib.types.ErrorInfo;
import io.ably.lib.types.Message;
import io.ably.lib.types.MessageAction;
import io.ably.lib.types.MessageDecodeException;
import io.ably.lib.types.MessageSerializer;
import io.ably.lib.types.PaginatedResult;
Expand Down Expand Up @@ -843,6 +844,12 @@ private void onMessage(final ProtocolMessage protocolMessage) {
if(msg.connectionId == null) msg.connectionId = protocolMessage.connectionId;
if(msg.timestamp == 0) msg.timestamp = protocolMessage.timestamp;
if(msg.id == null) msg.id = protocolMessage.id + ':' + i;
// (TM2p)
if(msg.version == null) msg.version = String.format("%s:%03d", protocolMessage.channelSerial, i);
// (TM2k)
if(msg.serial == null && msg.action == MessageAction.MESSAGE_CREATE) msg.serial = msg.version;
// (TM2o)
if(msg.createdAt == null && msg.action == MessageAction.MESSAGE_CREATE) msg.createdAt = msg.timestamp;

try {
msg.decode(options, decodingContext);
Expand Down
14 changes: 14 additions & 0 deletions lib/src/main/java/io/ably/lib/types/BaseMessage.java
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,20 @@ protected Long readLong(final JsonObject map, final String key) {
return element.getAsLong();
}

/**
* Read an optional numerical value.
* @return The value, or null if the key was not present in the map.
* @throws ClassCastException if an element exists for that key and that element is not a {@link JsonPrimitive}
* or is not a valid int value.
*/
protected Integer readInt(final JsonObject map, final String key) {
final JsonElement element = map.get(key);
if (null == element || element instanceof JsonNull) {
return null;
}
return element.getAsInt();
}

/* Msgpack processing */
boolean readField(MessageUnpacker unpacker, String fieldName, MessageFormat fieldType) throws IOException {
boolean result = true;
Expand Down
78 changes: 78 additions & 0 deletions lib/src/main/java/io/ably/lib/types/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,41 @@ public class Message extends BaseMessage {
*/
public String connectionKey;

/**
* (TM2k) serial string – an opaque string that uniquely identifies the message. If a message received from Ably
* (whether over realtime or REST, eg history) with an action of MESSAGE_CREATE does not contain a serial,
* the SDK must set it equal to its version.
*/
public String serial;

/**
* (TM2p) version string – an opaque string that uniquely identifies the message, and is different for different versions.
* If a message received from Ably over a realtime transport does not contain a version,
* the SDK must set it to <channelSerial>:<padded_index> from the channelSerial field of the enclosing ProtocolMessage,
* and padded_index is the index of the message inside the messages array of the ProtocolMessage,
* left-padded with 0s to three digits (for example, the second entry might be foo:001)
*/
public String version;

/**
* (TM2j) action enum
*/
public MessageAction action;

/**
* (TM2o) createdAt time in milliseconds since epoch. If a message received from Ably
* (whether over realtime or REST, eg history) with an action of MESSAGE_CREATE does not contain a createdAt,
* the SDK must set it equal to the TM2f timestamp.
*/
public Long createdAt;

private static final String NAME = "name";
private static final String EXTRAS = "extras";
private static final String CONNECTION_KEY = "connectionKey";
private static final String SERIAL = "serial";
private static final String VERSION = "version";
private static final String ACTION = "action";
private static final String CREATED_AT = "createdAt";

/**
* Default constructor
Expand Down Expand Up @@ -128,6 +160,10 @@ void writeMsgpack(MessagePacker packer) throws IOException {
int fieldCount = super.countFields();
if(name != null) ++fieldCount;
if(extras != null) ++fieldCount;
if(serial != null) ++fieldCount;
if(version != null) ++fieldCount;
if(action != null) ++fieldCount;
if(createdAt != null) ++fieldCount;
packer.packMapHeader(fieldCount);
super.writeFields(packer);
if(name != null) {
Expand All @@ -138,6 +174,22 @@ void writeMsgpack(MessagePacker packer) throws IOException {
packer.packString(EXTRAS);
extras.write(packer);
}
if(serial != null) {
packer.packString(SERIAL);
packer.packString(serial);
}
if(version != null) {
packer.packString(VERSION);
packer.packString(version);
}
if(action != null) {
packer.packString(ACTION);
packer.packInt(action.ordinal());
}
if(createdAt != null) {
packer.packString(CREATED_AT);
packer.packLong(createdAt);
}
}

Message readMsgpack(MessageUnpacker unpacker) throws IOException {
Expand All @@ -157,6 +209,14 @@ Message readMsgpack(MessageUnpacker unpacker) throws IOException {
name = unpacker.unpackString();
} else if (fieldName.equals(EXTRAS)) {
extras = MessageExtras.read(unpacker);
} else if (fieldName.equals(SERIAL)) {
serial = unpacker.unpackString();
} else if (fieldName.equals(VERSION)) {
version = unpacker.unpackString();
} else if (fieldName.equals(ACTION)) {
action = MessageAction.tryFindByOrdinal(unpacker.unpackInt());
} else if (fieldName.equals(CREATED_AT)) {
createdAt = unpacker.unpackLong();
} else {
Log.v(TAG, "Unexpected field: " + fieldName);
unpacker.skipValue();
Expand Down Expand Up @@ -313,6 +373,12 @@ protected void read(final JsonObject map) throws MessageDecodeException {
}
extras = MessageExtras.read((JsonObject) extrasElement);
}

serial = readString(map, SERIAL);
version = readString(map, VERSION);
Integer actionOrdinal = readInt(map, ACTION);
action = actionOrdinal == null ? null : MessageAction.tryFindByOrdinal(actionOrdinal);
createdAt = readLong(map, CREATED_AT);
}

public static class Serializer implements JsonSerializer<Message>, JsonDeserializer<Message> {
Expand All @@ -328,6 +394,18 @@ public JsonElement serialize(Message message, Type typeOfMessage, JsonSerializat
if (message.connectionKey != null) {
json.addProperty(CONNECTION_KEY, message.connectionKey);
}
if (message.serial != null) {
json.addProperty(SERIAL, message.serial);
}
if (message.version != null) {
json.addProperty(VERSION, message.version);
}
if (message.action != null) {
json.addProperty(ACTION, message.action.ordinal());
}
if (message.createdAt != null) {
json.addProperty(CREATED_AT, message.createdAt);
}
return json;
}

Expand Down
15 changes: 15 additions & 0 deletions lib/src/main/java/io/ably/lib/types/MessageAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.ably.lib.types;

public enum MessageAction {
MESSAGE_UNSET, // 0
MESSAGE_CREATE, // 1
MESSAGE_UPDATE, // 2
MESSAGE_DELETE, // 3
ANNOTATION_CREATE, // 4
ANNOTATION_DELETE, // 5
META_OCCUPANCY; // 6

static MessageAction tryFindByOrdinal(int ordinal) {
return values().length <= ordinal ? null: values()[ordinal];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

Expand All @@ -17,7 +19,9 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import io.ably.lib.types.MessageAction;
import io.ably.lib.types.MessageExtras;
import io.ably.lib.types.Param;
import io.ably.lib.util.Serialisation;
import org.junit.Ignore;
import org.junit.Rule;
Expand Down Expand Up @@ -970,4 +974,40 @@ public void opaque_message_extras() throws AblyException {
}
}
}

/**
* Check that important chat SDK fields are populated (serial, action, createdAt)
*/
@Test
public void should_have_serial_action_createdAt() throws AblyException {
ClientOptions opts = createOptions(testVars.keys[7].keyStr);
opts.clientId = "chat";
try (AblyRealtime realtime = new AblyRealtime(opts)) {
final Channel channel = realtime.channels.get("foo::$chat::$chatMessages");
CompletionWaiter msgComplete = new CompletionWaiter();
channel.subscribe(message -> {
assertNotNull(message.serial);
assertNotNull(message.version);
assertNotNull(message.createdAt);
assertEquals(MessageAction.MESSAGE_CREATE, message.action);
assertEquals("chat.message", message.name);
assertEquals("hello world!", ((JsonObject)message.data).get("text").getAsString());
msgComplete.onSuccess();
});

/* publish to the channel */
JsonObject chatMessage = new JsonObject();
chatMessage.addProperty("text", "hello world!");
realtime.request(
"POST",
"/chat/v2/rooms/foo/messages",
new Param[] { new Param("v", 3) },
HttpUtils.requestBodyFromGson(chatMessage, opts.useBinaryProtocol),
null
);

// wait until we get message on the channel
assertNull(msgComplete.waitFor(1, 10_000));
}
}
}
65 changes: 65 additions & 0 deletions lib/src/test/java/io/ably/lib/types/MessageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,69 @@ public void serialize_message_with_name_and_data() {
assertEquals("test-data", serializedObject.get("data").getAsString());
assertEquals("test-name", serializedObject.get("name").getAsString());
}

@Test
public void serialize_message_with_serial() {
// Given
Message message = new Message("test-name", "test-data");
message.clientId = "test-client-id";
message.connectionKey = "test-key";
message.action = MessageAction.MESSAGE_CREATE;
message.serial = "01826232498871-001@abcdefghij:001";

// When
JsonElement serializedElement = serializer.serialize(message, null, null);

// Then
JsonObject serializedObject = serializedElement.getAsJsonObject();
assertEquals("test-client-id", serializedObject.get("clientId").getAsString());
assertEquals("test-key", serializedObject.get("connectionKey").getAsString());
assertEquals("test-data", serializedObject.get("data").getAsString());
assertEquals("test-name", serializedObject.get("name").getAsString());
assertEquals(1, serializedObject.get("action").getAsInt());
assertEquals("01826232498871-001@abcdefghij:001", serializedObject.get("serial").getAsString());
}

@Test
public void deserialize_message_with_serial() throws Exception {
// Given
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("clientId", "test-client-id");
jsonObject.addProperty("data", "test-data");
jsonObject.addProperty("name", "test-name");
jsonObject.addProperty("action", 1);
jsonObject.addProperty("serial", "01826232498871-001@abcdefghij:001");

// When
Message message = Message.fromEncoded(jsonObject, new ChannelOptions());

// Then
assertEquals("test-client-id", message.clientId);
assertEquals("test-data", message.data);
assertEquals("test-name", message.name);
assertEquals(MessageAction.MESSAGE_CREATE, message.action);
assertEquals("01826232498871-001@abcdefghij:001", message.serial);
}


@Test
public void deserialize_message_with_unknown_action() throws Exception {
// Given
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("clientId", "test-client-id");
jsonObject.addProperty("data", "test-data");
jsonObject.addProperty("name", "test-name");
jsonObject.addProperty("action", 10);
jsonObject.addProperty("serial", "01826232498871-001@abcdefghij:001");

// When
Message message = Message.fromEncoded(jsonObject, new ChannelOptions());

// Then
assertEquals("test-client-id", message.clientId);
assertEquals("test-data", message.data);
assertEquals("test-name", message.name);
assertNull(message.action);
assertEquals("01826232498871-001@abcdefghij:001", message.serial);
}
}
9 changes: 6 additions & 3 deletions lib/src/test/resources/local/testAppSpec.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@
},
{
"capability": "{\"persisted:text_protocol:channel0\":[\"publish\",\"subscribe\",\"history\"],\"persisted:text_protocol:channel1\":[\"publish\",\"subscribe\",\"history\"],\"persisted:binary_protocol:channel0\":[\"publish\",\"subscribe\",\"history\"],\"persisted:binary_protocol:channel1\":[\"publish\",\"subscribe\",\"history\"],\"persisted:*\":[\"subscribe\",\"history\"]}"
}
],
},
{
"capability": "{ \"[*]*\":[\"*\"] }"
}
],
"namespaces": [
{
"id": "persisted",
Expand Down Expand Up @@ -78,4 +81,4 @@
]
}
]
}
}

0 comments on commit 6c0ffcf

Please sign in to comment.