Skip to content

Commit 97ed1af

Browse files
committed
api [nfc]: Place TopicName before Conversation
1 parent 2d25c79 commit 97ed1af

File tree

2 files changed

+64
-64
lines changed

2 files changed

+64
-64
lines changed

lib/api/model/model.dart

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,65 @@ String? tryParseEmojiCodeToUnicode(String emojiCode) {
532532
}
533533
}
534534

535+
/// The name of a Zulip topic.
536+
// TODO(dart): Can we forbid calling Object members on this extension type?
537+
// (The lack of "implements Object" ought to do that, but doesn't.)
538+
// In particular an interpolation "foo > $topic" is a bug we'd like to catch.
539+
// TODO(dart): Can we forbid using this extension type as a key in a Map?
540+
// (The lack of "implements Object" arguably should do that, but doesn't.)
541+
// Using as a Map key is almost certainly a bug because it won't case-fold;
542+
// see for example #739, #980, #1205.
543+
extension type const TopicName(String _value) {
544+
/// The canonical form of the resolved-topic prefix.
545+
// This is RESOLVED_TOPIC_PREFIX in web:
546+
// https://github.com/zulip/zulip/blob/1fac99733/web/shared/src/resolved_topic.ts
547+
static const resolvedTopicPrefix = '✔ ';
548+
549+
/// Pattern for an arbitrary resolved-topic prefix.
550+
///
551+
/// These always begin with [resolvedTopicPrefix]
552+
/// but can be weird and go on longer, like "✔ ✔✔ ".
553+
// This is RESOLVED_TOPIC_PREFIX_RE in web:
554+
// https://github.com/zulip/zulip/blob/1fac99733/web/shared/src/resolved_topic.ts#L4-L12
555+
static final resolvedTopicPrefixRegexp = RegExp(r'^✔ [ ✔]*');
556+
557+
/// The string this topic is identified by in the Zulip API.
558+
///
559+
/// This should be used in constructing HTTP requests to the server,
560+
/// but rarely for other purposes. See [displayName] and [canonicalize].
561+
String get apiName => _value;
562+
563+
/// The string this topic is displayed as to the user in our UI.
564+
///
565+
/// At the moment this always equals [apiName].
566+
/// In the future this will become null for the "general chat" topic (#1250),
567+
/// so that UI code can identify when it needs to represent the topic
568+
/// specially in the way prescribed for "general chat".
569+
// TODO(#1250) carry out that plan
570+
String get displayName => _value;
571+
572+
/// The key to use for "same topic as" comparisons.
573+
String canonicalize() => apiName.toLowerCase();
574+
575+
/// Whether the topic starts with [resolvedTopicPrefix].
576+
bool get isResolved => _value.startsWith(resolvedTopicPrefix);
577+
578+
/// This [TopicName] plus the [resolvedTopicPrefix] prefix.
579+
TopicName resolve() => TopicName(resolvedTopicPrefix + _value);
580+
581+
/// A [TopicName] with [resolvedTopicPrefixRegexp] stripped if present.
582+
TopicName unresolve() =>
583+
TopicName(_value.replaceFirst(resolvedTopicPrefixRegexp, ''));
584+
585+
/// Whether [this] and [other] have the same canonical form,
586+
/// using [canonicalize].
587+
bool isSameAs(TopicName other) => canonicalize() == other.canonicalize();
588+
589+
TopicName.fromJson(this._value);
590+
591+
String toJson() => apiName;
592+
}
593+
535594
/// As in [StreamMessage.conversation] and [DmMessage.conversation].
536595
///
537596
/// Different from [MessageDestination], this information comes from
@@ -702,65 +761,6 @@ enum MessageFlag {
702761
String toJson() => _$MessageFlagEnumMap[this]!;
703762
}
704763

705-
/// The name of a Zulip topic.
706-
// TODO(dart): Can we forbid calling Object members on this extension type?
707-
// (The lack of "implements Object" ought to do that, but doesn't.)
708-
// In particular an interpolation "foo > $topic" is a bug we'd like to catch.
709-
// TODO(dart): Can we forbid using this extension type as a key in a Map?
710-
// (The lack of "implements Object" arguably should do that, but doesn't.)
711-
// Using as a Map key is almost certainly a bug because it won't case-fold;
712-
// see for example #739, #980, #1205.
713-
extension type const TopicName(String _value) {
714-
/// The canonical form of the resolved-topic prefix.
715-
// This is RESOLVED_TOPIC_PREFIX in web:
716-
// https://github.com/zulip/zulip/blob/1fac99733/web/shared/src/resolved_topic.ts
717-
static const resolvedTopicPrefix = '✔ ';
718-
719-
/// Pattern for an arbitrary resolved-topic prefix.
720-
///
721-
/// These always begin with [resolvedTopicPrefix]
722-
/// but can be weird and go on longer, like "✔ ✔✔ ".
723-
// This is RESOLVED_TOPIC_PREFIX_RE in web:
724-
// https://github.com/zulip/zulip/blob/1fac99733/web/shared/src/resolved_topic.ts#L4-L12
725-
static final resolvedTopicPrefixRegexp = RegExp(r'^✔ [ ✔]*');
726-
727-
/// The string this topic is identified by in the Zulip API.
728-
///
729-
/// This should be used in constructing HTTP requests to the server,
730-
/// but rarely for other purposes. See [displayName] and [canonicalize].
731-
String get apiName => _value;
732-
733-
/// The string this topic is displayed as to the user in our UI.
734-
///
735-
/// At the moment this always equals [apiName].
736-
/// In the future this will become null for the "general chat" topic (#1250),
737-
/// so that UI code can identify when it needs to represent the topic
738-
/// specially in the way prescribed for "general chat".
739-
// TODO(#1250) carry out that plan
740-
String get displayName => _value;
741-
742-
/// The key to use for "same topic as" comparisons.
743-
String canonicalize() => apiName.toLowerCase();
744-
745-
/// Whether the topic starts with [resolvedTopicPrefix].
746-
bool get isResolved => _value.startsWith(resolvedTopicPrefix);
747-
748-
/// This [TopicName] plus the [resolvedTopicPrefix] prefix.
749-
TopicName resolve() => TopicName(resolvedTopicPrefix + _value);
750-
751-
/// A [TopicName] with [resolvedTopicPrefixRegexp] stripped if present.
752-
TopicName unresolve() =>
753-
TopicName(_value.replaceFirst(resolvedTopicPrefixRegexp, ''));
754-
755-
/// Whether [this] and [other] have the same canonical form,
756-
/// using [canonicalize].
757-
bool isSameAs(TopicName other) => canonicalize() == other.canonicalize();
758-
759-
TopicName.fromJson(this._value);
760-
761-
String toJson() => apiName;
762-
}
763-
764764
@JsonSerializable(fieldRename: FieldRename.snake)
765765
class StreamMessage extends Message {
766766
@override

test/api/model/model_checks.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ extension UserChecks on Subject<User> {
2424
extension ZulipStreamChecks on Subject<ZulipStream> {
2525
}
2626

27+
extension TopicNameChecks on Subject<TopicName> {
28+
Subject<String> get apiName => has((x) => x.apiName, 'apiName');
29+
Subject<String> get displayName => has((x) => x.displayName, 'displayName');
30+
}
31+
2732
extension StreamConversationChecks on Subject<StreamConversation> {
2833
Subject<String?> get displayRecipient => has((x) => x.displayRecipient, 'displayRecipient');
2934
}
@@ -50,11 +55,6 @@ extension MessageChecks on Subject<Message> {
5055
Subject<String?> get matchTopic => has((e) => e.matchTopic, 'matchTopic');
5156
}
5257

53-
extension TopicNameChecks on Subject<TopicName> {
54-
Subject<String> get apiName => has((x) => x.apiName, 'apiName');
55-
Subject<String> get displayName => has((x) => x.displayName, 'displayName');
56-
}
57-
5858
extension StreamMessageChecks on Subject<StreamMessage> {
5959
Subject<int> get streamId => has((e) => e.streamId, 'streamId');
6060
Subject<TopicName> get topic => has((e) => e.topic, 'topic');

0 commit comments

Comments
 (0)