stream_data: Validate parameters with Zod.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg
2024-06-21 11:55:20 -07:00
committed by Anders Kaseorg
parent 9b511f5caa
commit e28240a9ac
5 changed files with 86 additions and 64 deletions

View File

@@ -4,8 +4,8 @@ import {page_params} from "./base_page_params";
import {$t, $t_html} from "./i18n";
import type {RealmDefaultSettings} from "./realm_user_settings_defaults";
import {realm} from "./state_data";
import {StreamPostPolicy} from "./stream_types";
import type {StreamSpecificNotificationSettings} from "./sub_store";
import {StreamPostPolicy} from "./sub_store";
import type {
FollowedTopicNotificationSettings,
PmNotificationSettings,

View File

@@ -1,6 +1,11 @@
import {z} from "zod";
import {realm_default_settings_schema} from "./realm_user_settings_defaults";
import {
never_subscribed_stream_schema,
stream_schema,
stream_subscription_schema,
} from "./stream_types";
import {user_settings_schema} from "./user_settings";
const NOT_TYPED_YET = z.unknown();
@@ -368,10 +373,10 @@ export const state_data_schema = z
.and(
z
.object({
subscriptions: NOT_TYPED_YET,
unsubscribed: NOT_TYPED_YET,
never_subscribed: NOT_TYPED_YET,
realm_default_streams: NOT_TYPED_YET,
subscriptions: z.array(stream_subscription_schema),
unsubscribed: z.array(stream_subscription_schema),
never_subscribed: z.array(never_subscribed_stream_schema),
realm_default_streams: z.array(stream_schema),
})
.transform((stream_data) => ({stream_data})),
)

View File

@@ -7,13 +7,14 @@ import type {User} from "./people";
import * as people from "./people";
import * as settings_config from "./settings_config";
import * as settings_data from "./settings_data";
import type {StateData} from "./state_data";
import {current_user, realm} from "./state_data";
import type {StreamPostPolicy} from "./stream_types";
import * as sub_store from "./sub_store";
import type {
ApiStreamSubscription,
NeverSubscribedStream,
Stream,
StreamPostPolicy,
StreamSpecificNotificationSettings,
StreamSubscription,
} from "./sub_store";
@@ -21,13 +22,6 @@ import * as user_groups from "./user_groups";
import {user_settings} from "./user_settings";
import * as util from "./util";
type StreamInitParams = {
subscriptions: ApiStreamSubscription[];
unsubscribed: ApiStreamSubscription[];
never_subscribed: NeverSubscribedStream[];
realm_default_streams: Stream[];
};
// Type for the parameter of `create_sub_from_server_data` function.
type ApiGenericStreamSubscription =
| NeverSubscribedStream
@@ -834,7 +828,7 @@ export function get_new_stream_announcements_stream(): string {
return "";
}
export function initialize(params: StreamInitParams): void {
export function initialize(params: StateData["stream_data"]): void {
/*
We get `params` data, which is data that we "own"
and which has already been removed from `state_data`.

57
web/src/stream_types.ts Normal file
View File

@@ -0,0 +1,57 @@
import {z} from "zod";
export const enum StreamPostPolicy {
EVERYONE = 1,
ADMINS = 2,
RESTRICT_NEW_MEMBERS = 3,
MODERATORS = 4,
}
// These types are taken from the `zerver/lib/types.py`.
export const stream_schema = z.object({
creator_id: z.number().nullable(),
date_created: z.number(),
description: z.string(),
first_message_id: z.number().nullable(),
history_public_to_subscribers: z.boolean(),
invite_only: z.boolean(),
is_announcement_only: z.boolean(),
is_web_public: z.boolean(),
message_retention_days: z.number().nullable(),
name: z.string(),
rendered_description: z.string(),
stream_id: z.number(),
stream_post_policy: z.nativeEnum({
EVERYONE: StreamPostPolicy.EVERYONE,
ADMINS: StreamPostPolicy.ADMINS,
RESTRICT_NEW_MEMBERS: StreamPostPolicy.RESTRICT_NEW_MEMBERS,
MODERATORS: StreamPostPolicy.MODERATORS,
}),
can_remove_subscribers_group: z.number(),
});
export const stream_specific_notification_settings_schema = z.object({
audible_notifications: z.boolean().nullable(),
desktop_notifications: z.boolean().nullable(),
email_notifications: z.boolean().nullable(),
push_notifications: z.boolean().nullable(),
wildcard_mentions_notify: z.boolean().nullable(),
});
export const never_subscribed_stream_schema = stream_schema.extend({
stream_weekly_traffic: z.number().nullable(),
subscribers: z.array(z.number()).optional(),
});
export const stream_properties_schema = stream_specific_notification_settings_schema.extend({
color: z.string(),
is_muted: z.boolean(),
pin_to_top: z.boolean(),
});
// This is the raw data we get from the server for a subscription.
export const stream_subscription_schema = stream_schema.merge(stream_properties_schema).extend({
email_address: z.string().optional(),
stream_weekly_traffic: z.number().nullable(),
subscribers: z.array(z.number()).optional(),
});

View File

@@ -1,57 +1,23 @@
import type {z} from "zod";
import * as blueslip from "./blueslip";
import type {
never_subscribed_stream_schema,
stream_properties_schema,
stream_schema,
stream_specific_notification_settings_schema,
stream_subscription_schema,
} from "./stream_types";
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<T>;
export const enum StreamPostPolicy {
EVERYONE = 1,
ADMINS = 2,
RESTRICT_NEW_MEMBERS = 3,
MODERATORS = 4,
}
// These types are taken from the `zerver/lib/types.py`.
export type Stream = {
creator_id: number | null;
date_created: number;
description: string;
first_message_id: number | null;
history_public_to_subscribers: boolean;
invite_only: boolean;
is_announcement_only: boolean;
is_web_public: boolean;
message_retention_days: number | null;
name: string;
rendered_description: string;
stream_id: number;
stream_post_policy: StreamPostPolicy;
can_remove_subscribers_group: number;
};
export type StreamSpecificNotificationSettings = {
audible_notifications: boolean | null;
desktop_notifications: boolean | null;
email_notifications: boolean | null;
push_notifications: boolean | null;
wildcard_mentions_notify: boolean | null;
};
export type NeverSubscribedStream = Stream & {
stream_weekly_traffic: number | null;
subscribers?: number[];
};
export type StreamProperties = StreamSpecificNotificationSettings & {
color: string;
is_muted: boolean;
pin_to_top: boolean;
};
// This is the raw data we get from the server for a subscription.
export type ApiStreamSubscription = (Stream & StreamProperties) & {
email_address?: string;
stream_weekly_traffic: number | null;
subscribers?: number[];
};
export type Stream = z.infer<typeof stream_schema>;
export type StreamSpecificNotificationSettings = z.infer<
typeof stream_specific_notification_settings_schema
>;
export type NeverSubscribedStream = z.infer<typeof never_subscribed_stream_schema>;
export type StreamProperties = z.infer<typeof stream_properties_schema>;
export type ApiStreamSubscription = z.infer<typeof stream_subscription_schema>;
// These properties are added in `stream_data` when hydrating the streams and are not present in the data we get from the server.
export type ExtraStreamAttrs = {