@@ -718,16 +718,8 @@ class UpdateMessageEvent extends Event {
718
718
719
719
// final String? streamName; // ignore
720
720
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;
731
723
732
724
// final List<TopicLink> topicLinks; // TODO handle
733
725
@@ -747,25 +739,103 @@ class UpdateMessageEvent extends Event {
747
739
required this .messageIds,
748
740
required this .flags,
749
741
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,
755
743
required this .origContent,
756
744
required this .origRenderedContent,
757
745
required this .content,
758
746
required this .renderedContent,
759
747
required this .isMeMessage,
760
748
});
761
749
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
+
762
756
factory UpdateMessageEvent .fromJson (Map <String , dynamic > json) =>
763
757
_$UpdateMessageEventFromJson (json);
764
758
765
759
@override
766
760
Map <String , dynamic > toJson () => _$UpdateMessageEventToJson (this );
767
761
}
768
762
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
+
769
839
/// A Zulip event of type `delete_message` : https://zulip.com/api/get-events#delete_message
770
840
@JsonSerializable (fieldRename: FieldRename .snake)
771
841
class DeleteMessageEvent extends Event {
0 commit comments