Skip to content

Commit 9e841e8

Browse files
committed
api [nfc]: Establish that "local_id" is a JSON-encoded int
This will be used for local echoing. It's not yet fully documented; see CZO discussion: https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/local_id.2C.20queue_id.2Fsender_queue_id/near/2135340
1 parent 944aa5d commit 9e841e8

File tree

7 files changed

+52
-8
lines changed

7 files changed

+52
-8
lines changed

lib/api/model/events.dart

+16-1
Original file line numberDiff line numberDiff line change
@@ -682,11 +682,26 @@ class MessageEvent extends Event {
682682
@JsonKey(readValue: _readMessageValue, includeToJson: false)
683683
final Message message;
684684

685-
MessageEvent({required super.id, required this.message});
685+
// The format of this is documented to be chosen freely by the client.
686+
//
687+
// When present, this corresponds to the JSON-encoded int, "local_id",
688+
// from a previous [sendMessage] call by us.
689+
@JsonKey(fromJson: _localMessageIdFromJson, toJson: _localMessageIdToJson)
690+
final int? localMessageId;
691+
692+
MessageEvent({required super.id, required this.message, required this.localMessageId});
686693

687694
static Map<String, dynamic> _readMessageValue(Map<dynamic, dynamic> json, String key) =>
688695
{...json['message'] as Map<String, dynamic>, 'flags': json['flags']};
689696

697+
static int? _localMessageIdFromJson(Object? val) {
698+
return val == null ? null : int.parse(val as String);
699+
}
700+
701+
static String? _localMessageIdToJson(int? val) {
702+
return val?.toString();
703+
}
704+
690705
factory MessageEvent.fromJson(Map<String, dynamic> json) =>
691706
_$MessageEventFromJson(json);
692707

lib/api/model/events.g.dart

+10-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/api/route/messages.dart

+6-2
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ Future<SendMessageResult> sendMessage(
176176
required MessageDestination destination,
177177
required String content,
178178
String? queueId,
179-
String? localId,
179+
int? localId,
180180
bool? readBySender,
181181
}) {
182182
final supportsTypeDirect = connection.zulipFeatureLevel! >= 174; // TODO(server-7)
@@ -194,7 +194,11 @@ Future<SendMessageResult> sendMessage(
194194
}}),
195195
'content': RawParameter(content),
196196
if (queueId != null) 'queue_id': RawParameter(queueId),
197-
if (localId != null) 'local_id': localId, // TODO should this use RawParameter?
197+
// This is documented to be an optional String whose format is chosen freely
198+
// by the client. Use a JSON-encoded int consistently, so that we know how
199+
// to decode it when we receive the corresponding "message" event.
200+
// See also: [MessageEvent.localMessageId]
201+
if (localId != null) 'local_id': localId,
198202
if (readBySender != null) 'read_by_sender': readBySender,
199203
},
200204
overrideUserAgent: switch ((supportsReadBySender, readBySender)) {

test/api/model/events_checks.dart

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ extension SubscriptionUpdateEventChecks on Subject<SubscriptionUpdateEvent> {
3939

4040
extension MessageEventChecks on Subject<MessageEvent> {
4141
Subject<Message> get message => has((e) => e.message, 'message');
42+
Subject<int?> get localMessageId => has((e) => e.localMessageId, 'localMessageId');
4243
}
4344

4445
extension UpdateMessageEventChecks on Subject<UpdateMessageEvent> {

test/api/model/events_test.dart

+15
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,21 @@ void main() {
8888
check(mkEvent([MessageFlag.read])).message.flags.deepEquals([MessageFlag.read]);
8989
});
9090

91+
test('message: parsing local_message_id', () {
92+
final message = eg.streamMessage(flags: []);
93+
final event = Event.fromJson({
94+
'type': 'message',
95+
'id': 1,
96+
'message': deepToJson(message) as Map<String, dynamic>,
97+
'flags': <MessageFlag>[],
98+
'local_message_id': '1234',
99+
});
100+
check(event).isA<MessageEvent>().localMessageId.equals(1234);
101+
check(
102+
Event.fromJson(deepToJson(event) as Map<String, dynamic>)
103+
).isA<MessageEvent>().localMessageId.equals(1234);
104+
});
105+
91106
group('update_message', () {
92107
final message = eg.streamMessage();
93108
final baseJson = {

test/api/route/messages_test.dart

+3-3
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ void main() {
333333
required MessageDestination destination,
334334
required String content,
335335
String? queueId,
336-
String? localId,
336+
int? localId,
337337
bool? readBySender,
338338
required Map<String, String> expectedBodyFields,
339339
String? expectedUserAgent,
@@ -355,15 +355,15 @@ void main() {
355355
await checkSendMessage(connection,
356356
destination: StreamDestination(streamId, eg.t(topic)), content: content,
357357
queueId: 'abc:123',
358-
localId: '456',
358+
localId: 456,
359359
readBySender: true,
360360
expectedBodyFields: {
361361
'type': 'stream',
362362
'to': streamId.toString(),
363363
'topic': topic,
364364
'content': content,
365365
'queue_id': 'abc:123',
366-
'local_id': '"456"',
366+
'local_id': '456',
367367
'read_by_sender': 'true',
368368
});
369369
});

test/example_data.dart

+1-1
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ UserTopicEvent userTopicEvent(
621621
}
622622

623623
MessageEvent messageEvent(Message message) =>
624-
MessageEvent(id: 0, message: message);
624+
MessageEvent(id: 0, message: message, localMessageId: null);
625625

626626
DeleteMessageEvent deleteMessageEvent(List<StreamMessage> messages) {
627627
assert(messages.isNotEmpty);

0 commit comments

Comments
 (0)