message_store: Rewrite RawMessage type using Zod.

This commit rework RawMessage and it's related type to use
Zod for parsing the data received from the server.
This commit is contained in:
afeefuddin 2024-06-15 15:27:13 +05:30 committed by Tim Abbott
parent f148a7a3ed
commit 17e2d46760
4 changed files with 101 additions and 77 deletions

View File

@ -259,7 +259,6 @@ EXEMPT_FILES = make_set(
"web/src/topic_list.ts", "web/src/topic_list.ts",
"web/src/topic_popover.js", "web/src/topic_popover.js",
"web/src/tutorial.js", "web/src/tutorial.js",
"web/src/types.ts",
"web/src/typing.ts", "web/src/typing.ts",
"web/src/typing_events.ts", "web/src/typing_events.ts",
"web/src/ui_init.js", "web/src/ui_init.js",

View File

@ -1,82 +1,109 @@
import _ from "lodash"; import _ from "lodash";
import {z} from "zod";
import * as blueslip from "./blueslip"; import * as blueslip from "./blueslip";
import * as people from "./people"; import * as people from "./people";
import type {Submessage} from "./submessage"; import {topic_link_schema} from "./types";
import type {TopicLink} from "./types";
import type {UserStatusEmojiInfo} from "./user_status"; import type {UserStatusEmojiInfo} from "./user_status";
const stored_messages = new Map<number, Message>(); const stored_messages = new Map<number, Message>();
export type MatchedMessage = { const matched_message_schema = z.object({
match_content?: string | undefined; match_content: z.optional(z.string()),
match_subject?: string | undefined; match_subject: z.optional(z.string()),
}; });
export type MessageReactionType = "unicode_emoji" | "realm_emoji" | "zulip_extra_emoji"; export type MatchedMessage = z.infer<typeof matched_message_schema>;
export type DisplayRecipientUser = { const message_reaction_type_schema = z.enum(["unicode_emoji", "realm_emoji", "zulip_extra_emoji"]);
email: string;
full_name: string;
id: number;
};
export type DisplayRecipient = string | DisplayRecipientUser[]; export type MessageReactionType = z.infer<typeof message_reaction_type_schema>;
export type MessageEditHistoryEntry = { const display_recipient_user_schema = z.object({
user_id: number | null; email: z.string(),
timestamp: number; full_name: z.string(),
prev_content?: string; id: z.number(),
prev_rendered_content?: string; });
prev_rendered_content_version?: number;
prev_stream?: number;
prev_topic?: string;
stream?: number;
topic?: string;
};
export type MessageReaction = { export type DisplayRecipientUser = z.infer<typeof display_recipient_user_schema>;
emoji_name: string;
emoji_code: string;
reaction_type: MessageReactionType;
user_id: number;
};
export type RawMessage = { const display_recipient_schema = z.union([z.string(), z.array(display_recipient_user_schema)]);
avatar_url: string | null;
client: string; export type DisplayRecipient = z.infer<typeof display_recipient_schema>;
content: string;
content_type: "text/html"; const message_edit_history_entry_schema = z.object({
display_recipient: DisplayRecipient; user_id: z.nullable(z.number()),
edit_history?: MessageEditHistoryEntry[]; timestamp: z.number(),
id: number; prev_content: z.optional(z.string()),
is_me_message: boolean; prev_rendered_content: z.optional(z.string()),
last_edit_timestamp?: number; prev_rendered_content_version: z.optional(z.number()),
reactions: MessageReaction[]; prev_stream: z.optional(z.number()),
recipient_id: number; prev_topic: z.optional(z.string()),
sender_email: string; stream: z.optional(z.number()),
sender_full_name: string; topic: z.optional(z.string()),
sender_id: number; });
sender_realm_str: string;
submessages: Submessage[]; export type MessageEditHistoryEntry = z.infer<typeof message_edit_history_entry_schema>;
timestamp: number;
flags: string[]; const message_reaction_schema = z.object({
} & ( emoji_name: z.string(),
| { emoji_code: z.string(),
type: "private"; reaction_type: message_reaction_type_schema,
topic_links?: undefined; user_id: z.number(),
} });
| {
type: "stream"; export type MessageReaction = z.infer<typeof message_reaction_schema>;
stream_id: number;
// Messages that come from the server use `subject`. export const submessage_schema = z.object({
// Messages that come from `send_message` use `topic`. id: z.number(),
subject?: string; sender_id: z.number(),
topic?: string; message_id: z.number(),
topic_links: TopicLink[]; content: z.string(),
} msg_type: z.string(),
) & });
MatchedMessage;
export const raw_message_schema = z.intersection(
z.intersection(
z.object({
avatar_url: z.nullable(z.string()),
client: z.string(),
content: z.string(),
content_type: z.literal("text/html"),
display_recipient: display_recipient_schema,
edit_history: z.optional(z.array(message_edit_history_entry_schema)),
id: z.number(),
is_me_message: z.boolean(),
last_edit_timestamp: z.optional(z.number()),
reactions: z.array(message_reaction_schema),
recipient_id: z.number(),
sender_email: z.string(),
sender_full_name: z.string(),
sender_id: z.number(),
sender_realm_str: z.string(),
submessages: z.array(submessage_schema),
timestamp: z.number(),
flags: z.array(z.string()),
}),
z.discriminatedUnion("type", [
z.object({
type: z.literal("private"),
topic_links: z.optional(z.array(z.undefined())),
}),
z.object({
type: z.literal("stream"),
stream_id: z.number(),
// Messages that come from the server use `subject`.
// Messages that come from `send_message` use `topic`.
subject: z.optional(z.string()),
topic: z.optional(z.string()),
topic_links: z.array(topic_link_schema),
}),
]),
),
matched_message_schema,
);
export type RawMessage = z.infer<typeof raw_message_schema>;
// We add these boolean properties to Raw message in // We add these boolean properties to Raw message in
// `message_store.convert_raw_message_to_message_with_booleans` method. // `message_store.convert_raw_message_to_message_with_booleans` method.

View File

@ -10,13 +10,7 @@ import {todo_widget_extra_data_schema} from "./todo_widget";
import type {TodoWidgetOutboundData} from "./todo_widget"; import type {TodoWidgetOutboundData} from "./todo_widget";
import * as widgetize from "./widgetize"; import * as widgetize from "./widgetize";
export type Submessage = { export type Submessage = z.infer<typeof message_store.submessage_schema>;
id: number;
sender_id: number;
message_id: number;
content: string;
msg_type: string;
};
export const zform_widget_extra_data_schema = z export const zform_widget_extra_data_schema = z
.object({ .object({

View File

@ -1,8 +1,12 @@
import {z} from "zod";
// TODO/typescript: Move this to server_events // TODO/typescript: Move this to server_events
export type TopicLink = { export const topic_link_schema = z.object({
text: string; text: z.string(),
url: string; url: z.string(),
}; });
export type TopicLink = z.infer<typeof topic_link_schema>;
// TODO/typescript: Move this to server_events_dispatch // TODO/typescript: Move this to server_events_dispatch
export type UserGroupUpdateEvent = { export type UserGroupUpdateEvent = {