Skip to content

Commit 373e23a

Browse files
committed
api [nfc]: Add TopicName.processLikeServer
The point of this helper is to replicate what a topic sent from the client will become, after being processed by the server. This important when trying to create a local copy of a stream message, whose topic can get translated when it's delivered by the server.
1 parent 000cc84 commit 373e23a

File tree

3 files changed

+81
-9
lines changed

3 files changed

+81
-9
lines changed

lib/api/model/model.dart

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,15 @@ String? tryParseEmojiCodeToUnicode(String emojiCode) {
550550
}
551551
}
552552

553+
/// The topic servers understand to mean "there is no topic".
554+
///
555+
/// This should match
556+
/// https://github.com/zulip/zulip/blob/6.0/zerver/actions/message_edit.py#L940
557+
/// or similar logic at the latest `main`.
558+
// This is hardcoded in the server, and therefore untranslated; that's
559+
// zulip/zulip#3639.
560+
const String kNoTopicTopic = '(no topic)';
561+
553562
/// The name of a Zulip topic.
554563
// TODO(dart): Can we forbid calling Object members on this extension type?
555564
// (The lack of "implements Object" ought to do that, but doesn't.)
@@ -600,6 +609,53 @@ extension type const TopicName(String _value) {
600609
/// using [canonicalize].
601610
bool isSameAs(TopicName other) => canonicalize() == other.canonicalize();
602611

612+
/// Process this topic to match how it would appear on a message object from
613+
/// the server.
614+
///
615+
/// This returns the [TopicName] the server would be predicted to include
616+
/// in a message object resulting from sending to this [TopicName]
617+
/// in a [sendMessage] request.
618+
///
619+
/// This [TopicName] is required to have no leading or trailing whitespace.
620+
///
621+
/// For a client that supports empty topics, when FL>=334, the server converts
622+
/// `store.realmEmptyTopicDisplayName` to an empty string; when FL>=370,
623+
/// the server converts "(no topic)" to an empty string as well.
624+
///
625+
/// See API docs:
626+
/// https://zulip.com/api/send-message#parameter-topic
627+
TopicName processLikeServer({
628+
required int zulipFeatureLevel,
629+
required String? realmEmptyTopicDisplayName,
630+
}) {
631+
assert(_value.trim() == _value);
632+
// TODO(server-10) simplify this away
633+
if (zulipFeatureLevel < 334) {
634+
// From the API docs:
635+
// > Before Zulip 10.0 (feature level 334), empty string was not a valid
636+
// > topic name for channel messages.
637+
assert(_value.isNotEmpty);
638+
return this;
639+
}
640+
641+
// TODO(server-10) simplify this away
642+
if (zulipFeatureLevel < 370 && _value == kNoTopicTopic) {
643+
// From the API docs:
644+
// > Before Zulip 10.0 (feature level 370), "(no topic)" was not
645+
// > interpreted as an empty string.
646+
return TopicName(kNoTopicTopic);
647+
}
648+
649+
if (_value == kNoTopicTopic || _value == realmEmptyTopicDisplayName) {
650+
// From the API docs:
651+
// > When "(no topic)" or the value of realm_empty_topic_display_name
652+
// > found in the POST /register response is used for [topic],
653+
// > it is interpreted as an empty string.
654+
return TopicName('');
655+
}
656+
return TopicName(_value);
657+
}
658+
603659
TopicName.fromJson(this._value);
604660

605661
String toJson() => apiName;

lib/api/route/messages.dart

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,6 @@ const int kMaxTopicLengthCodePoints = 60;
178178
// https://zulip.com/api/send-message#parameter-content
179179
const int kMaxMessageLengthCodePoints = 10000;
180180

181-
/// The topic servers understand to mean "there is no topic".
182-
///
183-
/// This should match
184-
/// https://github.com/zulip/zulip/blob/6.0/zerver/actions/message_edit.py#L940
185-
/// or similar logic at the latest `main`.
186-
// This is hardcoded in the server, and therefore untranslated; that's
187-
// zulip/zulip#3639.
188-
const String kNoTopicTopic = '(no topic)';
189-
190181
/// https://zulip.com/api/send-message
191182
Future<SendMessageResult> sendMessage(
192183
ApiConnection connection, {

test/api/model/model_test.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,31 @@ void main() {
161161

162162
doCheck(eg.t('✔ a'), eg.t('✔ b'), false);
163163
});
164+
165+
test('processLikeServer', () {
166+
final emptyTopicDisplayName = eg.defaultRealmEmptyTopicDisplayName;
167+
void doCheck(TopicName topic, TopicName expected, int zulipFeatureLevel) {
168+
check(topic.processLikeServer(
169+
zulipFeatureLevel: zulipFeatureLevel,
170+
realmEmptyTopicDisplayName: emptyTopicDisplayName),
171+
).equals(expected);
172+
}
173+
174+
check(() => eg.t('').processLikeServer(
175+
zulipFeatureLevel: 333,
176+
realmEmptyTopicDisplayName: emptyTopicDisplayName),
177+
).throws<void>();
178+
doCheck(eg.t('(no topic)'), eg.t('(no topic)'), 333);
179+
doCheck(eg.t(emptyTopicDisplayName), eg.t(emptyTopicDisplayName), 333);
180+
doCheck(eg.t('other topic'), eg.t('other topic'), 333);
181+
182+
doCheck(eg.t(''), eg.t(''), 334);
183+
doCheck(eg.t('(no topic)'), eg.t('(no topic)'), 334);
184+
doCheck(eg.t(emptyTopicDisplayName), eg.t(''), 334);
185+
doCheck(eg.t('other topic'), eg.t('other topic'), 334);
186+
187+
doCheck(eg.t('(no topic)'), eg.t(''), 370);
188+
});
164189
});
165190

166191
group('DmMessage', () {

0 commit comments

Comments
 (0)