@@ -718,16 +718,8 @@ class UpdateMessageEvent extends Event {
718718
719719 // final String? streamName; // ignore
720720
721- @JsonKey (name: 'stream_id' )
722- final int ? origStreamId;
723- final int ? newStreamId;
724-
725- final PropagateMode ? propagateMode;
726-
727- @JsonKey (name: 'orig_subject' )
728- final TopicName ? origTopic;
729- @JsonKey (name: 'subject' )
730- final TopicName ? newTopic;
721+ @JsonKey (readValue: _readMoveData, fromJson: UpdateMessageMoveData .tryParseFromJson, includeToJson: false )
722+ final UpdateMessageMoveData ? moveData;
731723
732724 // final List<TopicLink> topicLinks; // TODO handle
733725
@@ -747,25 +739,103 @@ class UpdateMessageEvent extends Event {
747739 required this .messageIds,
748740 required this .flags,
749741 required this .editTimestamp,
750- required this .origStreamId,
751- required this .newStreamId,
752- required this .propagateMode,
753- required this .origTopic,
754- required this .newTopic,
742+ required this .moveData,
755743 required this .origContent,
756744 required this .origRenderedContent,
757745 required this .content,
758746 required this .renderedContent,
759747 required this .isMeMessage,
760748 });
761749
750+ static Map <String , dynamic > _readMoveData (Map <dynamic , dynamic > json, String key) {
751+ // Parsing [UpdateMessageMoveData] requires `json`, not the default `json[key]`.
752+ assert (json is Map <String , dynamic >); // value came through `fromJson` with this type
753+ return json as Map <String , dynamic >;
754+ }
755+
762756 factory UpdateMessageEvent .fromJson (Map <String , dynamic > json) =>
763757 _$UpdateMessageEventFromJson (json);
764758
765759 @override
766760 Map <String , dynamic > toJson () => _$UpdateMessageEventToJson (this );
767761}
768762
763+ /// Data structure representing a message move.
764+ class UpdateMessageMoveData {
765+ final int origStreamId;
766+ final int newStreamId;
767+
768+ final PropagateMode propagateMode;
769+
770+ final TopicName origTopic;
771+ final TopicName newTopic;
772+
773+ UpdateMessageMoveData ({
774+ required this .origStreamId,
775+ required this .newStreamId,
776+ required this .propagateMode,
777+ required this .origTopic,
778+ required this .newTopic,
779+ }) : assert (origStreamId != newStreamId || origTopic != newTopic);
780+
781+ /// Try to extract [UpdateMessageMoveData] from the JSON object for an
782+ /// [UpdateMessageEvent] .
783+ ///
784+ /// Returns `null` if there was no message move.
785+ ///
786+ /// Throws an error if the data is malformed.
787+ // When parsing this, 'stream_id', which is also present when there was only
788+ // a content edit, cannot be recovered if this ends up returning `null`.
789+ // This may matter if we ever need 'stream_id' when no message move occurred.
790+ static UpdateMessageMoveData ? tryParseFromJson (Map <String , Object ?> json) {
791+ final origStreamId = (json['stream_id' ] as num ? )? .toInt ();
792+ final newStreamIdRaw = (json['new_stream_id' ] as num ? )? .toInt ();
793+ final newStreamId = newStreamIdRaw ?? origStreamId;
794+
795+ final propagateModeString = json['propagate_mode' ] as String ? ;
796+ final propagateMode = propagateModeString == null ? null
797+ : PropagateMode .fromRawString (propagateModeString);
798+
799+ final origTopic = json['orig_subject' ] == null ? null
800+ : TopicName .fromJson (json['orig_subject' ] as String );
801+ final newTopicRaw = json['subject' ] == null ? null
802+ : TopicName .fromJson (json['subject' ] as String );
803+ final newTopic = newTopicRaw ?? origTopic;
804+
805+ if (origTopic == newTopic && origStreamId == newStreamId) {
806+ if (propagateMode != null ) {
807+ throw FormatException (
808+ 'Malformed UpdateMessageEvent: incoherent message-move fields; '
809+ 'propagate_mode present but no new channel or topic' );
810+ }
811+ return null ;
812+ }
813+
814+ if (origStreamId == null || newStreamId == null ) {
815+ // The `stream_id` field (aka origStreamId) is documented to be present on moves;
816+ // newStreamId should not be null either because it falls back to origStreamId.
817+ throw FormatException ('Malformed UpdateMessageEvent: move but no origStreamId' );
818+ }
819+ if (origTopic == null || newTopic == null ) {
820+ // The `orig_subject` field (aka origTopic) is documented to be present on moves;
821+ // newTopic should not be null either because it falls back to origTopic.
822+ throw FormatException ('Malformed UpdateMessageEvent: move but no origTopic' );
823+ }
824+ if (propagateMode == null ) {
825+ // The `propagate_mode` field (aka propagateMode) is documented to be present on moves.
826+ throw FormatException ('Malformed UpdateMessageEvent: move but no propagateMode' );
827+ }
828+
829+ return UpdateMessageMoveData (
830+ origStreamId: origStreamId,
831+ newStreamId: newStreamId,
832+ propagateMode: propagateMode,
833+ origTopic: origTopic,
834+ newTopic: newTopic,
835+ );
836+ }
837+ }
838+
769839/// A Zulip event of type `delete_message` : https://zulip.com/api/get-events#delete_message
770840@JsonSerializable (fieldRename: FieldRename .snake)
771841class DeleteMessageEvent extends Event {
0 commit comments