Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ECO-5139] feat: add action and serial fields #1048

Merged
merged 1 commit into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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);
ttypic marked this conversation as resolved.
Show resolved Hide resolved
// (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;
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* (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;

ttypic marked this conversation as resolved.
Show resolved Hide resolved
/**
* (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";
ttypic marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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());
ttypic marked this conversation as resolved.
Show resolved Hide resolved
} 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);
ttypic marked this conversation as resolved.
Show resolved Hide resolved
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());
}
ttypic marked this conversation as resolved.
Show resolved Hide resolved
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() {
ttypic marked this conversation as resolved.
Show resolved Hide resolved
// 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);
}
ttypic marked this conversation as resolved.
Show resolved Hide resolved


@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);
}
ttypic marked this conversation as resolved.
Show resolved Hide resolved
}
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 @@
]
}
]
}
}
Loading